Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

fix: pilot theme and undo orchestrator changes

+371 -353
+9 -66
src/pages/constituent/blur/artwork-controller/_applet.astro
··· 383 383 }; 384 384 385 385 const orchestrator = { 386 - primary: applet("/orchestrator/primary", { 387 - groupId: context.groupId, 388 - }), 386 + queueAudio: applet("/orchestrator/queue-audio", { groupId: context.groupId }), 387 + queueTracks: isMainGroup() 388 + ? applet("/orchestrator/queue-tracks", { groupId: context.groupId }) 389 + : undefined, 390 + processTracks: isMainGroup() ? applet("/orchestrator/process-tracks") : undefined, 389 391 }; 390 392 391 393 const processor = { ··· 400 402 const prog = progress(); 401 403 const curr = engine.queue.data.now; 402 404 const audio = curr ? engine.audio.data.items[curr.id] : undefined; 405 + const duration = curr?.stats?.duration ?? audio?.duration; 403 406 404 - if (audio) { 407 + if (audio && duration != undefined && !isNaN(duration)) { 405 408 const p = Temporal.Duration.from({ 406 - milliseconds: Math.round(audio.duration * prog * 1000), 409 + milliseconds: Math.round(duration * prog * 1000), 407 410 }).round({ 408 411 largestUnit: "hours", 409 412 }); 410 413 411 - const d = Temporal.Duration.from({ milliseconds: Math.round(audio.duration * 1000) }).round({ 414 + const d = Temporal.Duration.from({ milliseconds: Math.round(duration * 1000) }).round({ 412 415 largestUnit: "hours", 413 416 }); 414 417 ··· 462 465 (volume) => setVolume(volume), 463 466 ); 464 467 465 - // ORCHESTRATED 466 - 467 - context.settled().then(async () => { 468 - if (context.isMainInstance()) monitorAudioStuff(); 469 - }); 470 - 471 - async function monitorAudioStuff() { 472 - (await orchestrator.primary).sendAction("monitorAudioEnd", undefined, { 473 - timeoutDuration: 60000, 474 - }); 475 - } 476 - 477 468 //////////////////////////////////////////// 478 469 // 🎢 QUEUE 479 470 //////////////////////////////////////////// ··· 527 518 const currCacheId = currTrack ? await trackArtworkCacheId(currTrack) : undefined; 528 519 if (cacheId === currCacheId) setArtwork(art); 529 520 } 530 - 531 - // ORCHESTRATED 532 - 533 - context.settled().then(async () => { 534 - if (context.isMainInstance()) monitorQueueStuff(); 535 - }); 536 - 537 - async function monitorQueueStuff() { 538 - (await orchestrator.primary).sendAction("monitorActiveQueueItem", undefined, { 539 - timeoutDuration: 60000, 540 - }); 541 - } 542 - 543 - //////////////////////////////////////////// 544 - // TRACKS 545 - //////////////////////////////////////////// 546 - 547 - // ORCHESTRATED 548 - 549 - context.settled().then(() => { 550 - if (isMainGroup() && context.isMainInstance()) { 551 - return monitorTracksStuff().then(processInputs); 552 - } 553 - }); 554 - 555 - async function monitorTracksStuff() { 556 - (await orchestrator.primary).sendAction("monitorTracks", undefined, { 557 - timeoutDuration: 60000 * 5, 558 - }); 559 - } 560 - 561 - async function processInputs() { 562 - (await orchestrator.primary).sendAction("processInputs", undefined, { 563 - timeoutDuration: 60000 * 60, 564 - }); 565 - } 566 - 567 - //////////////////////////////////////////// 568 - // 🚛 HYDRATE 569 - //////////////////////////////////////////// 570 - 571 - context.unloadHandler = async () => { 572 - if (context.isMainInstance()) { 573 - monitorAudioStuff(); 574 - monitorQueueStuff(); 575 - if (isMainGroup()) monitorTracksStuff(); 576 - } 577 - }; 578 521 579 522 //////////////////////////////////////////// 580 523 // UI
+3 -1
src/pages/engine/audio/_applet.astro
··· 14 14 // SETUP 15 15 //////////////////////////////////////////// 16 16 const context = register<State>(); 17 + const groupId = context.groupId ?? "main"; 17 18 18 19 // Audio elements container 19 20 const container = document.createElement("div"); ··· 21 22 document.body.appendChild(container); 22 23 23 24 // Default volume 24 - const VOLUME_KEY = `@applets/engine/audio/${context.groupId || "main"}/volume`; 25 + const VOLUME_KEY = `@applets/engine/audio/${groupId}/volume`; 25 26 const vol = localStorage.getItem(VOLUME_KEY); 26 27 27 28 // Initial state ··· 63 64 64 65 // Unload 65 66 context.unloadHandler = async () => { 67 + console.log("UNLOADED"); 66 68 await context.settled(); 67 69 hydrateItems(); 68 70 };
+6 -2
src/pages/index.astro
··· 46 46 { url: "input/s3/", title: "S3-Compatible API" }, 47 47 ]; 48 48 49 - const orchestrators = [{ url: "orchestrator/primary/", title: "Primary (Queue, audio, tracks)" }]; 49 + const orchestrators = [ 50 + { url: "orchestrator/queue-audio/", title: "Queue ⭤ Audio" }, 51 + { url: "orchestrator/queue-tracks/", title: "Queue ⭤ Tracks" }, 52 + { url: "orchestrator/process-tracks/", title: "Process inputs into tracks" }, 53 + ]; 50 54 51 55 const output = [ 52 56 { url: "output/indexed-db/", title: "IndexedDB" }, ··· 145 149 <p> 146 150 There's tradeoffs to both approaches. A particular tradeoff to keep in mind for constituents 147 151 is that they'll have nested dependencies. So when overriding applets dependencies, the 148 - overrides need to passed down the tree. 152 + overrides need to be passed down the tree. 149 153 </p> 150 154 151 155 <List items={constituents} />
-237
src/pages/orchestrator/primary/_applet.astro
··· 1 - <script> 2 - import type { Applet } from "@web-applets/sdk"; 3 - import type { GroupConsult, ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 4 - import { applet, inputUrl, reactive, register, wait } from "@scripts/applet/common"; 5 - import { tracksCacheId } from "@scripts/output/common"; 6 - 7 - //////////////////////////////////////////// 8 - // SETUP 9 - //////////////////////////////////////////// 10 - import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 11 - import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 12 - 13 - const context = register<{ isProcessing: boolean }>(); 14 - 15 - // Initial data 16 - context.data = { 17 - isProcessing: false, 18 - }; 19 - 20 - // Applet connections 21 - const configurator = { 22 - input: applet("/configurator/input"), 23 - input_2: undefined as Applet | undefined, 24 - output: applet<ManagedOutput>("/configurator/output"), 25 - }; 26 - 27 - const engine = { 28 - audio: applet<AudioEngine.State>("/engine/audio", { groupId: context.groupId }), 29 - queue: applet<QueueEngine.State>("/engine/queue", { groupId: context.groupId }), 30 - }; 31 - 32 - const processor = { 33 - metadata: applet("/processor/metadata"), 34 - }; 35 - 36 - //////////////////////////////////////////// 37 - // [ACTIONS] AUDIO ⭤ QUEUE 38 - //////////////////////////////////////////// 39 - context.setActionHandler("monitorActiveQueueItem", monitorActiveQueueItem); 40 - context.setActionHandler("monitorAudioEnd", monitorAudioEnd); 41 - 42 - async function monitorActiveQueueItem() { 43 - await context.settled(); 44 - 45 - const audio = await engine.audio; 46 - const queue = await engine.queue; 47 - 48 - // When the active queue item has changed, 49 - // coordinate the audio engine accordingly. 50 - reactive( 51 - queue, 52 - (data) => data.now?.id, 53 - async () => { 54 - const activeTrack = queue.data.now; 55 - const isPlaying = audio.data.isPlaying; 56 - 57 - // Resolve URIs 58 - const url = activeTrack 59 - ? await inputUrl(await configurator.input, activeTrack.uri).then((a) => a?.url) 60 - : undefined; 61 - 62 - // Check if we still need to render 63 - if (queue.data.now?.id !== activeTrack?.id) return; 64 - 65 - // Play new active queue item 66 - // TODO: Take URL expiration timestamp into account 67 - // TODO: Preload next queue item 68 - audio.sendAction( 69 - "render", 70 - { 71 - audio: activeTrack 72 - ? [ 73 - { 74 - id: activeTrack.id, 75 - isPreload: false, 76 - url, 77 - }, 78 - ] 79 - : // TODO: Keep preloads 80 - [], 81 - play: activeTrack && isPlaying ? { audioId: activeTrack.id } : undefined, 82 - }, 83 - { 84 - timeoutDuration: 60000, 85 - }, 86 - ); 87 - }, 88 - ); 89 - } 90 - 91 - async function monitorAudioEnd() { 92 - const audio = await engine.audio; 93 - const queue = await engine.queue; 94 - 95 - // When the active audio has ended, 96 - // shift the queue. 97 - reactive( 98 - audio, 99 - (data) => data.items[queue.data.now?.id ?? Infinity]?.hasEnded ?? false, 100 - (hasEnded) => { 101 - if (hasEnded) queue.sendAction("shift", { groupId: context.groupId }, { worker: true }); 102 - }, 103 - ); 104 - } 105 - 106 - //////////////////////////////////////////// 107 - // [ACTIONS] PROCESS 108 - //////////////////////////////////////////// 109 - context.setActionHandler("processInputs", processInputs); 110 - 111 - async function processInputs() { 112 - if (context.data.isProcessing) return; 113 - context.data = { ...context.data, isProcessing: true }; 114 - console.log("🪵 Processing initiated"); 115 - 116 - const input = configurator.input_2 117 - ? configurator.input_2 118 - : await applet("/configurator/input", { context: self, newInstance: true }); 119 - 120 - if (!configurator.input_2) configurator.input_2 = input; 121 - 122 - const output = await configurator.output; 123 - const cachedTracks = output.data.tracks.collection; 124 - 125 - await input.sendAction("contextualize", cachedTracks, { 126 - timeoutDuration: 60000 * 5, 127 - worker: true, 128 - }); 129 - 130 - const tracks = await input.sendAction<Track[]>("list", cachedTracks, { 131 - timeoutDuration: 60000 * 60 * 24, 132 - worker: true, 133 - }); 134 - 135 - // Process 136 - const tracksWithMetadata = await tracks.reduce( 137 - async (promise: Promise<Track[]>, track: Track) => { 138 - const acc = await promise; 139 - 140 - if (track.tags && track.stats) return [...acc, track]; 141 - 142 - const resGet = await input.sendAction<ResolvedUri>( 143 - "resolve", 144 - { method: "GET", uri: track.uri }, 145 - { 146 - timeoutDuration: 60000 * 5, 147 - worker: true, 148 - }, 149 - ); 150 - 151 - const resHead = await input.sendAction<ResolvedUri>( 152 - "resolve", 153 - { method: "HEAD", uri: track.uri }, 154 - { 155 - timeoutDuration: 60000 * 5, 156 - worker: true, 157 - }, 158 - ); 159 - 160 - if (!resGet) return [...acc, track]; 161 - 162 - const metadataProcessor = await processor.metadata; 163 - const { stats, tags } = await metadataProcessor.sendAction( 164 - "supply", 165 - { urls: { get: resGet.url, head: resHead?.url || resGet.url } }, 166 - { 167 - timeoutDuration: 60000 * 15, 168 - worker: true, 169 - }, 170 - ); 171 - 172 - return [...acc, { ...track, stats, tags }]; 173 - }, 174 - Promise.resolve([]), 175 - ); 176 - 177 - // Save 178 - const changed = tracksCacheId(tracksWithMetadata) !== output.data.tracks.cacheId; 179 - 180 - if (changed) 181 - await output.sendAction("tracks", tracksWithMetadata, { 182 - timeoutDuration: 60000 * 5, 183 - }); 184 - 185 - // Fin 186 - console.log("🪵 Processing completed"); 187 - context.data = { ...context.data, isProcessing: false }; 188 - } 189 - 190 - //////////////////////////////////////////// 191 - // [ACTIONS] QUEUE ⭤ TRACKS 192 - //////////////////////////////////////////// 193 - context.setActionHandler("monitorTracks", monitorTracks); 194 - 195 - async function monitorTracks() { 196 - await context.settled(); 197 - 198 - // Add tracks to the queue once the tracks have been loaded; 199 - // and every time the collection changes. 200 - 201 - const input = await configurator.input; 202 - const output = await configurator.output; 203 - const queue = await engine.queue; 204 - 205 - await wait(output, (d) => d?.tracks.state === "loaded"); 206 - 207 - reactive( 208 - output, 209 - (data) => data.tracks.cacheId, 210 - async () => { 211 - const groups = await input.sendAction<GroupConsult>( 212 - "groupConsult", 213 - output.data.tracks.collection, 214 - { timeoutDuration: 60000 * 5, worker: true }, 215 - ); 216 - 217 - // Available tracks 218 - let tracks: Track[] = []; 219 - 220 - Object.values(groups).forEach((value) => { 221 - if (value.available === false) return; 222 - tracks = tracks.concat(value.tracks); 223 - }, []); 224 - 225 - // Set pool 226 - await queue.sendAction( 227 - "pool", 228 - { groupId: context.groupId, tracks }, 229 - { 230 - timeoutDuration: 60000, 231 - worker: true, 232 - }, 233 - ); 234 - }, 235 - ); 236 - } 237 - </script>
-6
src/pages/orchestrator/primary/_manifest.json
··· 1 - { 2 - "name": "diffuse/orchestrator/primary", 3 - "title": "Diffuse Orchestrator | Primary", 4 - "entrypoint": "index.html", 5 - "actions": {} 6 - }
src/pages/orchestrator/primary/index.astro src/pages/orchestrator/process-tracks/index.astro
+118
src/pages/orchestrator/process-tracks/_applet.astro
··· 1 + <script> 2 + import type { Applet } from "@web-applets/sdk"; 3 + import type { ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 4 + import { applet, register } from "@scripts/applet/common"; 5 + import { tracksCacheId } from "@scripts/output/common"; 6 + 7 + //////////////////////////////////////////// 8 + // SETUP 9 + //////////////////////////////////////////// 10 + const context = register<{ isProcessing: boolean }>(); 11 + 12 + // Initial data 13 + context.data = { 14 + isProcessing: false, 15 + }; 16 + 17 + // Applet connections 18 + const configurator = { 19 + input: applet("/configurator/input"), 20 + input_2: undefined as Applet | undefined, 21 + output: applet<ManagedOutput>("/configurator/output"), 22 + }; 23 + 24 + const processor = { 25 + metadata: applet("/processor/metadata"), 26 + }; 27 + 28 + //////////////////////////////////////////// 29 + // PROCESS 30 + //////////////////////////////////////////// 31 + context.setActionHandler("processInputs", processInputs); 32 + 33 + async function processInputs() { 34 + if (context.data.isProcessing) return; 35 + context.data = { ...context.data, isProcessing: true }; 36 + console.log("🪵 Processing initiated"); 37 + 38 + const input = configurator.input_2 39 + ? configurator.input_2 40 + : await applet("/configurator/input", { context: self, newInstance: true }); 41 + 42 + if (!configurator.input_2) configurator.input_2 = input; 43 + 44 + const output = await configurator.output; 45 + const cachedTracks = output.data.tracks.collection; 46 + 47 + await input.sendAction("contextualize", cachedTracks, { 48 + timeoutDuration: 60000 * 5, 49 + worker: true, 50 + }); 51 + 52 + const tracks = await input.sendAction<Track[]>("list", cachedTracks, { 53 + timeoutDuration: 60000 * 60 * 24, 54 + worker: true, 55 + }); 56 + 57 + // Process 58 + const tracksWithMetadata = await tracks.reduce( 59 + async (promise: Promise<Track[]>, track: Track) => { 60 + const acc = await promise; 61 + 62 + if (track.tags && track.stats) return [...acc, track]; 63 + 64 + const resGet = await input.sendAction<ResolvedUri>( 65 + "resolve", 66 + { method: "GET", uri: track.uri }, 67 + { 68 + timeoutDuration: 60000 * 5, 69 + worker: true, 70 + }, 71 + ); 72 + 73 + const resHead = await input.sendAction<ResolvedUri>( 74 + "resolve", 75 + { method: "HEAD", uri: track.uri }, 76 + { 77 + timeoutDuration: 60000 * 5, 78 + worker: true, 79 + }, 80 + ); 81 + 82 + if (!resGet) return [...acc, track]; 83 + 84 + const metadataProcessor = await processor.metadata; 85 + const { stats, tags } = await metadataProcessor.sendAction( 86 + "supply", 87 + { urls: { get: resGet.url, head: resHead?.url || resGet.url } }, 88 + { 89 + timeoutDuration: 60000 * 15, 90 + worker: true, 91 + }, 92 + ); 93 + 94 + return [...acc, { ...track, stats, tags }]; 95 + }, 96 + Promise.resolve([]), 97 + ); 98 + 99 + // Save 100 + const changed = tracksCacheId(tracksWithMetadata) !== output.data.tracks.cacheId; 101 + 102 + if (changed) 103 + await output.sendAction("tracks", tracksWithMetadata, { 104 + timeoutDuration: 60000 * 5, 105 + }); 106 + 107 + // Fin 108 + console.log("🪵 Processing completed"); 109 + context.data = { ...context.data, isProcessing: false }; 110 + } 111 + 112 + //////////////////////////////////////////// 113 + // 🚀 114 + //////////////////////////////////////////// 115 + context.settled().then(() => { 116 + // processInputs(); 117 + }); 118 + </script>
+6
src/pages/orchestrator/process-tracks/_manifest.json
··· 1 + { 2 + "name": "diffuse/orchestrator/process-tracks", 3 + "title": "Diffuse Orchestrator | Process Tracks", 4 + "entrypoint": "index.html", 5 + "actions": {} 6 + }
+96
src/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 (hasEnded) queue.sendAction("shift", { groupId: context.groupId }, { worker: true }); 85 + }, 86 + ); 87 + } 88 + 89 + //////////////////////////////////////////// 90 + // 🚀 91 + //////////////////////////////////////////// 92 + context.settled().then(() => { 93 + monitorActiveQueueItem(); 94 + monitorAudioEnd(); 95 + }); 96 + </script>
+6
src/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
src/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>
+78
src/pages/orchestrator/queue-tracks/_applet.astro
··· 1 + <script> 2 + import type { Applet } from "@web-applets/sdk"; 3 + import type { GroupConsult, ManagedOutput, Track } from "@applets/core/types.d.ts"; 4 + import { applet, reactive, register, wait } from "@scripts/applet/common"; 5 + 6 + //////////////////////////////////////////// 7 + // SETUP 8 + //////////////////////////////////////////// 9 + import type * as AudioEngine from "@applets/engine/audio/types.d.ts"; 10 + import type * as QueueEngine from "@applets/engine/queue/types.d.ts"; 11 + 12 + const context = register(); 13 + 14 + // Applet connections 15 + const configurator = { 16 + input: applet("/configurator/input"), 17 + input_2: undefined as Applet | undefined, 18 + output: applet<ManagedOutput>("/configurator/output"), 19 + }; 20 + 21 + const engine = { 22 + audio: applet<AudioEngine.State>("/engine/audio", { groupId: context.groupId }), 23 + queue: applet<QueueEngine.State>("/engine/queue", { groupId: context.groupId }), 24 + }; 25 + 26 + //////////////////////////////////////////// 27 + // QUEUE ⭤ TRACKS 28 + //////////////////////////////////////////// 29 + async function monitorTracks() { 30 + await context.settled(); 31 + 32 + // Add tracks to the queue once the tracks have been loaded; 33 + // and every time the collection changes. 34 + 35 + const input = await configurator.input; 36 + const output = await configurator.output; 37 + const queue = await engine.queue; 38 + 39 + await wait(output, (d) => d?.tracks.state === "loaded"); 40 + 41 + reactive( 42 + output, 43 + (data) => data.tracks.cacheId, 44 + async () => { 45 + const groups = await input.sendAction<GroupConsult>( 46 + "groupConsult", 47 + output.data.tracks.collection, 48 + { timeoutDuration: 60000 * 5, worker: true }, 49 + ); 50 + 51 + // Available tracks 52 + let tracks: Track[] = []; 53 + 54 + Object.values(groups).forEach((value) => { 55 + if (value.available === false) return; 56 + tracks = tracks.concat(value.tracks); 57 + }, []); 58 + 59 + // Set pool 60 + await queue.sendAction( 61 + "pool", 62 + { groupId: context.groupId, tracks }, 63 + { 64 + timeoutDuration: 60000, 65 + worker: true, 66 + }, 67 + ); 68 + }, 69 + ); 70 + } 71 + 72 + //////////////////////////////////////////// 73 + // 🚀 74 + //////////////////////////////////////////// 75 + context.settled().then(() => { 76 + monitorTracks(); 77 + }); 78 + </script>
+6
src/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
src/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>
-3
src/pages/theme/pilot/index.astro
··· 8 8 9 9 <!-- Temporary filler to push audio UI down to the bottom --> 10 10 <div class="filler" style="flex: 1;"></div> 11 - 12 - <!-- Theme applets --> 13 - <iframe id="applet__ui__audio" src="./audio/"></iframe> 14 11 </Page>
+8 -6
src/scripts/applet/common.ts
··· 33 33 }`; 34 34 35 35 let query: undefined | Record<string, string>; 36 - 37 - if (opts.groupId) { 38 - query = { groupId: opts.groupId }; 39 - } 36 + query = { groupId: opts.groupId || "main" }; 40 37 41 38 if (query) { 42 39 src = QS.stringifyUrl({ url: src, query }); ··· 238 235 originInstanceId: instanceId, 239 236 }); 240 237 241 - if (isMain()) { 238 + if (isMain() && event.data?.isInitialPing === true) { 242 239 channel.postMessage({ 243 240 type: "data", 244 241 data: context.codec.encode(scope.data), ··· 260 257 setTimeout(async () => { 261 258 const promised = await makeMainPromise(); 262 259 setIsMain(promised.isMain); 263 - context.unloadHandler?.(); 260 + if (promised.isMain) context.unloadHandler?.(); 264 261 }, 250); 265 262 } 266 263 break; ··· 286 283 }); 287 284 288 285 // Promise that fullfills whenever it figures out its the main instance or not. 286 + let pinged = false; 287 + 289 288 function makeMainPromise(timeoutDuration: number = 500) { 290 289 return new Promise<{ isMain: boolean }>((resolve) => { 291 290 const timeoutId = setTimeout(() => { ··· 308 307 channel.postMessage({ 309 308 type: "PING", 310 309 instanceId, 310 + isInitialPing: !pinged, 311 311 }); 312 + 313 + pinged = true; 312 314 }); 313 315 } 314 316
-16
src/scripts/orchestrator/primary/worker.ts
··· 1 - import { provide } from "@scripts/common"; 2 - 3 - //////////////////////////////////////////// 4 - // SETUP 5 - //////////////////////////////////////////// 6 - 7 - const actions = {}; 8 - 9 - const { tasks } = provide({ actions, tasks: actions }); 10 - 11 - export type Actions = typeof actions; 12 - export type Tasks = typeof tasks; 13 - 14 - //////////////////////////////////////////// 15 - // ACTIONS 16 - ////////////////////////////////////////////
+1
src/scripts/theme/blur/index.ts
··· 21 21 b: applet("/constituent/blur/artwork-controller", { container, groupId: labelB }), 22 22 }; 23 23 24 + // TODO: 24 25 // const _orchestrator = { 25 26 // primary: applet("/orchestrator/primary", { groupId: labelA }), 26 27 // };
+13 -12
src/scripts/theme/pilot/index.ts
··· 14 14 }; 15 15 16 16 const orchestrator = { 17 - primary: await applet("/orchestrator/primary"), 17 + queueAudio: applet("/orchestrator/queue-audio"), 18 + queueTracks: applet("/orchestrator/queue-tracks"), 19 + processTracks: applet("/orchestrator/process-tracks"), 18 20 }; 19 21 20 22 const ui = { ··· 47 49 // 🔉 AUDIO 48 50 //////////////////////////////////////////// 49 51 52 + let initialAudioChecked = false; 53 + 50 54 reactive( 51 55 ui.audio, 52 56 (data) => data.isPlaying, 53 - async (isPlaying, setter) => { 57 + async (isPlaying) => { 54 58 const audioId = engine.queue.data.now?.id; 55 59 56 - // Automatically start playing something if nothing is playing yet. 57 - if (!audioId) { 58 - if (isPlaying) { 59 - const now = await engine.queue.sendAction("shift", { groupId: "main" }); 60 - if (!now) { 61 - console.warn("No tracks available yet, try again later."); 62 - await ui.audio.sendAction("modifyIsPlaying", false); 63 - setter(false); 64 - } 60 + // Sync audio state and ui state 61 + // TODO: Figure out a better way to do this 62 + if (!initialAudioChecked) { 63 + if (engine.audio.data.isPlaying && !isPlaying) { 64 + ui.audio.sendAction("modifyIsPlaying", true); 65 + initialAudioChecked = true; 66 + return; 65 67 } 66 - return; 67 68 } 68 69 69 70 // Otherwise just control the audio
+3 -4
src/scripts/theme/webamp/index.ts
··· 13 13 }; 14 14 15 15 const orchestrator = { 16 - primary: applet("/orchestrator/primary"), 16 + queueAudio: applet("/orchestrator/queue-audio"), 17 + queueTracks: applet("/orchestrator/queue-tracks"), 18 + processTracks: applet("/orchestrator/process-tracks"), 17 19 }; 18 20 19 21 //////////////////////////////////////////// ··· 48 50 }) 49 51 .then(async () => { 50 52 await loadAndInsert(); 51 - (await orchestrator.primary).sendAction("process_inputs", undefined, { 52 - timeoutDuration: 60000 * 60, 53 - }); 54 53 }); 55 54 56 55 // Load & insert