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.

feat: controller orchestrator

+163 -116
+21 -3
src/common/foundation.js
··· 47 47 artwork: signal( 48 48 /** @type {import("~/components/orchestrator/artwork/element.js").CLASS | null} */ (null), 49 49 ), 50 + controller: signal( 51 + /** @type {import("~/components/orchestrator/controller/element.js").CLASS | null} */ (null), 52 + ), 50 53 autoQueue: signal( 51 54 /** @type {import("~/components/orchestrator/auto-queue/element.js").CLASS | null} */ (null), 52 55 ), ··· 78 81 /** @type {import("~/components/orchestrator/sources/element.js").CLASS | null} */ (null), 79 82 ), 80 83 }, 81 - 82 84 }; 83 85 84 86 /** ··· 104 106 105 107 orchestrator: { 106 108 artwork, 109 + controller, 107 110 autoQueue, 108 111 favourites, 109 112 mediaSession, ··· 136 139 137 140 orchestrator: { 138 141 artwork: signals.orchestrator.artwork.get, 142 + controller: signals.orchestrator.controller.get, 139 143 autoQueue: signals.orchestrator.autoQueue.get, 140 144 favourites: signals.orchestrator.favourites.get, 141 145 mediaSession: signals.orchestrator.mediaSession.get, ··· 147 151 scrobbleAudio: signals.orchestrator.scrobbleAudio.get, 148 152 sources: signals.orchestrator.sources.get, 149 153 }, 150 - 151 154 }, 152 155 153 156 // Utilities ··· 300 303 return findExistingOrAdd(a, signals.orchestrator.artwork); 301 304 } 302 305 303 - 304 306 // Orchestrators 305 307 async function autoQueue() { 306 308 const [{ default: AutoQueueOrchestrator }, q, r, t] = await Promise.all([ ··· 317 319 aqo.setAttribute("tracks-selector", t.selector); 318 320 319 321 return findExistingOrAdd(aqo, signals.orchestrator.autoQueue); 322 + } 323 + 324 + async function controller() { 325 + const [{ default: ControllerOrchestrator }, a, o, q] = await Promise.all([ 326 + import("~/components/orchestrator/controller/element.js"), 327 + audio(), 328 + output(), 329 + queue(), 330 + ]); 331 + 332 + const co = new ControllerOrchestrator(); 333 + co.setAttribute("audio-engine-selector", a.selector); 334 + co.setAttribute("output-selector", o.selector); 335 + co.setAttribute("queue-engine-selector", q.selector); 336 + 337 + return findExistingOrAdd(co, signals.orchestrator.controller); 320 338 } 321 339 322 340 async function favourites() {
+80
src/components/orchestrator/controller/element.js
··· 1 + import { DiffuseElement, query, whenElementsDefined } from "~/common/element.js"; 2 + import { computed, signal } from "~/common/signal.js"; 3 + 4 + /** 5 + * @import {OutputElement} from "~/components/output/types.d.ts" 6 + * @import AudioEngine from "~/components/engine/audio/element.js" 7 + * @import QueueEngine from "~/components/engine/queue/element.js" 8 + */ 9 + 10 + //////////////////////////////////////////// 11 + // ELEMENT 12 + //////////////////////////////////////////// 13 + 14 + /** 15 + * Provides commonly used computed signals derived from the audio engine, 16 + * queue engine, and output — so theme elements don't need to re-implement them. 17 + */ 18 + class ControllerOrchestrator extends DiffuseElement { 19 + static NAME = "diffuse/orchestrator/controller"; 20 + 21 + // SIGNALS - DEPENDENCIES 22 + 23 + $audio = signal(/** @type {AudioEngine | undefined} */ (undefined)); 24 + $output = signal(/** @type {OutputElement | undefined} */ (undefined)); 25 + $queue = signal(/** @type {QueueEngine | undefined} */ (undefined)); 26 + 27 + // SIGNALS - COMPUTED 28 + 29 + audio = computed(() => { 30 + const curr = this.$queue.value?.now(); 31 + return curr ? this.$audio.value?.state(curr.id) : undefined; 32 + }); 33 + 34 + currentTrack = computed(() => { 35 + const item = this.$queue.value?.now(); 36 + if (!item) return undefined; 37 + const col = this.$output.value?.tracks.collection(); 38 + if (!col || col.state !== "loaded") return undefined; 39 + return col.data.find((t) => t.id === item.id); 40 + }); 41 + 42 + isPlaying = computed(() => { 43 + return this.$audio.value?.isPlaying(); 44 + }); 45 + 46 + // LIFECYCLE 47 + 48 + /** 49 + * @override 50 + */ 51 + connectedCallback() { 52 + super.connectedCallback(); 53 + 54 + /** @type {AudioEngine} */ 55 + const audio = query(this, "audio-engine-selector"); 56 + 57 + /** @type {OutputElement} */ 58 + const output = query(this, "output-selector"); 59 + 60 + /** @type {QueueEngine} */ 61 + const queue = query(this, "queue-engine-selector"); 62 + 63 + whenElementsDefined({ audio, output, queue }).then(() => { 64 + this.$audio.value = audio; 65 + this.$output.value = output; 66 + this.$queue.value = queue; 67 + }); 68 + } 69 + } 70 + 71 + export default ControllerOrchestrator; 72 + 73 + //////////////////////////////////////////// 74 + // REGISTER 75 + //////////////////////////////////////////// 76 + 77 + export const CLASS = ControllerOrchestrator; 78 + export const NAME = "do-controller"; 79 + 80 + customElements.define(NAME, CLASS);
+25 -48
src/themes/blur/artwork-controller/element.js
··· 11 11 whenElementsDefined, 12 12 } from "~/common/element.js"; 13 13 14 - import { computed, signal, untracked } from "~/common/signal.js"; 14 + import { signal, untracked } from "~/common/signal.js"; 15 15 16 16 /** 17 17 * @import {RenderArg} from "~/common/element.d.ts" 18 18 * 19 19 * @import {InputElement} from "~/components/input/types.d.ts" 20 - * @import {OutputElement} from "~/components/output/types.d.ts" 21 - * @import AudioEngine from "~/components/engine/audio/element.js" 22 - * @import QueueEngine from "~/components/engine/queue/element.js" 23 20 * @import ArtworkOrchestrator from "~/components/orchestrator/artwork/element.js" 21 + * @import ControllerOrchestrator from "~/components/orchestrator/controller/element.js" 24 22 * @import FavouritesOrchestrator from "~/components/orchestrator/favourites/element.js" 25 23 */ 26 24 ··· 53 51 // SIGNALS - DEPENDENCIES 54 52 55 53 $artwork = signal(/** @type {ArtworkOrchestrator | undefined} */ (undefined)); 56 - $audio = signal(/** @type {AudioEngine | undefined} */ (undefined)); 54 + $controller = signal( 55 + /** @type {ControllerOrchestrator | undefined} */ (undefined), 56 + ); 57 57 $favourites = signal( 58 58 /** @type {FavouritesOrchestrator | undefined} */ (undefined), 59 59 ); 60 60 $input = signal(/** @type {InputElement | undefined} */ (undefined)); 61 - $output = signal(/** @type {OutputElement | undefined} */ (undefined)); 62 - $queue = signal(/** @type {QueueEngine | undefined} */ (undefined)); 63 61 64 62 // SIGNALS - COMPUTED 65 63 66 - audio = computed(() => { 67 - const curr = this.$queue.value?.now(); 68 - return curr ? this.$audio.value?.state(curr.id) : undefined; 69 - }); 70 - 71 - currentTrack = computed(() => { 72 - const item = this.$queue.value?.now(); 73 - if (!item) return undefined; 74 - const col = this.$output.value?.tracks.collection(); 75 - if (!col || col.state !== "loaded") return undefined; 76 - return col.data.find((t) => t.id === item.id); 77 - }); 78 - 79 - isPlaying = computed(() => { 80 - return this.$audio.value?.isPlaying(); 81 - }); 64 + audio = () => this.$controller.value?.audio(); 65 + currentTrack = () => this.$controller.value?.currentTrack(); 66 + isPlaying = () => this.$controller.value?.isPlaying(); 82 67 83 68 // LIFECYCLE 84 69 ··· 91 76 /** @type {ArtworkOrchestrator} */ 92 77 const artwork = query(this, "artwork-selector"); 93 78 94 - /** @type {AudioEngine} */ 95 - const audio = query(this, "audio-engine-selector"); 79 + /** @type {ControllerOrchestrator} */ 80 + const controller = query(this, "controller-orchestrator-selector"); 96 81 97 82 /** @type {InputElement} */ 98 83 const input = query(this, "input-selector"); 99 84 100 - /** @type {OutputElement} */ 101 - const output = query(this, "output-selector"); 102 - 103 - /** @type {QueueEngine} */ 104 - const queue = query(this, "queue-engine-selector"); 105 - 106 85 /** @type {FavouritesOrchestrator} */ 107 86 const favourites = query(this, "favourites-orchestrator-selector"); 108 87 109 - whenElementsDefined({ audio, artwork, favourites, input, output, queue }) 88 + whenElementsDefined({ artwork, controller, favourites, input }) 110 89 .then( 111 90 () => { 112 91 this.$artwork.value = artwork; 113 - this.$audio.value = audio; 92 + this.$controller.value = controller; 114 93 this.$input.value = input; 115 - this.$output.value = output; 116 - this.$queue.value = queue; 117 94 this.$favourites.value = favourites; 118 95 119 96 // Changed artwork based on active queue item. ··· 131 108 this.effect(() => this.#lightOrDark()); 132 109 133 110 this.effect(() => { 134 - const now = !!queue.now(); 111 + const now = !!this.$controller.value?.$queue.value?.now(); 135 112 const aud = this.audio()?.loadingState(); 136 113 const bool = now && aud !== "loaded"; 137 114 ··· 179 156 return; 180 157 } 181 158 182 - if (this.$queue.value?.now()?.id !== track?.id) { 159 + if (this.$controller.value?.$queue.value?.now()?.id !== track?.id) { 183 160 return; 184 161 } 185 162 ··· 303 280 }; 304 281 305 282 fullVolume = () => { 306 - this.$audio.value?.adjustVolume({ volume: 1 }); 283 + this.$controller.value?.$audio.value?.adjustVolume({ volume: 1 }); 307 284 }; 308 285 309 286 mute = () => { 310 - this.$audio.value?.adjustVolume({ volume: 0 }); 287 + this.$controller.value?.$audio.value?.adjustVolume({ volume: 0 }); 311 288 }; 312 289 313 290 next = () => { 314 - this.$queue.value?.shift(); 291 + this.$controller.value?.$queue.value?.shift(); 315 292 }; 316 293 317 294 playPause = () => { 318 - const audioId = this.$queue.value?.now()?.id; 295 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 319 296 320 297 if (this.isPlaying() && audioId) { 321 - this.$audio.value?.pause({ audioId }); 298 + this.$controller.value?.$audio.value?.pause({ audioId }); 322 299 } else if (audioId) { 323 - this.$audio.value?.play({ audioId }); 300 + this.$controller.value?.$audio.value?.play({ audioId }); 324 301 } 325 302 }; 326 303 327 304 previous = () => { 328 - this.$queue.value?.unshift(); 305 + this.$controller.value?.$queue.value?.unshift(); 329 306 }; 330 307 331 308 /** ··· 336 313 ? /** @type {HTMLProgressElement} */ (event.target) 337 314 : null; 338 315 const percentage = target ? event.offsetX / target.clientWidth : 0; 339 - const audioId = this.$queue.value?.now()?.id; 316 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 340 317 341 - if (audioId) this.$audio.value?.seek({ audioId, percentage }); 318 + if (audioId) this.$controller.value?.$audio.value?.seek({ audioId, percentage }); 342 319 }; 343 320 344 321 /** ··· 350 327 : null; 351 328 352 329 const percentage = target ? event.offsetX / target.clientWidth : 0; 353 - this.$audio.value?.adjustVolume({ volume: percentage }); 330 + this.$controller.value?.$audio.value?.adjustVolume({ volume: percentage }); 354 331 }; 355 332 356 333 toggleFavourite = () => { ··· 508 485 <div class="volume"> 509 486 <i @click="${this.mute}" class="ph-fill ph-speaker-none"></i> 510 487 <div @click="${this.setVolume}" class="progress-bar"> 511 - <progress max="100" value="${(this.$audio.value?.volume() ?? 488 + <progress max="100" value="${(this.$controller.value?.$audio.value?.volume() ?? 512 489 0) * 100}"></progress> 513 490 </div> 514 491 <i @click="${this
+3 -7
src/themes/blur/artwork-controller/facet/index.inline.js
··· 8 8 await foundation.orchestrator.queueAudio(); 9 9 await foundation.orchestrator.mediaSession(); 10 10 11 - const [aud, art, fav, inp, out, que] = await Promise.all([ 12 - foundation.engine.audio(), 11 + const [art, ctl, fav, inp] = await Promise.all([ 13 12 foundation.orchestrator.artwork(), 13 + foundation.orchestrator.controller(), 14 14 foundation.orchestrator.favourites(), 15 15 foundation.configurator.input(), 16 - foundation.orchestrator.output(), 17 - foundation.engine.queue(), 18 16 ]); 19 17 20 18 // Controller 21 19 const dac = new ArtworkController(); 22 20 dac.setAttribute("artwork-selector", art.selector); 23 - dac.setAttribute("audio-engine-selector", aud.selector); 21 + dac.setAttribute("controller-orchestrator-selector", ctl.selector); 24 22 dac.setAttribute("input-selector", inp.selector); 25 - dac.setAttribute("output-selector", out.selector); 26 - dac.setAttribute("queue-engine-selector", que.selector); 27 23 dac.setAttribute("favourites-orchestrator-selector", fav.selector); 28 24 29 25 // Add to DOM
+1 -3
src/themes/winamp/facet/index.html
··· 94 94 95 95 <!-- ⚡️ --> 96 96 <dtw-winamp 97 - audio-engine-selector="de-audio" 98 - output-selector="#output" 99 - queue-engine-selector="de-queue" 97 + controller-orchestrator-selector="do-controller" 100 98 repeat-shuffle-engine-selector="de-repeat-shuffle" 101 99 ></dtw-winamp> 102 100 </dtw-window-manager>
+1
src/themes/winamp/facet/index.inline.js
··· 21 21 await foundation.orchestrator.sources(); 22 22 await foundation.orchestrator.processTracks({ disableWhenReady: true }); 23 23 await foundation.orchestrator.queueAudio(); 24 + await foundation.orchestrator.controller(); 24 25 25 26 await import("~/themes/winamp/browser/element.js"); 26 27 await import("~/themes/winamp/window/element.js");
+32 -55
src/themes/winamp/winamp/element.js
··· 3 3 query, 4 4 whenElementsDefined, 5 5 } from "~/common/element.js"; 6 - import { computed, signal, untracked } from "~/common/signal.js"; 6 + import { signal, untracked } from "~/common/signal.js"; 7 7 8 8 /** 9 9 * @import {RenderArg} from "~/common/element.d.ts" 10 10 * 11 - * @import {OutputElement} from "~/components/output/types.d.ts" 12 - * @import AudioEngine from "~/components/engine/audio/element.js" 13 - * @import QueueEngine from "~/components/engine/queue/element.js" 11 + * @import ControllerOrchestrator from "~/components/orchestrator/controller/element.js" 14 12 * @import RepeatShuffleEngine from "~/components/engine/repeat-shuffle/element.js" 15 13 */ 16 14 ··· 235 233 236 234 // SIGNALS - DEPENDENCIES 237 235 238 - $audio = signal(/** @type {AudioEngine | undefined} */ (undefined)); 239 - $output = signal(/** @type {OutputElement | undefined} */ (undefined)); 240 - $queue = signal(/** @type {QueueEngine | undefined} */ (undefined)); 236 + $controller = signal( 237 + /** @type {ControllerOrchestrator | undefined} */ (undefined), 238 + ); 241 239 $repeatShuffle = signal( 242 240 /** @type {RepeatShuffleEngine | undefined} */ (undefined), 243 241 ); 244 242 245 243 // SIGNALS - COMPUTED 246 244 247 - audio = computed(() => { 248 - const curr = this.$queue.value?.now(); 249 - return curr ? this.$audio.value?.state(curr.id) : undefined; 250 - }); 251 - 252 - currentTrack = computed(() => { 253 - const item = this.$queue.value?.now(); 254 - if (!item) return undefined; 255 - const col = this.$output.value?.tracks.collection(); 256 - if (!col || col.state !== "loaded") return undefined; 257 - return col.data.find((t) => t.id === item.id); 258 - }); 259 - 260 - isPlaying = computed(() => { 261 - return this.$audio.value?.isPlaying(); 262 - }); 245 + audio = () => this.$controller.value?.audio(); 246 + currentTrack = () => this.$controller.value?.currentTrack(); 247 + isPlaying = () => this.$controller.value?.isPlaying(); 263 248 264 249 // LIFECYCLE 265 250 ··· 269 254 connectedCallback() { 270 255 super.connectedCallback(); 271 256 272 - /** @type {AudioEngine} */ 273 - const audio = query(this, "audio-engine-selector"); 274 - 275 - /** @type {OutputElement} */ 276 - const output = query(this, "output-selector"); 277 - 278 - /** @type {QueueEngine} */ 279 - const queue = query(this, "queue-engine-selector"); 257 + /** @type {ControllerOrchestrator} */ 258 + const controller = query(this, "controller-orchestrator-selector"); 280 259 281 260 /** @type {RepeatShuffleEngine} */ 282 261 const repeatShuffle = query(this, "repeat-shuffle-engine-selector"); 283 262 284 - whenElementsDefined({ audio, output, queue, repeatShuffle }).then(() => { 285 - this.$audio.value = audio; 286 - this.$output.value = output; 287 - this.$queue.value = queue; 263 + whenElementsDefined({ controller, repeatShuffle }).then(() => { 264 + this.$controller.value = controller; 288 265 this.$repeatShuffle.value = repeatShuffle; 289 266 290 267 this.effect(() => { ··· 306 283 }); 307 284 308 285 this.effect(() => { 309 - const audioId = this.$queue.value?.now()?.id; 286 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 310 287 const playing = this.isPlaying(); 311 288 untracked(() => { 312 289 if (audioId) this.#connectToAnalyser(audioId); ··· 865 842 /** @param {string} audioId */ 866 843 #connectToAnalyser(audioId) { 867 844 if (this.#srcNodes.has(audioId)) return; 868 - const audioEl = this.$audio.value 845 + const audioEl = this.$controller.value?.$audio.value 869 846 ?.querySelector(`de-audio-item[id="${audioId}"]:not([preload]) audio`); 870 847 if (!(audioEl instanceof HTMLAudioElement)) return; 871 848 this.#ensureAnalyser(); ··· 959 936 const PUSH_DOWN = H >= 16 ? 2 : 0; 960 937 961 938 // Lazy-connect current audio 962 - const audioId = this.$queue.value?.now()?.id; 939 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 963 940 if (audioId) this.#connectToAnalyser(audioId); 964 941 965 942 // Time-domain → FFT → spectral data ··· 1151 1128 #onVolumeInput = (e) => { 1152 1129 if (!(e.target instanceof HTMLInputElement)) return; 1153 1130 const volume = Number(e.target.value) / 100; 1154 - this.$audio.value?.adjustVolume({ volume }); 1131 + this.$controller.value?.$audio.value?.adjustVolume({ volume }); 1155 1132 1156 1133 this.#marqueeOverride.value = `Volume: ${Math.round(volume * 100)}%`; 1157 1134 clearTimeout(this.#marqueeOverrideTimeout); ··· 1183 1160 #onPositionChange = (e) => { 1184 1161 if (!(e.target instanceof HTMLInputElement)) return; 1185 1162 const percentage = Number(e.target.value) / 100; 1186 - const audioId = this.$queue.value?.now()?.id; 1187 - if (audioId) this.$audio.value?.seek({ audioId, percentage }); 1163 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 1164 + if (audioId) this.$controller.value?.$audio.value?.seek({ audioId, percentage }); 1188 1165 this.#marqueeOverride.value = null; 1189 1166 setTimeout(() => { this.#seekingProgress.value = null; }, 250); 1190 1167 }; 1191 1168 1192 1169 #playPause = () => { 1193 - const audioId = this.$queue.value?.now()?.id; 1170 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 1194 1171 this.#stopped.value = false; 1195 1172 if (this.isPlaying() && audioId) { 1196 - this.$audio.value?.pause({ audioId }); 1173 + this.$controller.value?.$audio.value?.pause({ audioId }); 1197 1174 } else if (audioId) { 1198 - this.$audio.value?.play({ audioId }); 1175 + this.$controller.value?.$audio.value?.play({ audioId }); 1199 1176 } 1200 1177 }; 1201 1178 1202 1179 #stop = () => { 1203 - const audioId = this.$queue.value?.now()?.id; 1180 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 1204 1181 if (!audioId) return; 1205 - if (this.isPlaying()) this.$audio.value?.pause({ audioId }); 1206 - this.$audio.value?.seek({ audioId, percentage: 0 }); 1182 + if (this.isPlaying()) this.$controller.value?.$audio.value?.pause({ audioId }); 1183 + this.$controller.value?.$audio.value?.seek({ audioId, percentage: 0 }); 1207 1184 this.#stopped.value = true; 1208 1185 }; 1209 1186 ··· 1212 1189 }; 1213 1190 1214 1191 #next = () => { 1215 - this.$queue.value?.shift(); 1192 + this.$controller.value?.$queue.value?.shift(); 1216 1193 }; 1217 1194 1218 1195 #previous = () => { 1219 - this.$queue.value?.unshift(); 1196 + this.$controller.value?.$queue.value?.unshift(); 1220 1197 }; 1221 1198 1222 1199 #toggleShuffle = () => { ··· 1321 1298 }; 1322 1299 1323 1300 #closeMain = () => { 1324 - const audioId = this.$queue.value?.now()?.id; 1325 - if (audioId && this.isPlaying()) this.$audio.value?.pause({ audioId }); 1301 + const audioId = this.$controller.value?.$queue.value?.now()?.id; 1302 + if (audioId && this.isPlaying()) this.$controller.value?.$audio.value?.pause({ audioId }); 1326 1303 this.#mainOpen.value = false; 1327 1304 }; 1328 1305 ··· 1377 1354 /** @param {number} idx */ 1378 1355 #playTrack = (idx) => { 1379 1356 this.#selectedIndex.value = idx; 1380 - const queue = this.$queue.value; 1357 + const queue = this.$controller.value?.$queue.value; 1381 1358 if (!queue) return; 1382 1359 const past = queue.past(); 1383 1360 const pastLen = past.length; ··· 1422 1399 `; 1423 1400 }; 1424 1401 1425 - const volume = this.$audio.value?.volume() ?? 1; 1402 + const volume = this.$controller.value?.$audio.value?.volume() ?? 1; 1426 1403 const volumeSprite = Math.round(volume * 28); 1427 1404 const volumeBgPos = `0 -${(volumeSprite - 1) * 15}px`; 1428 1405 const volumePct = Math.round(volume * 100); ··· 1465 1442 }; 1466 1443 1467 1444 // Playlist 1468 - const queueEl = this.$queue.value; 1445 + const queueEl = this.$controller.value?.$queue.value; 1469 1446 const nowItem = queueEl?.now(); 1470 1447 const allItems = [ 1471 1448 ...(queueEl?.past() ?? []), 1472 1449 ...(nowItem ? [nowItem] : []), 1473 1450 ...(queueEl?.future() ?? []), 1474 1451 ]; 1475 - const col = this.$output.value?.tracks.collection(); 1452 + const col = this.$controller.value?.$output.value?.tracks.collection(); 1476 1453 const trackMap = col?.state === "loaded" 1477 1454 ? new Map(col.data.map((t) => [t.id, t])) 1478 1455 : new Map();