A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: queue-audio orchestrator

+118 -228
-97
_backup/pages/orchestrator/queue-audio/_applet.astro
··· 1 - <script> 2 - import { applet, inputUrl, reactive, register } from "@scripts/applet/common"; 3 - 4 - //////////////////////////////////////////// 5 - // SETUP 6 - //////////////////////////////////////////// 7 - import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 8 - import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 9 - 10 - const context = register(); 11 - 12 - // Applet connections 13 - const configurator = { 14 - input: applet("/configurator/input"), 15 - }; 16 - 17 - const engine = { 18 - audio: applet<AudioEngine.State>("/engine/audio", { groupId: context.groupId }), 19 - queue: applet<QueueEngine.State>("/engine/queue", { groupId: context.groupId }), 20 - }; 21 - 22 - //////////////////////////////////////////// 23 - // QUEUE ⭤ AUDIO 24 - //////////////////////////////////////////// 25 - async function monitorActiveQueueItem() { 26 - const audio = await engine.audio; 27 - const queue = await engine.queue; 28 - 29 - // When the active queue item has changed, 30 - // coordinate the audio engine accordingly. 31 - reactive( 32 - queue, 33 - (data) => data.now?.id, 34 - async () => { 35 - if (!context.isMainInstance()) return; 36 - 37 - const activeTrack = queue.data.now; 38 - const isPlaying = audio.data.isPlaying; 39 - 40 - // Resolve URIs 41 - const url = activeTrack 42 - ? await inputUrl(await configurator.input, activeTrack.uri).then((a) => a?.url) 43 - : undefined; 44 - 45 - // Check if we still need to render 46 - if (queue.data.now?.id !== activeTrack?.id) return; 47 - 48 - // Play new active queue item 49 - // TODO: Take URL expiration timestamp into account 50 - // TODO: Preload next queue item 51 - audio.sendAction( 52 - "render", 53 - { 54 - audio: activeTrack 55 - ? [ 56 - { 57 - id: activeTrack.id, 58 - isPreload: false, 59 - url, 60 - }, 61 - ] 62 - : // TODO: Keep preloads 63 - [], 64 - play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, 65 - }, 66 - { 67 - timeoutDuration: 60000, 68 - }, 69 - ); 70 - }, 71 - ); 72 - } 73 - 74 - async function monitorAudioEnd() { 75 - const audio = await engine.audio; 76 - const queue = await engine.queue; 77 - 78 - // When the active audio has ended, 79 - // shift the queue. 80 - reactive( 81 - audio, 82 - (data) => data.items[queue.data.now?.id ?? Infinity]?.hasEnded ?? false, 83 - (hasEnded) => { 84 - if (!context.isMainInstance()) return; 85 - if (hasEnded) queue.sendAction("shift", { groupId: context.groupId }, { worker: true }); 86 - }, 87 - ); 88 - } 89 - 90 - //////////////////////////////////////////// 91 - // 🚀 92 - //////////////////////////////////////////// 93 - context.settled().then(() => { 94 - monitorActiveQueueItem(); 95 - monitorAudioEnd(); 96 - }); 97 - </script>
-6
_backup/pages/orchestrator/queue-audio/_manifest.json
··· 1 - { 2 - "name": "diffuse/orchestrator/queue-audio", 3 - "title": "Diffuse Orchestrator | Queue Audio", 4 - "entrypoint": "index.html", 5 - "actions": {} 6 - }
-9
_backup/pages/orchestrator/queue-audio/index.astro
··· 1 - --- 2 - import Layout from "@layouts/applet.astro"; 3 - import Applet from "./_applet.astro"; 4 - import { title } from "./_manifest.json"; 5 - --- 6 - 7 - <Layout title={title}> 8 - <Applet /> 9 - </Layout>
-74
_backup/pages/orchestrator/queue-tracks/_applet.astro
··· 1 - <script> 2 - import type { GroupConsult, ManagedOutput, Track } from "@applets/core/types.d.ts"; 3 - import { applet, reactive, register, wait } from "@scripts/applet/common"; 4 - 5 - //////////////////////////////////////////// 6 - // SETUP 7 - //////////////////////////////////////////// 8 - import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 9 - 10 - const context = register(); 11 - 12 - // Applet connections 13 - const configurator = { 14 - input: applet("/configurator/input"), 15 - output: applet<ManagedOutput>("/configurator/output"), 16 - }; 17 - 18 - const engine = { 19 - queue: applet<QueueEngine.State>("/engine/queue", { groupId: context.groupId }), 20 - }; 21 - 22 - //////////////////////////////////////////// 23 - // QUEUE ⭤ TRACKS 24 - //////////////////////////////////////////// 25 - async function monitorTracks() { 26 - await context.settled(); 27 - 28 - // Add tracks to the queue once the tracks have been loaded; 29 - // and every time the collection changes. 30 - 31 - const input = await configurator.input; 32 - const output = await configurator.output; 33 - const queue = await engine.queue; 34 - 35 - await wait(output, (d) => d?.tracks.state === "loaded"); 36 - 37 - reactive( 38 - output, 39 - (data) => data.tracks.cacheId, 40 - async () => { 41 - if (!context.isMainInstance()) return; 42 - 43 - const groups = await input.sendAction<GroupConsult>( 44 - "groupConsult", 45 - output.data.tracks.collection, 46 - { timeoutDuration: 60000 * 5, worker: true }, 47 - ); 48 - 49 - // Available tracks 50 - let tracks: Track[] = []; 51 - 52 - Object.values(groups).forEach((value) => { 53 - if (value.available === false) return; 54 - tracks = tracks.concat(value.tracks); 55 - }, []); 56 - 57 - // Set pool 58 - await queue.sendAction( 59 - "pool", 60 - { groupId: context.groupId, tracks }, 61 - { 62 - timeoutDuration: 60000, 63 - worker: true, 64 - }, 65 - ); 66 - }, 67 - ); 68 - } 69 - 70 - //////////////////////////////////////////// 71 - // 🚀 72 - //////////////////////////////////////////// 73 - monitorTracks(); 74 - </script>
-6
_backup/pages/orchestrator/queue-tracks/_manifest.json
··· 1 - { 2 - "name": "diffuse/orchestrator/queue-tracks", 3 - "title": "Diffuse Orchestrator | Queue Tracks", 4 - "entrypoint": "index.html", 5 - "actions": {} 6 - }
-9
_backup/pages/orchestrator/queue-tracks/index.astro
··· 1 - --- 2 - import Layout from "@layouts/applet.astro"; 3 - import Applet from "./_applet.astro"; 4 - import { title } from "./_manifest.json"; 5 - --- 6 - 7 - <Layout title={title}> 8 - <Applet /> 9 - </Layout>
+1 -1
src/common/element.js
··· 123 123 this.broadcast = this.broadcast.bind(this); 124 124 125 125 /** @type {Signal<Promise<BroadcastingStatus>>} */ 126 - this.#broadcastingStatus = signal(this.#status.promise, { unbiased: true }); 126 + this.#broadcastingStatus = signal(this.#status.promise, { eager: true }); 127 127 this.broadcastingStatus = this.#broadcastingStatus.get; 128 128 } 129 129
+2 -5
src/common/signal.js
··· 10 10 /** 11 11 * @template T 12 12 * @param {T} initialValue 13 - * @param {{ unbiased?: boolean }} [options] 13 + * @param {{ eager?: boolean }} [options] 14 14 * @returns {Signal<T>} 15 15 */ 16 16 export function signal(initialValue, options) { 17 17 const s = alienSignal(initialValue); 18 - const isPrimitive = initialValue !== null && 19 - initialValue !== undefined && 20 - Object(initialValue) !== initialValue; 21 - if (isPrimitive || options?.unbiased === true) { 18 + if (options?.eager === true) { 22 19 return _signal({ 23 20 get: () => s(), 24 21 set: (v) => s(v),
+3
src/component/engine/audio/element.js
··· 49 49 #items = signal(/** @type {Audio[]} */ ([])); 50 50 #volume = signal(0.5); 51 51 52 + $hasEnded = signal(false); 52 53 $isPlaying = signal(false); 53 54 54 55 // STATE 55 56 57 + hasEnded = this.$hasEnded.get; 56 58 isPlaying = this.$isPlaying.get; 57 59 items = this.#items.get; 58 60 volume = this.#volume.get; ··· 332 334 audio.currentTime = 0; 333 335 334 336 engineItem(audio)?.state({ hasEnded: true }); 337 + engineItem(audio)?.engine?.$hasEnded.set(true); 335 338 } 336 339 337 340 /**
+97
src/component/orchestrator/queue-audio/element.js
··· 1 + import { DiffuseElement, query } from "@common/element.js"; 2 + import { untracked } from "@common/signal.js"; 3 + 4 + /** 5 + * @import {InputElement, OutputElement, Track} from "@component/core/types.d.ts" 6 + */ 7 + 8 + //////////////////////////////////////////// 9 + // ELEMENT 10 + //////////////////////////////////////////// 11 + 12 + /** 13 + * When the active queue item changes, 14 + * coordinate the audio engine accordingly. 15 + * 16 + * Vice versa, when the audio ends, 17 + * shift the queue if needed. 18 + */ 19 + class QueueAudioOrchestrator extends DiffuseElement { 20 + constructor() { 21 + super(); 22 + 23 + /** @type {InputElement} */ 24 + this.input = query(this, "input-selector"); 25 + 26 + /** @type {import("@component/engine/audio/element.js").CLASS} */ 27 + this.audio = query(this, "audio-engine-selector"); 28 + 29 + /** @type {import("@component/engine/queue/element.js").CLASS} */ 30 + this.queue = query(this, "queue-engine-selector"); 31 + } 32 + 33 + // LIFECYCLE 34 + 35 + /** 36 + * @override 37 + */ 38 + async connectedCallback() { 39 + super.connectedCallback(); 40 + 41 + // Wait until defined 42 + await customElements.whenDefined(this.audio.localName); 43 + await customElements.whenDefined(this.input.localName); 44 + await customElements.whenDefined(this.queue.localName); 45 + 46 + // Effects 47 + this.effect(() => this.monitorActiveQueueItem()); 48 + this.effect(() => this.monitorAudioEnd()); 49 + } 50 + 51 + // 🛠️ 52 + 53 + async monitorActiveQueueItem() { 54 + const activeTrack = this.queue.now(); 55 + const isPlaying = untracked(this.audio.isPlaying); 56 + 57 + // Resolve URIs 58 + const url = activeTrack 59 + ? await this.input.resolve({ method: "GET", uri: activeTrack.uri }).then( 60 + (a) => a?.url, 61 + ) 62 + : undefined; 63 + 64 + // Check if we still need to render 65 + if (this.queue.now?.()?.id !== activeTrack?.id) return; 66 + 67 + // Play new active queue item 68 + // TODO: Take URL expiration timestamp into account 69 + // TODO: Preload next queue item 70 + this.audio.supply({ 71 + audio: activeTrack && url 72 + ? [{ 73 + id: activeTrack.id, 74 + isPreload: false, 75 + url, 76 + }] 77 + // TODO: Keep preloads 78 + : [], 79 + play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, 80 + }); 81 + } 82 + 83 + async monitorAudioEnd() { 84 + if (this.audio.hasEnded()) await this.queue.shift(); 85 + } 86 + } 87 + 88 + export default QueueAudioOrchestrator; 89 + 90 + //////////////////////////////////////////// 91 + // REGISTER 92 + //////////////////////////////////////////// 93 + 94 + export const CLASS = QueueAudioOrchestrator; 95 + export const NAME = "do-queue-audio"; 96 + 97 + customElements.define(NAME, QueueAudioOrchestrator);
+2 -2
src/component/orchestrator/queue-tracks/element.js
··· 35 35 async connectedCallback() { 36 36 super.connectedCallback(); 37 37 38 - // Wait until defined 38 + // When defined 39 39 await customElements.whenDefined(this.output.localName); 40 40 41 - // ... 41 + // Watch tracks collection 42 42 this.effect(() => { 43 43 const tracks = this.output.tracks.collection(); 44 44 this.poolAvailable(tracks);
+6 -18
src/theme/blur/index.js
··· 1 + import "@component/engine/audio/element.js"; 1 2 import "@component/input/opensubsonic/element.js"; 3 + import "@component/orchestrator/process-tracks/element.js"; 4 + import "@component/orchestrator/queue-audio/element.js"; 5 + import "@component/orchestrator/queue-tracks/element.js"; 2 6 import "@component/processor/metadata/element.js"; 3 7 4 - import * as Audio from "@component/engine/audio/element.js"; 5 8 import * as Output from "@component/output/indexed-db/element.js"; 6 9 import * as Queue from "@component/engine/queue/element.js"; 7 10 8 - import "@component/orchestrator/process-tracks/element.js"; 9 - 10 11 import { component } from "@common/element.js"; 11 12 import { effect } from "@common/signal.js"; 12 13 ··· 14 15 * @import {Item} from "@component/engine/queue/types.d.ts" 15 16 */ 16 17 17 - const audio = component(Audio); 18 18 const output = component(Output); 19 19 const queue = component(Queue); 20 20 21 21 globalThis.output = output; 22 - 23 - // QUEUE 22 + globalThis.queue = queue; 24 23 25 24 effect(() => { 26 - const now = queue.now(); 27 - if (now === null) return; 28 - 29 - audio.supply({ 30 - audio: [ 31 - { 32 - id: now.id, 33 - isPreload: false, 34 - url: now.uri, 35 - }, 36 - ], 37 - }); 25 + console.log("Active queue item:", queue.now()); 38 26 });
+7 -1
src/theme/blur/index.vto
··· 24 24 output-selector="do-indexed-db" 25 25 ></do-process-tracks> 26 26 27 + <do-queue-audio 28 + input-selector="di-opensubsonic" 29 + audio-engine-selector="de-audio" 30 + queue-engine-selector="de-queue" 31 + ></do-queue-audio> 32 + 27 33 <do-queue-tracks 28 34 input-selector="di-opensubsonic" 29 35 output-selector="do-indexed-db" 30 - queue-selector="de-queue" 36 + queue-engine-selector="de-queue" 31 37 ></do-queue-tracks> 32 38 33 39 <!-- Scripts -->