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: repeat-shuffle orchestrator

+160 -20
+7 -16
src/common/constituents/default.js
··· 2 2 import InputOrchestrator from "@components/orchestrator/input/element.js"; 3 3 import OutputOrchestrator from "@components/orchestrator/output/element.js"; 4 4 import QueueTracksOrchestrator from "@components/orchestrator/queue-tracks/element.js"; 5 + import RepeatShuffleOrchestrator from "@components/orchestrator/repeat-shuffle/element.js"; 5 6 import SearchProcessor from "@components/processor/search/element.js"; 6 7 import SearchTracksOrchestrator from "@components/orchestrator/search-tracks/element.js"; 7 - 8 - import { effect } from "../signal.js"; 9 - import QueueAudioOrchestrator from "@components/orchestrator/queue-audio/element.js"; 10 8 11 9 export const GROUP = "constituents"; 12 10 ··· 45 43 oqt.setAttribute("output-selector", "#output"); 46 44 oqt.setAttribute("queue-engine-selector", queue.localName); 47 45 46 + const rso = new RepeatShuffleOrchestrator(); 47 + rso.setAttribute("group", GROUP); 48 + rso.setAttribute("queue-engine-selector", queue.localName); 49 + 48 50 const ost = new SearchTracksOrchestrator(); 49 51 ost.setAttribute("group", GROUP); 50 52 ost.setAttribute("input-selector", "#input"); 51 53 ost.setAttribute("output-selector", "#output"); 52 54 ost.setAttribute("search-processor-selector", search.localName); 53 55 54 - document.body.append(oqt, ost); 55 - 56 - // Signals & effects 57 - effect(() => { 58 - const trigger = queue.now(); 59 - const _other_trigger = queue.poolHash(); 60 - 61 - oqt.isLeader().then((isLeader) => { 62 - if (!isLeader) return; 63 - queue.fill({ amount: 10, shuffled: true }); 64 - if (!trigger) queue.shift(); 65 - }); 66 - }); 56 + document.body.append(oqt, rso, ost); 67 57 68 58 // Return elements 69 59 return { ··· 80 70 input, 81 71 output, 82 72 queueTracks: oqt, 73 + repeatShuffle: rso, 83 74 }, 84 75 processor: { 85 76 search,
+38 -2
src/components/orchestrator/queue-audio/element.js
··· 1 1 import { BroadcastableDiffuseElement, query } from "@common/element.js"; 2 - import { untracked } from "@common/signal.js"; 2 + import { signal, untracked } from "@common/signal.js"; 3 3 4 4 /** 5 5 * @import {InputElement} from "@components/input/types.d.ts" ··· 17 17 * shift the queue if needed. 18 18 */ 19 19 class QueueAudioOrchestrator extends BroadcastableDiffuseElement { 20 + static observedAttributes = ["repeat"]; 21 + 22 + // SIGNALS 23 + 24 + #repeat = signal(false); 25 + 26 + // LIFE CYCLE 27 + 20 28 /** 21 29 * @override 22 30 */ 23 31 async connectedCallback() { 32 + this.#repeat.value = this.hasAttribute("repeat"); 33 + 24 34 // Broadcast if needed 25 35 if (this.hasAttribute("group")) { 26 36 this.broadcast(this.nameWithGroup, {}); ··· 48 58 this.effect(() => this.monitorAudioEnd()); 49 59 } 50 60 61 + /** 62 + * @override 63 + * @param {string} name 64 + * @param {string} oldValue 65 + * @param {string} newValue 66 + */ 67 + attributeChangedCallback(name, oldValue, newValue) { 68 + super.attributeChangedCallback(name, oldValue, newValue); 69 + 70 + if (name === "repeat") { 71 + this.#repeat.value = newValue != null; 72 + } 73 + } 74 + 51 75 // 🛠️ 52 76 53 77 async monitorActiveQueueItem() { ··· 97 121 const now = this.queue.now(); 98 122 const aud = now ? this.audio.state(now.id) : undefined; 99 123 100 - if (aud?.hasEnded() && (await this.isLeader())) await this.queue.shift(); 124 + if (aud?.hasEnded() && (await this.isLeader())) { 125 + // TODO: Not sure yet if this is the best way to approach this. 126 + // The idea is that scrobblers would more easily pick this up, 127 + // as opposed to just resetting the audio. 128 + if (this.#repeat.value) { 129 + await this.queue.add({ 130 + inFront: true, 131 + tracks: [this.queue.now()], 132 + }); 133 + } 134 + 135 + await this.queue.shift(); 136 + } 101 137 } 102 138 } 103 139
+99
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 + this.broadcast(this.nameWithGroup, {}); 28 + } 29 + 30 + // Super 31 + super.connectedCallback(); 32 + 33 + /** @type {import("@components/engine/queue/element.js").CLASS} */ 34 + const queue = query(this, "queue-engine-selector"); 35 + 36 + // Assign to self 37 + this.queue = queue; 38 + 39 + // Signals 40 + const storagePrefix = 41 + `${this.constructor.prototype.constructor.NAME}/${this.group}/`; 42 + 43 + this.#repeat.value = 44 + localStorage.getItem(`${storagePrefix}/repeat`) === "true" ? true : false; 45 + this.#shuffle.value = 46 + localStorage.getItem(`${storagePrefix}/shuffle`) === "true" 47 + ? true 48 + : false; 49 + 50 + // Wait until defined 51 + await customElements.whenDefined(queue.localName); 52 + 53 + // Effects 54 + this.effect(() => { 55 + const trigger = queue.now(); 56 + const _other_trigger = queue.poolHash(); 57 + 58 + this.isLeader().then((isLeader) => { 59 + if (!isLeader) return; 60 + // TODO: What happens when shuffle changes here? Need to reset queue probably. 61 + queue.fill({ amount: 10, shuffled: this.#shuffle.value }); 62 + if (!trigger) queue.shift(); 63 + }); 64 + }); 65 + 66 + this.effect(() => 67 + localStorage.setItem( 68 + `${storagePrefix}/repeat`, 69 + this.#repeat.value ? "true" : "false", 70 + ) 71 + ); 72 + 73 + this.effect(() => 74 + localStorage.setItem( 75 + `${storagePrefix}/shuffle`, 76 + this.#shuffle.value ? "true" : "false", 77 + ) 78 + ); 79 + } 80 + 81 + // ACTIONS 82 + 83 + /** @param {boolean} bool */ 84 + setRepeat = (bool) => this.#repeat.value = bool; 85 + 86 + /** @param {boolean} bool */ 87 + setShuffle = (bool) => this.#shuffle.value = bool; 88 + } 89 + 90 + export default RepeatShuffleOrchestrator; 91 + 92 + //////////////////////////////////////////// 93 + // REGISTER 94 + //////////////////////////////////////////// 95 + 96 + export const CLASS = RepeatShuffleOrchestrator; 97 + export const NAME = "do-repeat-shuffle"; 98 + 99 + customElements.define(NAME, CLASS);
+2 -2
src/index.vto
··· 81 81 - url: "components/orchestrator/queue-tracks/element.js" 82 82 title: "Queue ⭤ Tracks" 83 83 desc: "Sets the given queue element pool whenever the tracks signal from the given output changes." 84 - - title: "Repeat & Shuffle" 84 + - url: "components/orchestrator/repeat-shuffle/element.js" 85 + title: "Repeat & Shuffle" 85 86 desc: "An opinionated way to setup repeat & shuffle." 86 - todo: true 87 87 - url: "components/orchestrator/search-tracks/element.js" 88 88 title: "Search ⭤ Tracks" 89 89 desc: "Supplies tracks to the given search processor whenever the tracks collection changes."
+14
src/themes/blur/artwork-controller/index.vto
··· 24 24 25 25 <script type="module"> 26 26 import { config } from "./common/constituents/default.js" 27 + import { effect } from "./common/signal.js" 28 + 27 29 import QueueAudioOrchestrator from "./components/orchestrator/queue-audio/element.js"; 28 30 29 31 import "./components/engine/audio/element.js" ··· 43 45 oqa.setAttribute("queue-engine-selector", defaults.engine.queue.localName); 44 46 45 47 document.body.append(oqa) 48 + 49 + // Effects 50 + effect(() => { 51 + const rso = defaults.orchestrator.repeatShuffle 52 + const repeat = rso.repeat() 53 + 54 + if (repeat && !oqa.hasAttribute("repeat")) { 55 + oqa.toggleAttribute("repeat") 56 + } else if (!repeat && oqa.hasAttribute("repeat")) { 57 + oqa.removeAttribute("repeat") 58 + } 59 + }) 46 60 </script>