@radiolise/metadata-client
Version:
Library for subscribing to metadata of ICY radio streams; powered by the Radiolise API, based on WebSockets
127 lines (122 loc) • 3.4 kB
JavaScript
var ErrorTypes = /* @__PURE__ */ ((ErrorTypes2) => {
ErrorTypes2["MALFORMED_PAYLOAD"] = "malformedPayload";
ErrorTypes2["SERVER_UNREACHABLE"] = "serverUnreachable";
ErrorTypes2["SERVER_HTTP_ERROR"] = "serverHttpError";
ErrorTypes2["NON_ICY_RESOURCE"] = "nonIcyResource";
return ErrorTypes2;
})(ErrorTypes || {});
var name = "@radiolise/metadata-client";
const NO_OPERATION = () => {
};
function isFunction(value) {
return typeof value === "function";
}
function parsePayload(rawData) {
return JSON.parse(rawData);
}
function unwrapHandler(observer) {
return (isFunction(observer) ? observer : observer.next)?.bind(observer);
}
function addHandler(set, handler) {
if (!handler)
return;
set.add(handler);
}
function deleteHandler(set, handler) {
if (!handler)
return;
set.delete(handler);
}
function invokeHandlers(handlers, value) {
for (const handler of handlers) {
handler(value);
}
}
function createMetadataClient(options) {
const { url: socketUrl, reconnect = true, reconnectDelay = 2e3 } = options;
const connect = async () => {
client = new WebSocket(socketUrl, "v1");
client.addEventListener("message", (event) => {
const payload = parsePayload(event.data);
if (payload.action === "setTitle") {
nowPlaying = { title: payload.data.title };
} else if (payload.action === "reportError") {
nowPlaying = { title: "", error: payload.data.type };
}
invokeHandlers(updateHandlers, nowPlaying);
});
client.addEventListener("close", (event) => {
if (event.wasClean) {
return;
}
onSocketError(event.code);
if (client && reconnect) {
setTimeout(connect, reconnectDelay);
}
});
await ensureSocketReady();
if (streamUrl) {
trackStream(streamUrl);
}
};
const terminate = () => {
client?.close();
client = void 0;
};
const ensureSocketReady = () => {
return new Promise((resolve, reject) => {
if (client === void 0) {
return reject(new Error(`[${name}] already terminated`));
}
if (client.readyState === WebSocket.OPEN) {
return resolve(client);
}
client.addEventListener("open", () => resolve(client), { once: true });
});
};
const dispatch = async (payload) => {
const socket = await ensureSocketReady();
socket.send(JSON.stringify(payload));
};
const trackStream = (url) => {
streamUrl = url?.toString();
if (!streamUrl) {
return dispatch({
action: "unsubscribe"
});
}
return dispatch({
action: "subscribe",
data: { url: streamUrl }
});
};
const subscribe = (observer) => {
const next = unwrapHandler(observer);
if (!next) {
return {
unsubscribe: NO_OPERATION
};
}
next(nowPlaying);
addHandler(updateHandlers, next);
return {
unsubscribe: () => {
deleteHandler(updateHandlers, next);
}
};
};
const metadataClient = {
[Symbol.observable || "@@observable"]: () => metadataClient,
trackStream,
terminate,
subscribe
};
const updateHandlers = /* @__PURE__ */ new Set();
let client;
let nowPlaying = { title: "" };
let streamUrl;
const onSocketError = options.onSocketError?.bind(metadataClient) ?? NO_OPERATION;
connect();
return metadataClient;
}
export { ErrorTypes, createMetadataClient };