UNPKG

@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
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 };