Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

feat: queue-audio grouping

+35 -133
+9 -82
src/pages/constituents/blur/artwork-controller/_applet.astro
··· 77 77 78 78 .controller menu { 79 79 display: flex; 80 - font-size: 70%; 80 + font-size: var(--fs-2xs); 81 81 gap: var(--space-sm); 82 82 margin: var(--space-md) 0; 83 83 padding: 0; ··· 203 203 <script> 204 204 import { FastAverageColor } from "fast-average-color"; 205 205 206 - import { computed, effect, type Signal, signal } from "spellcaster"; 207 - import { repeat, tags, text, type ElementConfigurator } from "spellcaster/hyperscript.js"; 206 + import { computed, effect, type Signal, signal, throttled } from "spellcaster"; 207 + import { tags, text, type ElementConfigurator } from "spellcaster/hyperscript.js"; 208 208 209 209 import type { ManagedOutput, Track } from "@applets/core/types"; 210 210 import { ··· 215 215 reactive, 216 216 register, 217 217 trackArtworkCacheId, 218 - wait, 219 218 } from "@scripts/applets/common"; 220 - import { arrayShuffle } from "@scripts/common"; 221 219 import scope from "astro:scope"; 222 220 223 221 //////////////////////////////////////////// ··· 249 247 queue: await applet<QueueEngine.State>("../../engine/queue", { groupId: groupId() }), 250 248 }; 251 249 252 - const orchestrator = { 250 + const _orchestrator = { 253 251 inputCache: await applet("../../../orchestrator/input-cache"), 252 + queueAudioTracks: await applet("../../../orchestrator/queue-audio-tracks", { 253 + groupId: groupId(), 254 + }), 254 255 }; 255 256 256 257 const processor = { ··· 264 265 reactive( 265 266 engine.audio, 266 267 (data) => data.items[engine.queue.data.now?.id ?? Infinity]?.isPlaying ?? false, 267 - (isPlaying) => setIsPlaying(isPlaying), 268 + (isPlaying) => throttled(() => setIsPlaying(isPlaying))(), 268 269 ); 269 270 270 271 reactive( 271 272 engine.audio, 272 273 (data) => data.items[engine.queue.data.now?.id ?? Infinity]?.progress ?? 0, 273 - (progress: number) => setProgress(progress), 274 + setProgress, 274 275 ); 275 276 276 277 //////////////////////////////////////////// 277 278 // 🎢 QUEUE 278 279 //////////////////////////////////////////// 279 280 280 - // TODO: Shuffle, limit amount, etc. 281 - async function fillQueue() { 282 - await engine.queue.sendAction("add", arrayShuffle(configurator.output.data.tracks.collection), { 283 - timeoutDuration: 60000, 284 - }); 285 - } 286 - 287 - // When the active audio has ended, 288 - // shift the queue. 289 - 290 - // NOTE: 291 - // This could probably be optimised, but it works. 292 - 293 - reactive( 294 - engine.audio, 295 - (data) => data.items[engine.queue.data.now?.id ?? Infinity]?.hasEnded ?? false, 296 - (hasEnded) => { 297 - if (hasEnded) engine.queue.sendAction("shift"); 298 - }, 299 - ); 300 - 301 - // When the active queue item has changed, 302 - // coordinate the audio engine accordingly. 303 - 304 - reactive( 305 - engine.queue, 306 - (data) => data.now?.id, 307 - async () => { 308 - const playingNow = engine.queue.data.now; 309 - const volume = engine.audio.data.volume; 310 - 311 - // Play new active queue item 312 - // TODO: Take URL expiration timestamp into account 313 - // TODO: Preload next queue item 314 - engine.audio.sendAction( 315 - "render", 316 - { 317 - audio: playingNow 318 - ? [ 319 - { 320 - id: playingNow.id, 321 - isPreload: false, 322 - url: await inputUrl(configurator.input, playingNow.uri).then((a) => a?.url), 323 - }, 324 - ] 325 - : // NOTE: This probably isn't correct, keep preloads? 326 - [], 327 - 328 - // TODO: Only play if currently playing, otherwise keep paused. 329 - play: playingNow 330 - ? { 331 - audioId: playingNow.id, 332 - volume, 333 - } 334 - : undefined, 335 - }, 336 - { 337 - timeoutDuration: 60000, 338 - }, 339 - ); 340 - 341 - // Add more tracks to the queue if needed 342 - if (playingNow) fillQueue(); 343 - }, 344 - ); 345 - 346 - // Add tracks to the queue once the tracks have been loaded. 347 - 348 - wait(configurator.output, (d) => d?.tracks.state === "loaded").then(() => { 349 - reactive(configurator.output, (d) => d.tracks.cacheId, fillQueue); 350 - }); 351 - 352 281 // React to active queue item. 353 282 354 283 reactive( ··· 462 391 463 392 function playPause() { 464 393 const audioId = engine.queue.data.now?.id; 465 - 466 - console.log(isPlaying(), audioId); 467 394 468 395 if (isPlaying() && audioId) { 469 396 engine.audio.sendAction("pause", { audioId });
+1 -1
src/pages/index.astro
··· 47 47 48 48 const orchestrators = [ 49 49 { url: "orchestrator/input-cache/", title: "Input caching" }, 50 - { url: "orchestrator/single-queue/", title: "Single queue" }, 50 + { url: "orchestrator/queue-audio-tracks/", title: "Queue ⭤ Audio ⭤ Tracks" }, 51 51 ]; 52 52 53 53 const output = [
+6
src/pages/orchestrator/queue-audio-tracks/_manifest.json
··· 1 + { 2 + "name": "diffuse/orchestrator/queue-audio-tracks", 3 + "title": "Diffuse Orchestrator | Queue Audio Tracks", 4 + "entrypoint": "index.html", 5 + "actions": {} 6 + }
+10 -14
src/pages/orchestrator/single-queue/_applet.astro src/pages/orchestrator/queue-audio-tracks/_applet.astro
··· 1 1 <script> 2 - import type { ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 2 + import type { ManagedOutput } from "@applets/core/types.d.ts"; 3 3 import { applet, inputUrl, reactive, register, wait } from "@scripts/applets/common"; 4 4 5 5 //////////////////////////////////////////// ··· 26 26 //////////////////////////////////////////// 27 27 // ACTIONS 28 28 //////////////////////////////////////////// 29 - context.setActionHandler("fill", fill); 30 29 31 30 // TODO: Shuffle, limit track amount, etc. 32 - async function fill(tracks: Track[]) { 31 + async function fill() { 32 + const tracks = configurator.output.data.tracks.collection; 33 + 33 34 await engine.queue.sendAction("add", tracks, { 34 35 timeoutDuration: 60000, 35 36 }); ··· 94 95 { 95 96 id: playingNow.id, 96 97 isPreload: false, 97 - url: await inputUrl(configurator.input, playingNow.uri), 98 + url: await inputUrl(configurator.input, playingNow.uri).then((a) => a?.url), 98 99 }, 99 100 ] 100 101 : // NOTE: This probably isn't correct, keep preloads? ··· 112 113 ); 113 114 114 115 // Add more tracks to the queue if needed 115 - if (playingNow) fill(configurator.output.data.tracks.collection); 116 + if (playingNow) fill(); 116 117 }, 117 118 ); 118 119 119 120 //////////////////////////////////////////// 120 - // 🎻 [Connections → Configurators] 121 + // 🏕️ [Connections → Output] 121 122 // 📦 OUTPUT 122 123 //////////////////////////////////////////// 123 124 124 - // Add tracks to the queue once the tracks have been loaded. 125 + // Add tracks to the queue once the tracks have been loaded; 126 + // and every time the collection changes. 125 127 126 128 wait(configurator.output, (d) => d?.tracks.state === "loaded").then(() => { 127 - connect( 128 - configurator.output, 129 - (data) => data.tracks.cacheId, 130 - () => { 131 - fill(configurator.output.data.tracks.collection); 132 - }, 133 - ); 129 + connect(configurator.output, (data) => data.tracks.cacheId, fill); 134 130 }); 135 131 </script>
-16
src/pages/orchestrator/single-queue/_manifest.json
··· 1 - { 2 - "name": "diffuse/orchestrator/single-queue", 3 - "title": "Diffuse Orchestrator | Single queue", 4 - "entrypoint": "index.html", 5 - "actions": { 6 - "fill": { 7 - "title": "Fill", 8 - "description": "Fill up the queue.", 9 - "params_schema": { 10 - "type": "array", 11 - "items": { "type": "object" }, 12 - "description": "Array of tracks to be used to fill up the queue." 13 - } 14 - } 15 - } 16 - }
src/pages/orchestrator/single-queue/index.astro src/pages/orchestrator/queue-audio-tracks/index.astro
-14
src/scripts/themes/blur/index.ts
··· 8 8 //////////////////////////////////////////// 9 9 const ui = {}; 10 10 11 - //////////////////////////////////////////// 12 - // ⚙️ [Connections → Engines] 13 - // 🔉 AUDIO 14 - //////////////////////////////////////////// 15 - 16 - // TODO 17 - 18 - //////////////////////////////////////////// 19 - // 🌅 [Connections → UI] 20 - // 🔉 AUDIO 21 - //////////////////////////////////////////// 22 - 23 - // TODO 24 - 25 11 // TESTING 26 12 27 13 // wait(configurator.output, (data) => data?.tracks?.state === "loaded").then(async () => {
+6 -6
src/scripts/themes/pilot/index.ts
··· 20 20 21 21 const orchestrator = { 22 22 input: await applet("../../orchestrator/input-cache"), 23 - queue: await applet("../../orchestrator/single-queue"), 23 + queue: await applet("../../orchestrator/queue-audio-tracks"), 24 24 }; 25 25 26 26 const ui = { ··· 56 56 ui.audio, 57 57 (data) => data.isPlaying, 58 58 async (isPlaying, setter) => { 59 - const trackId = engine.queue.data.now?.id; 59 + const audioId = engine.queue.data.now?.id; 60 60 const volume = engine.audio.data.volume; 61 61 62 62 // Automatically start playing something if nothing is playing yet. 63 - if (!trackId) { 63 + if (!audioId) { 64 64 if (isPlaying) { 65 65 const now = await engine.queue.sendAction("shift"); 66 66 if (!now) { ··· 74 74 75 75 // Otherwise just control the audio 76 76 if (isPlaying) { 77 - engine.audio.sendAction("play", { trackId, volume }); 77 + engine.audio.sendAction("play", { audioId, volume }); 78 78 } else { 79 - engine.audio.sendAction("pause", { trackId }); 79 + engine.audio.sendAction("pause", { audioId }); 80 80 } 81 81 }, 82 82 ); ··· 88 88 if (seekPosition !== undefined && engine.queue.data.now?.id) { 89 89 engine.audio.sendAction("seek", { 90 90 percentage: seekPosition, 91 - trackId: engine.queue.data.now.id, 91 + audioId: engine.queue.data.now.id, 92 92 }); 93 93 } 94 94 },
+3
src/styles/variables.css
··· 1 1 :root { 2 2 /* Font scales */ 3 + --fs-3xs: clamp(0.41rem, -0.06vi + 0.42rem, 0.38rem); 4 + --fs-2xs: clamp(0.51rem, -0.02vi + 0.52rem, 0.5rem); 5 + --fs-xs: clamp(0.64rem, 0.05vi + 0.63rem, 0.67rem); 3 6 --fs-sm: clamp(0.8rem, 0.17vi + 0.76rem, 0.89rem); 4 7 --fs-base: clamp(1rem, 0.34vi + 0.91rem, 1.19rem); 5 8 --fs-md: clamp(1.25rem, 0.61vi + 1.1rem, 1.58rem);