From 54406ce71dee28dd3114c2cfabfaeb63adb76205 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Sun, 29 Mar 2026 22:05:48 +0200 Subject: [PATCH] feat: extract preload behaviour into a prelude facet --- src/_data/facets.json | 7 +++ src/common/facets/constants.js | 1 + .../orchestrator/queue-audio/element.js | 31 --------- .../playback/preload/prelude/index.html | 1 + .../playback/preload/prelude/index.inline.js | 63 +++++++++++++++++++ 5 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 src/facets/playback/preload/prelude/index.html create mode 100644 src/facets/playback/preload/prelude/index.inline.js diff --git a/src/_data/facets.json b/src/_data/facets.json index cdef4cf4..09e1b20d 100644 --- a/src/_data/facets.json +++ b/src/_data/facets.json @@ -106,6 +106,13 @@ "category": "Data", "desc": "The default setup for user-data storage output. Adds support for: AT Protocol and S3-compatible storage. For both of these a custom local-first syncing algorithm is used." }, + { + "url": "facets/playback/preload/prelude/index.html", + "title": "Preload Tracks", + "kind": "prelude", + "category": "Playback", + "desc": "Automatically preload the next track in the queue." + }, { "url": "facets/data/process-tracks/prelude/index.html", "title": "Process Tracks", diff --git a/src/common/facets/constants.js b/src/common/facets/constants.js index 57144ca0..f0c0cdbc 100644 --- a/src/common/facets/constants.js +++ b/src/common/facets/constants.js @@ -12,4 +12,5 @@ export const STARTING_SET_URIS = [ "facets/data/output-bundle/index.html", "facets/data/process-tracks/prelude/index.html", "facets/playback/auto-queue/prelude/index.html", + "facets/playback/preload/prelude/index.html", ]; diff --git a/src/components/orchestrator/queue-audio/element.js b/src/components/orchestrator/queue-audio/element.js index 55afb5b6..a2985ded 100644 --- a/src/components/orchestrator/queue-audio/element.js +++ b/src/components/orchestrator/queue-audio/element.js @@ -73,7 +73,6 @@ class QueueAudioOrchestrator extends BroadcastableDiffuseElement { if (!queue) return; const activeItem = queue.now(); - const nextItem = queue.future()[0] ?? null; const tracksCol = this.output?.tracks.collection(); const tracks = tracksCol?.state === "loaded" ? tracksCol.data : undefined; @@ -81,10 +80,6 @@ class QueueAudioOrchestrator extends BroadcastableDiffuseElement { ? tracks?.find((t) => t.id === activeItem.id) : undefined; - const nextTrack = nextItem - ? tracks?.find((t) => t.id === nextItem.id) - : undefined; - if ((await this.isLeader()) === false) return; const isPlaying = untracked(audio.isPlaying); @@ -114,32 +109,6 @@ class QueueAudioOrchestrator extends BroadcastableDiffuseElement { audio: activeAudio, play: activeItem && isPlaying ? { audioId: activeItem.id } : undefined, }); - - // Preload next track after a delay - clearTimeout(this._preloadTimeout); - if (!nextTrack) return; - - this._preloadTimeout = setTimeout(async () => { - const resolvedNextUri = await input.resolve({ - method: "GET", - uri: nextTrack.uri, - }); - - const nextUrl = resolvedNextUri && !("stream" in resolvedNextUri) - ? resolvedNextUri.url - : undefined; - - if (!nextUrl) return; - - audio.supply({ - audio: [...activeAudio, { - id: nextItem.id, - isPreload: true, - url: nextUrl, - track: nextTrack, - }], - }); - }, 30_000); } async monitorAudioEnd() { diff --git a/src/facets/playback/preload/prelude/index.html b/src/facets/playback/preload/prelude/index.html new file mode 100644 index 00000000..e1ae6036 --- /dev/null +++ b/src/facets/playback/preload/prelude/index.html @@ -0,0 +1 @@ + diff --git a/src/facets/playback/preload/prelude/index.inline.js b/src/facets/playback/preload/prelude/index.inline.js new file mode 100644 index 00000000..76f78924 --- /dev/null +++ b/src/facets/playback/preload/prelude/index.inline.js @@ -0,0 +1,63 @@ +import foundation from "~/common/foundation.js"; +import { effect } from "~/common/signal.js"; + +effect(() => { + // Trigger setup when audio is used + if (foundation.signals.engine.audio()) { + setup(); + } +}); + +async function setup() { + const [audio, input, output, queue] = await Promise.all([ + foundation.engine.audio(), + foundation.configurator.input(), + foundation.orchestrator.output(), + foundation.engine.queue(), + ]); + + await Promise.all([ + customElements.whenDefined(audio.localName), + customElements.whenDefined(input.localName), + customElements.whenDefined(queue.localName), + ]); + + /** @type {number | undefined} */ + let preloadTimeout; + + effect(() => { + const nextItem = queue.future()[0] ?? null; + const tracksCol = output?.tracks.collection(); + const tracks = tracksCol?.state === "loaded" ? tracksCol.data : undefined; + const nextTrack = nextItem + ? tracks?.find((t) => t.id === nextItem.id) + : undefined; + + clearTimeout(preloadTimeout); + if (!nextTrack) return; + + preloadTimeout = setTimeout(async () => { + const resolvedNextUri = await input.resolve({ + method: "GET", + uri: nextTrack.uri, + }); + + const nextUrl = resolvedNextUri && !("stream" in resolvedNextUri) + ? resolvedNextUri.url + : undefined; + + if (!nextUrl) return; + + const activeItems = audio.items().filter((i) => !i.isPreload); + + audio.supply({ + audio: [...activeItems, { + id: nextItem.id, + isPreload: true, + url: nextUrl, + track: nextTrack, + }], + }); + }, 30_000); + }); +} -- 2.51.2