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

Configure Feed

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

refactor: foundation + some orchestrators

+338 -389
+4 -4
deno.jsonc
··· 80 80 "./components/engine/audio/element.js": "./src/components/engine/audio/element.js", 81 81 "./components/engine/queue/element.js": "./src/components/engine/queue/element.js", 82 82 "./components/engine/queue/worker.js": "./src/components/engine/queue/worker.js", 83 + "./components/engine/repeat-shuffle/element.js": "./src/components/engine/repeat-shuffle/element.js", 83 84 "./components/input/common.js": "./src/components/input/common.js", 84 85 "./components/input/opensubsonic/common.js": "./src/components/input/opensubsonic/common.js", 85 86 "./components/input/opensubsonic/constants.js": "./src/components/input/opensubsonic/constants.js", ··· 89 90 "./components/input/s3/constants.js": "./src/components/input/s3/constants.js", 90 91 "./components/input/s3/element.js": "./src/components/input/s3/element.js", 91 92 "./components/input/s3/worker.js": "./src/components/input/s3/worker.js", 93 + "./components/orchestrator/auto-queue/element.js": "./src/components/orchestrator/auto-queue/element.js", 94 + "./components/orchestrator/auto-queue/worker.js": "./src/components/orchestrator/auto-queue/worker.js", 92 95 "./components/orchestrator/input/element.js": "./src/components/orchestrator/input/element.js", 93 96 "./components/orchestrator/output/element.js": "./src/components/orchestrator/output/element.js", 94 97 "./components/orchestrator/process-tracks/element.js": "./src/components/orchestrator/process-tracks/element.js", 95 98 "./components/orchestrator/process-tracks/worker.js": "./src/components/orchestrator/process-tracks/worker.js", 96 99 "./components/orchestrator/queue-audio/element.js": "./src/components/orchestrator/queue-audio/element.js", 97 - "./components/orchestrator/queue-tracks/element.js": "./src/components/orchestrator/queue-tracks/element.js", 98 - "./components/orchestrator/queue-tracks/worker.js": "./src/components/orchestrator/queue-tracks/worker.js", 99 100 "./components/orchestrator/search-tracks/element.js": "./src/components/orchestrator/search-tracks/element.js", 100 101 "./components/orchestrator/search-tracks/worker.js": "./src/components/orchestrator/search-tracks/worker.js", 101 - "./components/orchestrator/repeat-shuffle/element.js": "./src/components/orchestrator/repeat-shuffle/element.js", 102 102 "./components/orchestrator/sources/element.js": "./src/components/orchestrator/sources/element.js", 103 103 "./components/output/common.js": "./src/components/output/common.js", 104 104 "./components/output/polymorphic/indexed-db/constants.js": "./src/components/output/polymorphic/indexed-db/constants.js", ··· 126 126 "./components/input/opensubsonic/types.d.ts": "./src/components/input/opensubsonic/types.d.ts", 127 127 "./components/input/s3/types.d.ts": "./src/components/input/s3/types.d.ts", 128 128 "./components/input/types.d.ts": "./src/components/input/types.d.ts", 129 + "./components/orchestrator/auto-queue/types.d.ts": "./src/components/orchestrator/auto-queue/types.d.ts", 129 130 "./components/orchestrator/process-tracks/types.d.ts": "./src/components/orchestrator/process-tracks/types.d.ts", 130 - "./components/orchestrator/queue-tracks/types.d.ts": "./src/components/orchestrator/queue-tracks/types.d.ts", 131 131 "./components/orchestrator/search-tracks/types.d.ts": "./src/components/orchestrator/search-tracks/types.d.ts", 132 132 "./components/output/polymorphic/indexed-db/types.d.ts": "./src/components/output/polymorphic/indexed-db/types.d.ts", 133 133 "./components/output/types.d.ts": "./src/components/output/types.d.ts",
+44 -38
src/common/constituents/foundation.js
··· 1 1 import ArtworkProcessor from "@components/processor/artwork/element.js"; 2 2 import AudioEngine from "@components/engine/audio/element.js"; 3 + import AutoQueueOrchestrator from "@components/orchestrator/auto-queue/element.js"; 3 4 import Queue from "@components/engine/queue/element.js"; 4 5 import InputOrchestrator from "@components/orchestrator/input/element.js"; 5 6 import OutputOrchestrator from "@components/orchestrator/output/element.js"; 6 7 import MetadataProcessor from "@components/processor/metadata/element.js"; 7 8 import ProcessTracksOrchestrator from "@components/orchestrator/process-tracks/element.js"; 8 9 import QueueAudioOrchestrator from "@components/orchestrator/queue-audio/element.js"; 9 - import QueueTracksOrchestrator from "@components/orchestrator/queue-tracks/element.js"; 10 - import RepeatShuffleOrchestrator from "@components/orchestrator/repeat-shuffle/element.js"; 10 + import RepeatShuffleEngine from "@components/engine/repeat-shuffle/element.js"; 11 11 import SearchProcessor from "@components/processor/search/element.js"; 12 12 import SearchTracksOrchestrator from "@components/orchestrator/search-tracks/element.js"; 13 13 import SourcesOrchestrator from "@components/orchestrator/sources/element.js"; ··· 24 24 export const config = { 25 25 GROUP, 26 26 27 - /* Some predefined activity groups */ 28 - assemblage: { 27 + features: { 28 + fillQueueAutomatically, 29 29 playAudioFromQueue, 30 - queueManagement, 30 + processInputs, 31 31 searchThroughCollection, 32 32 }, 33 33 ··· 35 35 engine: { 36 36 audio, 37 37 queue, 38 + repeatShuffle, 38 39 }, 39 40 orchestrator: { 41 + autoQueue, 40 42 input, 41 43 output, 42 44 queueAudio, 43 - queueTracks, 44 45 processTracks, 45 - repeatShuffle, 46 46 searchTracks, 47 47 sources, 48 48 }, ··· 57 57 58 58 // 📦️ 59 59 60 - function playAudioFromQueue() { 61 - const base = queueManagement(); 60 + function fillQueueAutomatically() { 61 + return { 62 + engine: { 63 + queue: queue(), 64 + repeatShuffle: repeatShuffle(), 65 + }, 66 + orchestrator: { 67 + autoQueue: autoQueue(), 68 + input: input(), 69 + output: output(), 70 + }, 71 + }; 72 + } 62 73 74 + function playAudioFromQueue() { 63 75 return { 64 - ...base, 65 76 engine: { 66 - ...base.engine, 67 77 audio: audio(), 78 + queue: queue(), 68 79 }, 69 80 orchestrator: { 70 - ...base.orchestrator, 71 81 queueAudio: queueAudio(), 72 82 }, 73 83 }; 74 84 } 75 85 76 - function queueManagement() { 86 + function processInputs() { 77 87 return { 78 - engine: { 79 - queue: queue(), 80 - }, 81 88 orchestrator: { 82 89 input: input(), 83 90 output: output(), 84 91 processTracks: processTracks(), 85 - queueTracks: queueTracks(), 86 - repeatShuffle: repeatShuffle(), 87 92 }, 88 93 processor: { 89 94 metadata: metadata(), ··· 143 148 } 144 149 145 150 // Orchestrators 151 + function autoQueue() { 152 + const i = input(); 153 + const o = output(); 154 + const q = queue(); 155 + const r = repeatShuffle(); 156 + 157 + const aqo = new AutoQueueOrchestrator(); 158 + aqo.setAttribute("group", GROUP); 159 + aqo.setAttribute("input-selector", i.selector); 160 + aqo.setAttribute("output-selector", o.selector); 161 + aqo.setAttribute("queue-engine-selector", q.selector); 162 + aqo.setAttribute("repeat-shuffle-engine-selector", r.selector); 163 + 164 + return findExistingOrAdd(aqo); 165 + } 166 + 146 167 function input() { 147 168 const i = new InputOrchestrator(); 148 169 i.setAttribute("group", GROUP); ··· 178 199 const a = audio(); 179 200 const i = input(); 180 201 const q = queue(); 202 + const r = repeatShuffle(); 181 203 182 204 const oqa = new QueueAudioOrchestrator(); 183 205 oqa.setAttribute("group", GROUP); 184 206 oqa.setAttribute("audio-engine-selector", a.selector); 185 207 oqa.setAttribute("input-selector", i.selector); 186 208 oqa.setAttribute("queue-engine-selector", q.selector); 209 + oqa.setAttribute("repeat-shuffle-engine-selector", r.selector); 187 210 188 211 return findExistingOrAdd(oqa); 189 212 } 190 213 191 - function queueTracks() { 192 - const i = input(); 193 - const o = output(); 194 - const q = queue(); 195 - 196 - const oqt = new QueueTracksOrchestrator(); 197 - oqt.setAttribute("group", GROUP); 198 - oqt.setAttribute("input-selector", i.selector); 199 - oqt.setAttribute("output-selector", o.selector); 200 - oqt.setAttribute("queue-engine-selector", q.selector); 201 - 202 - return findExistingOrAdd(oqt); 203 - } 204 - 205 214 function repeatShuffle() { 206 - const q = queue(); 207 - 208 - const ors = new RepeatShuffleOrchestrator(); 209 - ors.setAttribute("group", GROUP); 210 - ors.setAttribute("queue-engine-selector", q.selector); 215 + const rse = new RepeatShuffleEngine(); 216 + rse.setAttribute("group", GROUP); 211 217 212 - return findExistingOrAdd(ors); 218 + return findExistingOrAdd(rse); 213 219 } 214 220 215 221 function searchTracks() {
+86
src/components/engine/repeat-shuffle/element.js
··· 1 + import { BroadcastableDiffuseElement } from "@common/element.js"; 2 + import { signal } from "@common/signal.js"; 3 + 4 + //////////////////////////////////////////// 5 + // ELEMENT 6 + //////////////////////////////////////////// 7 + 8 + class RepeatShuffleEngine extends BroadcastableDiffuseElement { 9 + static NAME = "diffuse/engine/repeat-shuffle"; 10 + 11 + // SIGNALS 12 + 13 + #repeat = signal(false); 14 + #shuffle = signal(false); 15 + 16 + repeat = this.#repeat.get; 17 + shuffle = this.#shuffle.get; 18 + 19 + // LIFECYCLE 20 + 21 + /** 22 + * @override 23 + */ 24 + connectedCallback() { 25 + // Broadcast if needed 26 + if (this.hasAttribute("group")) { 27 + const actions = this.broadcast(this.nameWithGroup, { 28 + setRepeat: { strategy: "replicate", fn: this.setRepeat }, 29 + setShuffle: { strategy: "replicate", fn: this.setShuffle }, 30 + }); 31 + 32 + if (actions) { 33 + this.setRepeat = actions.setRepeat; 34 + this.setShuffle = actions.setShuffle; 35 + } 36 + } 37 + 38 + // Super 39 + super.connectedCallback(); 40 + 41 + // Signals 42 + const storagePrefix = 43 + `${this.constructor.prototype.constructor.NAME}/${this.group}/`; 44 + 45 + this.#repeat.value = 46 + localStorage.getItem(`${storagePrefix}/repeat`) === "true" ? true : false; 47 + this.#shuffle.value = 48 + localStorage.getItem(`${storagePrefix}/shuffle`) === "true" 49 + ? true 50 + : false; 51 + 52 + // Effects 53 + this.effect(() => 54 + localStorage.setItem( 55 + `${storagePrefix}/repeat`, 56 + this.#repeat.value ? "true" : "false", 57 + ) 58 + ); 59 + 60 + this.effect(() => 61 + localStorage.setItem( 62 + `${storagePrefix}/shuffle`, 63 + this.#shuffle.value ? "true" : "false", 64 + ) 65 + ); 66 + } 67 + 68 + // ACTIONS 69 + 70 + /** @param {boolean} bool */ 71 + setRepeat = async (bool) => this.#repeat.value = bool; 72 + 73 + /** @param {boolean} bool */ 74 + setShuffle = async (bool) => this.#shuffle.value = bool; 75 + } 76 + 77 + export default RepeatShuffleEngine; 78 + 79 + //////////////////////////////////////////// 80 + // REGISTER 81 + //////////////////////////////////////////// 82 + 83 + export const CLASS = RepeatShuffleEngine; 84 + export const NAME = "de-repeat-shuffle"; 85 + 86 + customElements.define(NAME, CLASS);
+133
src/components/orchestrator/auto-queue/element.js
··· 1 + import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 + import { untracked } from "@common/signal.js"; 3 + 4 + /** 5 + * @import {ProxiedActions} from "@common/worker.d.ts" 6 + * @import {InputElement} from "@components/input/types.d.ts" 7 + * @import {OutputElement} from "@components/output/types.d.ts" 8 + * @import RepeatShuffleEngine from "@components/engine/repeat-shuffle/element.js" 9 + * 10 + * @import {Actions} from "./types.d.ts" 11 + */ 12 + 13 + //////////////////////////////////////////// 14 + // ELEMENT 15 + //////////////////////////////////////////// 16 + 17 + /** 18 + * Update the queue pool whenever tracks have been loaded, 19 + * or the tracks collection changes. 20 + * 21 + * At the same time, 22 + */ 23 + class AutoTracksOrchestrator extends BroadcastableDiffuseElement { 24 + static NAME = "diffuse/orchestrator/auto-queue"; 25 + static WORKER_URL = "components/orchestrator/auto-queue/worker.js"; 26 + 27 + /** @type {ProxiedActions<Actions>} */ 28 + #proxy; 29 + 30 + constructor() { 31 + super(); 32 + this.#proxy = this.workerProxy({ 33 + forceNew: { 34 + dependencies: { 35 + input: true, 36 + }, 37 + }, 38 + }); 39 + } 40 + 41 + // LIFECYCLE 42 + 43 + /** 44 + * @override 45 + */ 46 + async connectedCallback() { 47 + // Broadcast if needed 48 + if (this.hasAttribute("group")) { 49 + this.broadcast(this.nameWithGroup, {}); 50 + } 51 + 52 + // Super 53 + super.connectedCallback(); 54 + 55 + /** @type {InputElement} */ 56 + const input = query(this, "input-selector"); 57 + 58 + /** @type {OutputElement} */ 59 + const output = query(this, "output-selector"); 60 + 61 + /** @type {import("@components/engine/queue/element.js").CLASS} */ 62 + const queue = query(this, "queue-engine-selector"); 63 + 64 + /** @type {RepeatShuffleEngine} */ 65 + const repeatShuffle = query(this, "repeat-shuffle-engine-selector"); 66 + 67 + // Assign to self 68 + this.input = input; 69 + this.output = output; 70 + this.queue = queue; 71 + this.repeatShuffle = repeatShuffle; 72 + 73 + // When defined 74 + await customElements.whenDefined(input.localName); 75 + await customElements.whenDefined(output.localName); 76 + await customElements.whenDefined(queue.localName); 77 + await customElements.whenDefined(repeatShuffle.localName); 78 + 79 + // Watch tracks collection 80 + this.effect(() => { 81 + const tracks = output.tracks.collection(); 82 + 83 + this.isLeader().then((isLeader) => { 84 + if (!isLeader) return; 85 + untracked(() => this.#proxy.poolAvailable({ tracks })); 86 + }); 87 + }); 88 + 89 + // Automatically fill queue 90 + this.effect(() => { 91 + const trigger = queue.now(); 92 + const _other_trigger = queue.supplyFingerprint(); 93 + 94 + this.isLeader().then((isLeader) => { 95 + if (!isLeader) return; 96 + 97 + queue.fill({ amount: 10, shuffled: repeatShuffle.shuffle() }); 98 + 99 + // Insert now-playing track if there's none 100 + if (!trigger) queue.shift(); 101 + }); 102 + }); 103 + 104 + // TODO: Clear non-manual items from the queue 105 + // when 'shuffle' gets turned off or on. 106 + } 107 + 108 + // WORKERS 109 + 110 + /** 111 + * @override 112 + */ 113 + dependencies() { 114 + if (!this.input) throw new Error("Input element not defined yet"); 115 + if (!this.queue) throw new Error("Queue element not defined yet"); 116 + 117 + return { 118 + input: this.input, 119 + queue: this.queue, 120 + }; 121 + } 122 + } 123 + 124 + export default AutoTracksOrchestrator; 125 + 126 + //////////////////////////////////////////// 127 + // REGISTER 128 + //////////////////////////////////////////// 129 + 130 + export const CLASS = AutoTracksOrchestrator; 131 + export const NAME = "do-auto-queue"; 132 + 133 + customElements.define(NAME, CLASS);
+5
src/components/orchestrator/auto-queue/types.d.ts
··· 1 + import type { Track } from "@definitions/types.d.ts"; 2 + 3 + export type Actions = { 4 + poolAvailable(_: { tracks: Track[] }): Promise<void>; 5 + };
+7 -23
src/components/orchestrator/queue-audio/element.js
··· 1 1 import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 - import { signal, untracked } from "@common/signal.js"; 2 + import { untracked } from "@common/signal.js"; 3 3 4 4 /** 5 5 * @import {InputElement} from "@components/input/types.d.ts" 6 + * @import RepeatShuffleEngine from "@components/engine/repeat-shuffle/element.js" 6 7 */ 7 8 8 9 //////////////////////////////////////////// ··· 18 19 */ 19 20 class QueueAudioOrchestrator extends BroadcastableDiffuseElement { 20 21 static NAME = "diffuse/orchestrator/queue-audio"; 21 - static observedAttributes = ["repeat"]; 22 - 23 - // SIGNALS 24 - 25 - #repeat = signal(false); 26 22 27 23 // LIFE CYCLE 28 24 ··· 30 26 * @override 31 27 */ 32 28 async connectedCallback() { 33 - this.#repeat.value = this.hasAttribute("repeat"); 34 - 35 29 // Broadcast if needed 36 30 if (this.hasAttribute("group")) { 37 31 this.broadcast(this.nameWithGroup, {}); ··· 49 43 /** @type {import("@components/engine/queue/element.js").CLASS} */ 50 44 this.queue = query(this, "queue-engine-selector"); 51 45 46 + /** @type {RepeatShuffleEngine} */ 47 + this.repeatShuffle = query(this, "repeat-shuffle-engine-selector"); 48 + 52 49 // Wait until defined 53 50 await customElements.whenDefined(this.audio.localName); 54 51 await customElements.whenDefined(this.input.localName); 55 52 await customElements.whenDefined(this.queue.localName); 53 + await customElements.whenDefined(this.repeatShuffle.localName); 56 54 57 55 // Effects 58 56 this.effect(() => this.monitorActiveQueueItem()); 59 57 this.effect(() => this.monitorAudioEnd()); 60 58 } 61 59 62 - /** 63 - * @override 64 - * @param {string} name 65 - * @param {string} oldValue 66 - * @param {string} newValue 67 - */ 68 - attributeChangedCallback(name, oldValue, newValue) { 69 - super.attributeChangedCallback(name, oldValue, newValue); 70 - 71 - if (name === "repeat") { 72 - this.#repeat.value = newValue != null; 73 - } 74 - } 75 - 76 60 // 🛠️ 77 61 78 62 async monitorActiveQueueItem() { ··· 126 110 // TODO: Not sure yet if this is the best way to approach this. 127 111 // The idea is that scrobblers would more easily pick this up, 128 112 // as opposed to just resetting the audio. 129 - if (this.#repeat.value) { 113 + if (this.repeatShuffle?.repeat()) { 130 114 const now = this.queue.now(); 131 115 if (now) { 132 116 await this.queue.add({
-111
src/components/orchestrator/queue-tracks/element.js
··· 1 - import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 - import { untracked } from "@common/signal.js"; 3 - 4 - /** 5 - * @import {Track} from "@definitions/types.d.ts" 6 - * @import {ProxiedActions} from "@common/worker.d.ts" 7 - * @import {InputElement} from "@components/input/types.d.ts" 8 - * @import {OutputElement} from "@components/output/types.d.ts" 9 - * 10 - * @import {Actions} from "./types.d.ts" 11 - */ 12 - 13 - //////////////////////////////////////////// 14 - // ELEMENT 15 - //////////////////////////////////////////// 16 - 17 - /** 18 - * Update the queue pool whenever 19 - * tracks have been loaded, 20 - * or the tracks collection changes. 21 - */ 22 - class QueueTracksOrchestrator extends BroadcastableDiffuseElement { 23 - static NAME = "diffuse/orchestrator/queue-tracks"; 24 - static WORKER_URL = "components/orchestrator/queue-tracks/worker.js"; 25 - 26 - /** @type {ProxiedActions<Actions>} */ 27 - #proxy; 28 - 29 - constructor() { 30 - super(); 31 - this.#proxy = this.workerProxy({ 32 - forceNew: { 33 - dependencies: { 34 - input: true, 35 - }, 36 - }, 37 - }); 38 - } 39 - 40 - // LIFECYCLE 41 - 42 - /** 43 - * @override 44 - */ 45 - async connectedCallback() { 46 - // Broadcast if needed 47 - if (this.hasAttribute("group")) { 48 - this.broadcast(this.nameWithGroup, {}); 49 - } 50 - 51 - // Super 52 - super.connectedCallback(); 53 - 54 - /** @type {InputElement} */ 55 - const input = query(this, "input-selector"); 56 - 57 - /** @type {OutputElement} */ 58 - const output = query(this, "output-selector"); 59 - 60 - /** @type {import("@components/engine/queue/element.js").CLASS} */ 61 - const queue = query(this, "queue-engine-selector"); 62 - 63 - // Assign to self 64 - this.input = input; 65 - this.output = output; 66 - this.queue = queue; 67 - 68 - // When defined 69 - await customElements.whenDefined(this.input.localName); 70 - await customElements.whenDefined(this.output.localName); 71 - await customElements.whenDefined(this.queue.localName); 72 - 73 - // Watch tracks collection 74 - this.effect(() => { 75 - const tracks = output.tracks.collection(); 76 - 77 - this.isLeader().then((isLeader) => { 78 - if (!isLeader) return; 79 - untracked(() => this.#proxy.poolAvailable(tracks)); 80 - }); 81 - }); 82 - 83 - // 🌸 84 - } 85 - 86 - // WORKERS 87 - 88 - /** 89 - * @override 90 - */ 91 - dependencies() { 92 - if (!this.input) throw new Error("Input element not defined yet"); 93 - if (!this.queue) throw new Error("Queue element not defined yet"); 94 - 95 - return { 96 - input: this.input, 97 - queue: this.queue, 98 - }; 99 - } 100 - } 101 - 102 - export default QueueTracksOrchestrator; 103 - 104 - //////////////////////////////////////////// 105 - // REGISTER 106 - //////////////////////////////////////////// 107 - 108 - export const CLASS = QueueTracksOrchestrator; 109 - export const NAME = "do-queue-tracks"; 110 - 111 - customElements.define(NAME, QueueTracksOrchestrator);
-5
src/components/orchestrator/queue-tracks/types.d.ts
··· 1 - import type { Track } from "@definitions/types.d.ts"; 2 - 3 - export type Actions = { 4 - poolAvailable(tracks: Track[]): Promise<void>; 5 - };
+1 -1
src/components/orchestrator/queue-tracks/worker.js src/components/orchestrator/auto-queue/worker.js
··· 16 16 * @type {ActionsWithTunnel<Actions>["poolAvailable"]} 17 17 */ 18 18 export async function poolAvailable({ data, ports }) { 19 - const cachedTracks = data.filter((t) => t.kind !== "placeholder"); 19 + const cachedTracks = data.tracks.filter((t) => t.kind !== "placeholder"); 20 20 21 21 /** @type {ProxiedActions<InputActions>} */ 22 22 const input = workerProxy(() => ports.input);
-100
src/components/orchestrator/repeat-shuffle/element.js
··· 1 - import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 - import { signal } from "@common/signal.js"; 3 - 4 - //////////////////////////////////////////// 5 - // ELEMENT 6 - //////////////////////////////////////////// 7 - 8 - class RepeatShuffleOrchestrator extends BroadcastableDiffuseElement { 9 - static NAME = "diffuse/orchestrator/repeat-shuffle"; 10 - 11 - // SIGNALS 12 - 13 - #repeat = signal(false); 14 - #shuffle = signal(false); 15 - 16 - repeat = this.#repeat.get; 17 - shuffle = this.#shuffle.get; 18 - 19 - // LIFECYCLE 20 - 21 - /** 22 - * @override 23 - */ 24 - async connectedCallback() { 25 - // Broadcast if needed 26 - if (this.hasAttribute("group")) { 27 - // TODO: Replicate state (repeat & shuffle) 28 - this.broadcast(this.nameWithGroup, {}); 29 - } 30 - 31 - // Super 32 - super.connectedCallback(); 33 - 34 - /** @type {import("@components/engine/queue/element.js").CLASS} */ 35 - const queue = query(this, "queue-engine-selector"); 36 - 37 - // Assign to self 38 - this.queue = queue; 39 - 40 - // Signals 41 - const storagePrefix = 42 - `${this.constructor.prototype.constructor.NAME}/${this.group}/`; 43 - 44 - this.#repeat.value = 45 - localStorage.getItem(`${storagePrefix}/repeat`) === "true" ? true : false; 46 - this.#shuffle.value = 47 - localStorage.getItem(`${storagePrefix}/shuffle`) === "true" 48 - ? true 49 - : false; 50 - 51 - // Wait until defined 52 - await customElements.whenDefined(queue.localName); 53 - 54 - // Effects 55 - this.effect(() => { 56 - const trigger = queue.now(); 57 - const _other_trigger = queue.supplyFingerprint(); 58 - 59 - this.isLeader().then((isLeader) => { 60 - if (!isLeader) return; 61 - // TODO: What happens when shuffle changes here? Need to reset queue probably. 62 - queue.fill({ amount: 10, shuffled: this.#shuffle.value }); 63 - if (!trigger) queue.shift(); 64 - }); 65 - }); 66 - 67 - this.effect(() => 68 - localStorage.setItem( 69 - `${storagePrefix}/repeat`, 70 - this.#repeat.value ? "true" : "false", 71 - ) 72 - ); 73 - 74 - this.effect(() => 75 - localStorage.setItem( 76 - `${storagePrefix}/shuffle`, 77 - this.#shuffle.value ? "true" : "false", 78 - ) 79 - ); 80 - } 81 - 82 - // ACTIONS 83 - 84 - /** @param {boolean} bool */ 85 - setRepeat = (bool) => this.#repeat.value = bool; 86 - 87 - /** @param {boolean} bool */ 88 - setShuffle = (bool) => this.#shuffle.value = bool; 89 - } 90 - 91 - export default RepeatShuffleOrchestrator; 92 - 93 - //////////////////////////////////////////// 94 - // REGISTER 95 - //////////////////////////////////////////// 96 - 97 - export const CLASS = RepeatShuffleOrchestrator; 98 - export const NAME = "do-repeat-shuffle"; 99 - 100 - customElements.define(NAME, CLASS);
+2 -2
src/index.vto
··· 21 21 - title: "Loader" 22 22 todo: true 23 23 desc: > 24 - **A theme that loads other themes!** Load a theme from a URL, text snippet or from your user data output. _If you're taking the first steps to customize, checkout the [constituents loader](#constituents) first!_ 24 + **A theme that loads other themes!** _If you're taking the first steps to customize, checkout the [constituents loader](#constituents) first!_ 25 25 - url: "themes/webamp/" 26 26 title: "Webamp" 27 27 desc: > ··· 37 37 - url: "themes/loader/constituent/" 38 38 title: "Loader" 39 39 desc: > 40 - **Bring in other constituents!** Load a constituent from a URL, text snippet or from your user data output. 40 + **Bring in other constituents!** Load a constituent from a code snippet, store it in your user data output, explore community creations, or generate one. 41 41 - url: "themes/webamp/browser/" 42 42 title: "Webamp / Browser" 43 43 desc: >
+4 -4
src/themes/blur/artwork-controller/element.js
··· 22 22 * @import AudioEngine from "@components/engine/audio/element.js" 23 23 * @import QueueEngine from "@components/engine/queue/element.js" 24 24 * @import ArtworkProcessor from "@components/processor/artwork/element.js" 25 - * @import RepeatShuffleOrchestrator from "@components/orchestrator/repeat-shuffle/element.js" 25 + * @import RepeatShuffleEngine from "@components/engine/repeat-shuffle/element.js" 26 26 */ 27 27 28 28 class ArtworkController extends DiffuseElement { ··· 58 58 $audio = signal(/** @type {AudioEngine | undefined} */ (undefined)); 59 59 $input = signal(/** @type {InputElement | undefined} */ (undefined)); 60 60 $queue = signal(/** @type {QueueEngine | undefined} */ (undefined)); 61 - $repeatShuffle = signal(/** @type {RepeatShuffleOrchestrator | undefined} */ (undefined)); 61 + $repeatShuffle = signal(/** @type {RepeatShuffleEngine | undefined} */ (undefined)); 62 62 63 63 // SIGNALS - COMPUTED 64 64 ··· 91 91 /** @type {QueueEngine} */ 92 92 const queue = query(this, "queue-engine-selector"); 93 93 94 - /** @type {RepeatShuffleOrchestrator} */ 95 - const repeatShuffle = query(this, "repeat-shuffle-orchestrator-selector"); 94 + /** @type {RepeatShuffleEngine} */ 95 + const repeatShuffle = query(this, "repeat-shuffle-engine-selector"); 96 96 97 97 this.$artwork.value = artwork; 98 98 this.$audio.value = audio;
+8 -21
src/themes/blur/artwork-controller/index.js
··· 1 1 import foundation from "@common/constituents/foundation.js"; 2 - import { effect } from "@common/signal.js"; 3 - 4 2 import ArtworkController from "@themes/blur/artwork-controller/element.js"; 5 3 6 4 // Setup the prerequisite elements 7 - const assemblage = foundation.assemblage.playAudioFromQueue(); 5 + foundation.features.fillQueueAutomatically(); 6 + foundation.features.playAudioFromQueue(); 7 + foundation.features.processInputs(); 8 8 9 - const aud = assemblage.engine.audio; 10 - const inp = assemblage.orchestrator.input; 11 - const oqa = assemblage.orchestrator.queueAudio; 12 - const ors = assemblage.orchestrator.repeatShuffle; 13 - const que = assemblage.engine.queue; 14 - 9 + const aud = foundation.engine.audio(); 15 10 const art = foundation.processor.artwork(); 11 + const inp = foundation.orchestrator.input(); 12 + const que = foundation.engine.queue(); 13 + const rse = foundation.engine.repeatShuffle(); 16 14 17 15 // Controller 18 16 const dac = new ArtworkController(); ··· 20 18 dac.setAttribute("audio-engine-selector", aud.selector); 21 19 dac.setAttribute("input-selector", inp.selector); 22 20 dac.setAttribute("queue-engine-selector", que.selector); 23 - dac.setAttribute("repeat-shuffle-orchestrator-selector", ors.selector); 21 + dac.setAttribute("repeat-shuffle-engine-selector", rse.selector); 24 22 25 23 // Add to DOM 26 24 document.body.append(dac); 27 - 28 - // Effect - Link the repeat/shuffle & queue-audio orchestrators 29 - effect(() => { 30 - const repeat = ors.repeat(); 31 - 32 - if (repeat && !oqa.hasAttribute("repeat")) { 33 - oqa.toggleAttribute("repeat"); 34 - } else if (!repeat && oqa.hasAttribute("repeat")) { 35 - oqa.removeAttribute("repeat"); 36 - } 37 - });
-59
src/themes/blur/index.js
··· 1 - import "@components/input/opensubsonic/element.js"; 2 - import "@components/processor/metadata/element.js"; 3 - 4 - import * as Audio from "@components/engine/audio/element.js"; 5 - import * as Output from "@components/orchestrator/output/element.js"; 6 - import * as Queue from "@components/engine/queue/element.js"; 7 - 8 - import { component } from "@common/element.js"; 9 - import { effect } from "@common/signal.js"; 10 - 11 - const audio = component(Audio); 12 - const output = component(Output); 13 - const queue = component(Queue); 14 - 15 - globalThis.audio = audio; 16 - globalThis.output = output; 17 - globalThis.queue = queue; 18 - 19 - // 🚀 20 - 21 - isLeader().then((bool) => { 22 - if (!bool) return; 23 - 24 - // Only load these orchestrators if leader 25 - import("@components/orchestrator/process-tracks/element.js"); 26 - import("@components/orchestrator/queue-audio/element.js"); 27 - import("@components/orchestrator/queue-tracks/element.js"); 28 - }); 29 - 30 - // EFFECTS 31 - 32 - effect(() => { 33 - console.log("Active queue item:", queue.now()); 34 - }); 35 - 36 - effect(() => { 37 - console.log("Queue pool hash:", queue.supplyFingerprint()); 38 - }); 39 - 40 - /** 41 - * Make sure there's always some random tracks in the queue. 42 - */ 43 - effect(() => { 44 - const trigger = queue.now(); 45 - const _other_trigger = queue.supplyFingerprint(); 46 - 47 - isLeader().then((bool) => { 48 - if (bool) { 49 - queue.fill({ amount: 10, shuffled: true }); 50 - if (!trigger) queue.shift(); 51 - } 52 - }); 53 - }); 54 - 55 - // 🛠️ 56 - 57 - async function isLeader() { 58 - return await audio.isLeader(); 59 - }
+9 -6
src/themes/loader/constituent/index.js
··· 40 40 </a> 41 41 <br /> 42 42 <small> 43 - <span @click="${deleteConstituent( 44 - c.cid, 45 - )}" style="cursor: pointer;"> 43 + <span @click="${deleteConstituent({ 44 + cid: c.cid, 45 + name: c.name, 46 + })}" style="cursor: pointer;"> 46 47 Delete 47 48 </span> 48 49 </small> ··· 68 69 `; 69 70 70 71 /** 71 - * @param {string} cid 72 + * @param {{ cid: string; name: string }} _ 72 73 */ 73 - function deleteConstituent(cid) { 74 + function deleteConstituent({ cid, name }) { 74 75 return () => { 75 76 output.constituents.save( 76 - output.constituents.collection().filter((c) => c.cid !== cid), 77 + output.constituents.collection().filter((c) => 78 + !(c.name === name && c.cid === cid) 79 + ), 77 80 ); 78 81 }; 79 82 }
+22 -8
src/themes/loader/constituent/index.vto
··· 112 112 <h2 id="foundation" style="margin-top: 0;">Foundation</h2> 113 113 114 114 <p> 115 - Diffuse comes with a foundation that preconfigures all elements so you don't have to set them up yourself, along with an assemblage of elements for certain activities. It internally tracks the DOM addition of the custom elements, so no need to worry about setting up an element multiple times. 115 + Diffuse comes with a foundation that preconfigures all elements so you don't have to set them up yourself, along with a combination of elements for certain features. It internally tracks the DOM addition of the custom elements, so no need to worry about setting up an element multiple times. 116 116 </p> 117 117 <p> 118 118 <small><i class="ph-fill ph-info"></i> Refer to the <a href="#elements">elements index</a> to find out what each element does.</small> ··· 124 124 {{ /echo }} 125 125 {{ echo -}}foundation.engine.audio(){{- /echo }} 126 126 {{ echo -}}foundation.engine.queue(){{- /echo }} 127 + {{ echo -}}foundation.engine.repeatShuffle(){{- /echo }} 127 128 129 + {{ echo -}}foundation.orchestrator.autoQueue(){{- /echo }} 128 130 {{ echo -}}foundation.orchestrator.input(){{- /echo }} 129 131 {{ echo -}}foundation.orchestrator.output(){{- /echo }} 130 132 {{ echo -}}foundation.orchestrator.queueAudio(){{- /echo }} 131 - {{ echo -}}foundation.orchestrator.queueTracks(){{- /echo }} 132 133 {{ echo -}}foundation.orchestrator.processTracks(){{- /echo }} 133 - {{ echo -}}foundation.orchestrator.repeatShuffle(){{- /echo }} 134 134 {{ echo -}}foundation.orchestrator.searchTracks(){{- /echo }} 135 135 {{ echo -}}foundation.orchestrator.sources(){{- /echo }} 136 136 ··· 141 141 </div> 142 142 143 143 <p> 144 - <small>Assemblages:</small> 144 + <small>Features:</small> 145 145 </p> 146 146 <ul style="margin-bottom: 0;"> 147 147 <li> 148 + <span>Fill the queue automatically <small>(infinite play)</small></span> 149 + <div class="list-description"> 150 + <code>foundation.feature.fillQueueAutomatically()</code> 151 + </div> 152 + </li> 153 + <li> 148 154 <span>Play audio from the queue</span> 149 155 <div class="list-description"> 150 - <code>foundation.assemblage.playAudioFromQueue()</code> 156 + <code>foundation.feature.playAudioFromQueue()</code> 151 157 </div> 152 158 </li> 153 159 <li> 154 - <span>Queue management</span> 160 + <span>Process inputs <small>(into tracks, etc)</small></span> 155 161 <div class="list-description"> 156 - <code>foundation.assemblage.queueManagement()</code> 162 + <code>foundation.feature.processInputs()</code> 157 163 </div> 158 164 </li> 159 165 <li> 160 166 <span>Search through your collection</span> 161 167 <div class="list-description" style="margin-bottom: 0;"> 162 - <code>foundation.assemblage.searchThroughCollection()</code> 168 + <code>foundation.feature.searchThroughCollection()</code> 163 169 </div> 164 170 </li> 171 + </ul> 172 + <h3>Notes</h3> 173 + <p> 174 + While you have the ability to do whatever you want in a custom constituent, the existing constituents are designed to work a certain way; so here's some things to keep in mind: 175 + </p> 176 + <ul> 177 + <li><span>In most cases you'll want to call <code>foundation.feature.processInputs()</code> so that your audio files and streams actually show up.</span></li> 178 + <li><span>Most elements are configured in broadcast mode so they communicate across tabs. There are a few exceptions such as inputs, where we prefer parallelisation.</span></li> 165 179 </ul> 166 180 </section> 167 181
+12 -6
src/themes/webamp/browser/index.js
··· 1 1 import foundation from "@common/constituents/foundation.js"; 2 2 import BrowserElement from "@themes/webamp/browser/element.js"; 3 3 4 - const que = foundation.assemblage.queueManagement(); 5 - const sea = foundation.assemblage.searchThroughCollection(); 4 + foundation.features.fillQueueAutomatically(); 5 + foundation.features.processInputs(); 6 + foundation.features.searchThroughCollection(); 7 + 8 + const inp = foundation.orchestrator.input(); 9 + const out = foundation.orchestrator.output(); 10 + const que = foundation.engine.queue(); 11 + const sea = foundation.processor.search(); 6 12 7 13 const el = new BrowserElement(); 8 - el.setAttribute("input-selector", que.orchestrator.input.selector); 9 - el.setAttribute("output-selector", que.orchestrator.output.selector); 10 - el.setAttribute("queue-engine-selector", que.engine.queue.selector); 11 - el.setAttribute("search-processor-selector", sea.processor.search.selector); 14 + el.setAttribute("input-selector", inp.selector); 15 + el.setAttribute("output-selector", out.selector); 16 + el.setAttribute("queue-engine-selector", que.selector); 17 + el.setAttribute("search-processor-selector", sea.selector); 12 18 13 19 document.querySelector("#placeholder")?.replaceWith(el);
+1 -1
src/themes/webamp/index.js
··· 1 1 import "@components/input/opensubsonic/element.js"; 2 2 import "@components/input/s3/element.js"; 3 + import "@components/orchestrator/auto-queue/element.js"; 3 4 import "@components/orchestrator/input/element.js"; 4 5 import "@components/orchestrator/output/element.js"; 5 6 import "@components/orchestrator/process-tracks/element.js"; 6 - import "@components/orchestrator/queue-tracks/element.js"; 7 7 import "@components/orchestrator/search-tracks/element.js"; 8 8 import "@components/orchestrator/sources/element.js"; 9 9 import "@components/processor/metadata/element.js";