From 72d87b91bf9df489cdc04e7c412a1f8213e81343 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Thu, 17 Jul 2025 18:09:41 +0200 Subject: [PATCH] feat: bunch of art-controller improvements --- .../blur/artwork-controller/_applet.astro | 87 ++++++++++++------- src/pages/engine/audio/_applet.astro | 27 ++++++ src/pages/engine/audio/types.d.ts | 2 + src/pages/engine/queue/_applet.astro | 6 ++ src/pages/orchestrator/primary/_applet.astro | 26 +++--- src/scripts/applet/common.ts | 64 ++++++++++---- src/scripts/common.ts | 13 ++- src/scripts/engine/queue/worker.ts | 27 +++--- src/scripts/input/native-fs/common.ts | 15 +++- src/scripts/input/native-fs/worker.ts | 13 ++- src/scripts/input/opensubsonic/common.ts | 31 ++++--- src/scripts/input/opensubsonic/worker.ts | 20 ++--- src/scripts/input/s3/common.ts | 29 +++++-- src/scripts/input/s3/worker.ts | 15 ++-- src/scripts/processor/artwork/worker.ts | 10 ++- src/scripts/processor/metadata/common.ts | 8 +- src/scripts/theme/webamp/index.ts | 8 +- 17 files changed, 269 insertions(+), 132 deletions(-) diff --git a/src/pages/constituent/blur/artwork-controller/_applet.astro b/src/pages/constituent/blur/artwork-controller/_applet.astro index dedff2d..fe74dae 100644 --- a/src/pages/constituent/blur/artwork-controller/_applet.astro +++ b/src/pages/constituent/blur/artwork-controller/_applet.astro @@ -374,7 +374,7 @@ import "@styles/diffuse/fonts.css"; // Applet connections const configurator = { - input: await applet("/configurator/input"), + input: applet("/configurator/input"), }; const engine = { @@ -383,13 +383,13 @@ import "@styles/diffuse/fonts.css"; }; const orchestrator = { - primary: await applet("/orchestrator/primary", { + primary: applet("/orchestrator/primary", { groupId: context.groupId, }), }; const processor = { - artwork: await applet("/processor/artwork"), + artwork: applet("/processor/artwork"), }; //////////////////////////////////////////// @@ -464,11 +464,16 @@ import "@styles/diffuse/fonts.css"; // ORCHESTRATED - context.settled().then(() => { - if (context.isMainInstance()) - orchestrator.primary.sendAction("monitor_audio_end", undefined, { timeoutDuration: 60000 }); + context.settled().then(async () => { + if (context.isMainInstance()) monitorAudioStuff(); }); + async function monitorAudioStuff() { + (await orchestrator.primary).sendAction("monitorAudioEnd", undefined, { + timeoutDuration: 60000, + }); + } + //////////////////////////////////////////// // 🎢 QUEUE //////////////////////////////////////////// @@ -478,13 +483,16 @@ import "@styles/diffuse/fonts.css"; reactive( engine.queue, (data) => data.now, - (track) => setActiveTrack(track || undefined), + (track) => { + setActiveTrack(track || undefined); + setProgress(0); + }, ); // Changed artwork based on active queue item. // (debounced) - reactive(engine.queue, (data) => data.now, debounce(2000, changeArtwork)); + reactive(engine.queue, (data) => data.now, debounce(1000, changeArtwork)); async function changeArtwork() { const track = engine.queue.data.now; @@ -494,16 +502,20 @@ import "@styles/diffuse/fonts.css"; return; } + const input = await configurator.input; const cacheId = await trackArtworkCacheId(track); - const art = await processor.artwork.sendAction( + const urls = { + get: await inputUrl(input, track.uri, "GET").then((a) => a?.url), + head: await inputUrl(input, track.uri, "HEAD").then((a) => a?.url), + }; + + const proc = await processor.artwork; + const art = await proc.sendAction( "artwork", { cacheId, tags: track.tags, - urls: { - get: await inputUrl(configurator.input, track.uri, "GET").then((a) => a?.url), - head: await inputUrl(configurator.input, track.uri, "HEAD").then((a) => a?.url), - }, + urls, }, { timeoutDuration: 60000 * 5, @@ -518,13 +530,16 @@ import "@styles/diffuse/fonts.css"; // ORCHESTRATED - context.settled().then(() => { - if (context.isMainInstance()) - orchestrator.primary.sendAction("monitor_active_queue_item", undefined, { - timeoutDuration: 60000, - }); + context.settled().then(async () => { + if (context.isMainInstance()) monitorQueueStuff(); }); + async function monitorQueueStuff() { + (await orchestrator.primary).sendAction("monitorActiveQueueItem", undefined, { + timeoutDuration: 60000, + }); + } + //////////////////////////////////////////// // TRACKS //////////////////////////////////////////// @@ -532,21 +547,35 @@ import "@styles/diffuse/fonts.css"; // ORCHESTRATED context.settled().then(() => { - console.log("READY"); - if (isMainGroup() && context.isMainInstance()) { - orchestrator.primary - .sendAction("insert_tracks_into_queue", undefined, { - timeoutDuration: 60000 * 5, - }) - .then(() => { - orchestrator.primary.sendAction("process_inputs", undefined, { - timeoutDuration: 60000 * 60, - }); - }); + return monitorTracksStuff().then(processInputs); } }); + async function monitorTracksStuff() { + (await orchestrator.primary).sendAction("monitorTracks", undefined, { + timeoutDuration: 60000 * 5, + }); + } + + async function processInputs() { + (await orchestrator.primary).sendAction("processInputs", undefined, { + timeoutDuration: 60000 * 60, + }); + } + + //////////////////////////////////////////// + // 🚛 HYDRATE + //////////////////////////////////////////// + + context.unloadHandler = async () => { + if (context.isMainInstance()) { + monitorAudioStuff(); + monitorQueueStuff(); + if (isMainGroup()) monitorTracksStuff(); + } + }; + //////////////////////////////////////////// // UI //////////////////////////////////////////// diff --git a/src/pages/engine/audio/_applet.astro b/src/pages/engine/audio/_applet.astro index 09b8d08..d7699a3 100644 --- a/src/pages/engine/audio/_applet.astro +++ b/src/pages/engine/audio/_applet.astro @@ -61,6 +61,31 @@ } }); + // Unload + context.unloadHandler = async () => { + await context.settled(); + hydrateItems(); + }; + + function hydrateItems() { + const playingItem = context.data.isPlaying + ? Object.values(context.data.items).find((item) => item.isPlaying) + : undefined; + + render({ + audio: Object.values(context.data.items).map((item: AudioState) => { + return { + id: item.id, + isPreload: item.isPreload, + mimeType: item.mimeType, + progress: item.progress, + url: item.url, + }; + }), + play: playingItem ? { audioId: playingItem.id } : undefined, + }); + } + //////////////////////////////////////////// // ACTIONS //////////////////////////////////////////// @@ -187,7 +212,9 @@ loadingState: "loading", isPlaying: true, isPreload: item.isPreload ?? false, + mimeType: item.mimeType, progress: item.progress ?? 0, + url: item.url, }, }; }, {}); diff --git a/src/pages/engine/audio/types.d.ts b/src/pages/engine/audio/types.d.ts index 2fb65de..afbbd42 100644 --- a/src/pages/engine/audio/types.d.ts +++ b/src/pages/engine/audio/types.d.ts @@ -25,5 +25,7 @@ export interface AudioState { }; isPlaying: boolean; isPreload: boolean; + mimeType?: string; progress: number; + url: string; } diff --git a/src/pages/engine/queue/_applet.astro b/src/pages/engine/queue/_applet.astro index 739ec64..cac5930 100644 --- a/src/pages/engine/queue/_applet.astro +++ b/src/pages/engine/queue/_applet.astro @@ -22,6 +22,12 @@ const groupId = context.groupId || "main"; // Initial state + context.data = { + future: [], + now: null, + past: [], + }; + context.data = await worker.data(groupId); // Keep applet data with worker data in sync diff --git a/src/pages/orchestrator/primary/_applet.astro b/src/pages/orchestrator/primary/_applet.astro index 7174f7b..fe88f9c 100644 --- a/src/pages/orchestrator/primary/_applet.astro +++ b/src/pages/orchestrator/primary/_applet.astro @@ -19,7 +19,7 @@ // Applet connections const configurator = { - input: applet("/configurator/input", { context: self }), + input: applet("/configurator/input"), input_2: undefined as Applet | undefined, output: applet("/configurator/output"), }; @@ -36,8 +36,8 @@ //////////////////////////////////////////// // [ACTIONS] AUDIO ⭤ QUEUE //////////////////////////////////////////// - context.setActionHandler("monitor_active_queue_item", monitorActiveQueueItem); - context.setActionHandler("monitor_audio_end", monitorAudioEnd); + context.setActionHandler("monitorActiveQueueItem", monitorActiveQueueItem); + context.setActionHandler("monitorAudioEnd", monitorAudioEnd); async function monitorActiveQueueItem() { await context.settled(); @@ -106,7 +106,7 @@ //////////////////////////////////////////// // [ACTIONS] PROCESS //////////////////////////////////////////// - context.setActionHandler("process_inputs", processInputs); + context.setActionHandler("processInputs", processInputs); async function processInputs() { if (context.data.isProcessing) return; @@ -190,13 +190,11 @@ //////////////////////////////////////////// // [ACTIONS] QUEUE ⭤ TRACKS //////////////////////////////////////////// - context.setActionHandler("insert_tracks_into_queue", insertTracksIntoQueue); + context.setActionHandler("monitorTracks", monitorTracks); - async function insertTracksIntoQueue() { + async function monitorTracks() { await context.settled(); - console.log("SETTLED"); - // Add tracks to the queue once the tracks have been loaded; // and every time the collection changes. @@ -206,8 +204,6 @@ await wait(output, (d) => d?.tracks.state === "loaded"); - console.log("TRACKS LOADED"); - reactive( output, (data) => data.tracks.cacheId, @@ -218,12 +214,12 @@ { timeoutDuration: 60000 * 5, worker: true }, ); - console.log("CONSULTED"); - // Available tracks - const tracks = Object.values(groups).reduce((acc: Track[], value) => { - if (value.available === false) return acc; - return [...acc, ...value.tracks]; + let tracks: Track[] = []; + + Object.values(groups).forEach((value) => { + if (value.available === false) return; + tracks = tracks.concat(value.tracks); }, []); // Set pool diff --git a/src/scripts/applet/common.ts b/src/scripts/applet/common.ts index 61e3966..83a400f 100644 --- a/src/scripts/applet/common.ts +++ b/src/scripts/applet/common.ts @@ -115,6 +115,7 @@ export type DiffuseApplet = { set data(data: T); codec: Codec; + unloadHandler?: () => void; isMainInstance(): boolean | null; setActionHandler(actionId: string, actionHandler: H): void; @@ -146,17 +147,6 @@ export function register( encode: (data: DataType) => data as any, }; - // Channel - const channelContext = - mode === "broadcast" - ? broadcastChannel({ - channelId, - codec, - instanceId, - scope, - }) - : undefined; - // Context const context: DiffuseApplet = { groupId, @@ -202,17 +192,28 @@ export function register( }; } + // Channel + const channelContext = + mode === "broadcast" + ? broadcastChannel({ + channelId, + context, + instanceId, + scope, + }) + : undefined; + return context; } function broadcastChannel({ channelId, - codec, + context, instanceId, scope, }: { channelId: string; - codec: Codec; + context: DiffuseApplet; instanceId: string; scope: AppletScope; }) { @@ -234,12 +235,13 @@ function broadcastChannel({ channel.postMessage({ type: "PONG", instanceId: event.data.instanceId, + originInstanceId: instanceId, }); if (isMain()) { channel.postMessage({ type: "data", - data: codec.encode(scope.data), + data: context.codec.encode(scope.data), }); } break; @@ -252,6 +254,18 @@ function broadcastChannel({ break; } + case "UNLOADED": { + if (!context.isMainInstance()) { + // We need to wait until the other side is actually unloaded 🤷‍♀️ + setTimeout(async () => { + const promised = await makeMainPromise(); + setIsMain(promised.isMain); + context.unloadHandler?.(); + }, 250); + } + break; + } + case "action": { if (isMain()) { const result = await scope.actionHandlers[event.data.actionId]?.(...event.data.arguments); @@ -265,22 +279,25 @@ function broadcastChannel({ } case "data": { - scope.data = codec.decode(event.data.data); + scope.data = context.codec.decode(event.data.data); break; } } }); // Promise that fullfills whenever it figures out its the main instance or not. - function makeMainPromise() { + function makeMainPromise(timeoutDuration: number = 500) { return new Promise<{ isMain: boolean }>((resolve) => { const timeoutId = setTimeout(() => { channel.removeEventListener("message", handler); resolve({ isMain: true }); - }, 5000); + }, timeoutDuration); const handler = (event: MessageEvent) => { - if (event.data?.type === "PONG" || event.data?.type === "PING") { + if ( + (event.data?.type === "PONG" || event.data?.type === "PING") && + event.data?.instanceId === instanceId + ) { clearTimeout(timeoutId); channel.removeEventListener("message", handler); resolve({ isMain: false }); @@ -305,7 +322,7 @@ function broadcastChannel({ if (isMain()) { channel.postMessage({ type: "data", - data: codec.encode(event.data), + data: context.codec.encode(event.data), }); } }); @@ -352,6 +369,15 @@ function broadcastChannel({ scope.setActionHandler(actionId, handler); }; + // Before unload + self.addEventListener("beforeunload", (event) => { + if (context.isMainInstance()) { + channel.postMessage({ + type: "UNLOADED", + }); + } + }); + // Fin return { channel, diff --git a/src/scripts/common.ts b/src/scripts/common.ts index b237ddf..0ba0948 100644 --- a/src/scripts/common.ts +++ b/src/scripts/common.ts @@ -96,10 +96,15 @@ export function groupTracksPerScheme( tracks: Track[], initial: Record = {}, ): Record { - return tracks.reduce((acc: Record, track: Track) => { - const scheme = track.uri.split(":", 1)[0]; - return { ...acc, [scheme]: [...(acc[scheme] || []), track] }; - }, initial); + const acc: Record = {}; + + tracks.forEach((track) => { + const scheme = track.uri.substring(0, track.uri.indexOf(":")); + acc[scheme] ??= []; + acc[scheme].push(track); + }); + + return acc; } export function inIframe() { diff --git a/src/scripts/engine/queue/worker.ts b/src/scripts/engine/queue/worker.ts index b0b831c..5a3f161 100644 --- a/src/scripts/engine/queue/worker.ts +++ b/src/scripts/engine/queue/worker.ts @@ -125,21 +125,18 @@ function fill(groupId: string) { const queue = state(groupId); if (queue.future.length >= QUEUE_SIZE) return; - let reducedPool = internal(groupId).pool.reduce( - ({ past, pool }: { past: Set; pool: Track[] }, track: Track) => { - if (past.has(track.id)) - return { - past: past.difference(new Set(track.id)), - pool, - }; - - return { - past, - pool: [...pool, track], - }; - }, - { past: new Set(queue.past.map((t) => t.id)), pool: [] }, - ).pool; + const pool: Track[] = []; + + let past = new Set(queue.past.map((t) => t.id)); + let reducedPool = pool; + + internal(groupId).pool.forEach((track: Track) => { + if (past.has(track.id)) { + past = past.difference(new Set(track.id)); + } else { + pool.push(track); + } + }); if (reducedPool.length === 0) { reducedPool = internal(groupId).pool; diff --git a/src/scripts/input/native-fs/common.ts b/src/scripts/input/native-fs/common.ts index 4818c93..0a4e1a7 100644 --- a/src/scripts/input/native-fs/common.ts +++ b/src/scripts/input/native-fs/common.ts @@ -20,13 +20,20 @@ export async function fetchHandlesList() { }); } export function groupTracksByHandle(tracks: Track[]) { - return tracks.reduce((acc: Record, track: Track) => { + const acc: Record = {}; + + tracks.forEach((track: Track) => { const id = trackHandleId(track); if (!id) return acc; - const obj = { tracks: acc[id] ? [...acc[id].tracks, track] : [track] }; - return { ...acc, [id]: obj }; - }, {}); + if (acc[id]) { + acc[id].tracks.push(track); + } else { + acc[id] = { tracks: [track] }; + } + }); + + return acc; } export function isSupported() { diff --git a/src/scripts/input/native-fs/worker.ts b/src/scripts/input/native-fs/worker.ts index cd8d845..fa47d8f 100644 --- a/src/scripts/input/native-fs/worker.ts +++ b/src/scripts/input/native-fs/worker.ts @@ -80,16 +80,21 @@ export async function list(cachedTracks: Track[] = []) { ); // Group tracks by handle id & index by track uri - const cache = cachedTracks.reduce((acc: Record>, track: Track) => { + const cache: Record> = {}; + + cachedTracks.forEach((track: Track) => { const handleId = trackHandleId(track); - if (!handleId) return acc; + if (!handleId) return; - return { ...acc, [handleId]: { ...(acc[handleId] || {}), [track.uri]: track } }; - }, {}); + cache[handleId] ??= {}; + cache[handleId][track.uri] = track; + }); // Replace indexes in groups of which we have the handle. // Keeping around tracks with handles we don't have access to, // and removing tracks that are no longer available (for handles we do have access to). + + // TODO: Refactor to not use `reduce`, for performance. const groups = processed.flat(1).reduce( (acc, track) => { const handleId = trackHandleId(track); diff --git a/src/scripts/input/opensubsonic/common.ts b/src/scripts/input/opensubsonic/common.ts index 7dcbd0a..447678c 100644 --- a/src/scripts/input/opensubsonic/common.ts +++ b/src/scripts/input/opensubsonic/common.ts @@ -61,15 +61,22 @@ export function createClient(server: Server) { } export function groupTracksByServer(tracks: Track[]) { - return tracks.reduce((acc: Record, track: Track) => { + const acc: Record = {}; + + tracks.forEach((track: Track) => { const parsed = parseURI(track.uri); - if (!parsed) return acc; + if (!parsed) return; const id = serverId(parsed.server); - const obj = { server: parsed.server, tracks: acc[id] ? [...acc[id].tracks, track] : [track] }; - return { ...acc, [id]: obj }; - }, {}); + if (acc[id]) { + acc[id].tracks.push(track); + } else { + acc[id] = { server: parsed.server, tracks: [track] }; + } + }); + + return acc; } export async function loadServers(): Promise> { @@ -121,15 +128,19 @@ export async function saveServers(items: Record) { } export function serversFromTracks(tracks: Track[]) { - return tracks.reduce((acc: Record, track: Track) => { + const acc: Record = {}; + + tracks.forEach((track: Track) => { const parsed = parseURI(track.uri); - if (!parsed) return acc; + if (!parsed) return; const id = serverId(parsed.server); - if (acc[id]) return acc; + if (acc[id]) return; + + acc[id] = parsed.server; + }); - return { ...acc, [id]: parsed.server }; - }, {}); + return acc; } export function serverId(server: Server) { diff --git a/src/scripts/input/opensubsonic/worker.ts b/src/scripts/input/opensubsonic/worker.ts index 2e4b905..6121c1f 100644 --- a/src/scripts/input/opensubsonic/worker.ts +++ b/src/scripts/input/opensubsonic/worker.ts @@ -70,15 +70,17 @@ async function groupConsult(tracks: Track[]): Promise { } async function list(cachedTracks: Track[] = []) { - const cache = cachedTracks.reduce((acc: Record>, t: Track) => { + const cache: Record> = {}; + + cachedTracks.forEach((t: Track) => { const parsed = parseURI(t.uri); - if (!parsed || !parsed.path) return acc; + if (!parsed || !parsed.path) return; const sid = serverId(parsed?.server); - const trk = { [URI.unescapeComponent(parsed.path)]: t }; - return { ...acc, [sid]: acc[sid] ? { ...acc[sid], ...trk } : trk }; - }, {}); + cache[sid] ??= {}; + cache[sid][URI.unescapeComponent(parsed.path)] = t; + }); async function search(client: SubsonicAPI, offset = 0): Promise { const result = await client.search3({ @@ -161,13 +163,7 @@ async function resolve({ uri }: { method: string; uri: string }) { id: songId, format: "raw", }) - .then((a) => a.blob()) - .then((blob) => URL.createObjectURL(blob)); - - // NOTE: - // First idea was to get the URL for the download and use that instead. - // Problem is, more often than not, servers don't allow for CORS Range requests, - // so it's basically useless. + .then((a) => a.url); return { expiresAt: Infinity, url }; } diff --git a/src/scripts/input/s3/common.ts b/src/scripts/input/s3/common.ts index 1eb84f0..9528d27 100644 --- a/src/scripts/input/s3/common.ts +++ b/src/scripts/input/s3/common.ts @@ -11,15 +11,19 @@ import type { Bucket } from "./types"; // 🛠️ //////////////////////////////////////////// export function bucketsFromTracks(tracks: Track[]) { - return tracks.reduce((acc: Record, track: Track) => { + const acc: Record = {}; + + tracks.forEach((track: Track) => { const parsed = parseURI(track.uri); - if (!parsed) return acc; + if (!parsed) return; const id = bucketId(parsed.bucket); - if (acc[id]) return acc; + if (acc[id]) return; + + acc[id] = parsed.bucket; + }); - return { ...acc, [id]: parsed.bucket }; - }, {}); + return acc; } export function bucketId(bucket: Bucket) { @@ -64,15 +68,22 @@ export function encodeAwsUriComponent(a: string) { } export function groupTracksByBucket(tracks: Track[]) { - return tracks.reduce((acc: Record, track: Track) => { + const acc: Record = {}; + + tracks.forEach((track: Track) => { const parsed = parseURI(track.uri); if (!parsed) return acc; const id = bucketId(parsed.bucket); - const obj = { bucket: parsed.bucket, tracks: acc[id] ? [...acc[id].tracks, track] : [track] }; - return { ...acc, [id]: obj }; - }, {}); + if (acc[id]) { + acc[id].tracks.push(track); + } else { + acc[id] = { bucket: parsed.bucket, tracks: [track] }; + } + }); + + return acc; } export async function loadBuckets(): Promise> { diff --git a/src/scripts/input/s3/worker.ts b/src/scripts/input/s3/worker.ts index f58e281..a8d0cc4 100644 --- a/src/scripts/input/s3/worker.ts +++ b/src/scripts/input/s3/worker.ts @@ -67,15 +67,20 @@ async function groupConsult(tracks: Track[]): Promise { } async function list(cachedTracks: Track[] = []) { - const cache = cachedTracks.reduce((acc: Record>, t: Track) => { + const cache: Record> = {}; + + cachedTracks.forEach((t: Track) => { const parsed = parseURI(t.uri); - if (!parsed) return acc; + if (!parsed) return; const bid = bucketId(parsed?.bucket); - const trk = { [parsed.path]: t }; - return { ...acc, [bid]: acc[bid] ? { ...acc[bid], ...trk } : trk }; - }, {}); + if (cache[bid]) { + cache[bid][parsed.path] = t; + } else { + cache[bid] = { [parsed.path]: t }; + } + }); const buckets = await loadBuckets(); const promises = Object.values(buckets).map(async (bucket) => { diff --git a/src/scripts/processor/artwork/worker.ts b/src/scripts/processor/artwork/worker.ts index 73637a2..73059bc 100644 --- a/src/scripts/processor/artwork/worker.ts +++ b/src/scripts/processor/artwork/worker.ts @@ -2,6 +2,7 @@ import type { IPicture } from "music-metadata"; import * as IDB from "idb-keyval"; import type { Artwork, ArtworkRequest } from "./types"; +import type { Extraction } from "../metadata/types"; import { provide } from "@scripts/common"; import { IDB_ARTWORK_PREFIX } from "./constants"; import { musicMetadataTags } from "../metadata/common"; @@ -170,8 +171,13 @@ async function processRequest(req: ArtworkRequest): Promise { let art: Artwork[] = []; // Get metadata + possible artwork from file metadata - const meta = await musicMetadataTags({ ...req, includeArtwork: true }); - if (!req.tags) req.tags = meta.tags; + const meta = await musicMetadataTags({ ...req, includeArtwork: true }).catch((err) => { + console.error("music-metadata error", err); + const extraction: Extraction = {}; + return extraction; + }); + + if (!req.tags && meta.tags) req.tags = meta.tags; // Add artwork from metadata const fromMeta = diff --git a/src/scripts/processor/metadata/common.ts b/src/scripts/processor/metadata/common.ts index 3d987e1..9c7f7bb 100644 --- a/src/scripts/processor/metadata/common.ts +++ b/src/scripts/processor/metadata/common.ts @@ -1,5 +1,4 @@ import { parseBlob, parseFromTokenizer, parseWebStream } from "music-metadata"; -import { contentType } from "@std/media-types"; import * as URI from "uri-js"; import * as HTTP_TOKENIZER from "@tokenizer/http"; import * as RANGE_TOKENIZER from "@tokenizer/range"; @@ -32,6 +31,13 @@ export async function musicMetadataTags({ } else if (urls) { const httpClient = new HTTP_TOKENIZER.HttpClient(urls.head, { resolveUrl: false }); httpClient.resolvedUrl = urls.get; + const getHeadInfo = httpClient.getHeadInfo; + + // FUCKAROUND: Not sure of the downsides of this + httpClient.getHeadInfo = async () => { + const info = await getHeadInfo.call(httpClient); + return { ...info, acceptPartialRequests: true }; + }; const tokenizer = await RANGE_TOKENIZER.tokenizer(httpClient); diff --git a/src/scripts/theme/webamp/index.ts b/src/scripts/theme/webamp/index.ts index bf4ded0..010387a 100644 --- a/src/scripts/theme/webamp/index.ts +++ b/src/scripts/theme/webamp/index.ts @@ -95,9 +95,11 @@ async function loadTracks(): Promise { ); // Available tracks - const tracks = Object.values(groups).reduce((acc: Track[], value) => { - if (value.available === false) return acc; - return [...acc, ...value.tracks]; + let tracks: Track[] = []; + + Object.values(groups).forEach((value) => { + if (value.available === false) return; + tracks = tracks.concat(value.tracks); }, []); return tracks.map((track) => { -- knotmirror 0.0.1