Experiment to rebuild Diffuse using web applets.
0

Configure Feed

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

at main 3.3 kB View raw
1import type { URLTrack } from "webamp"; 2import Webamp from "webamp"; 3 4import type { GroupConsult, ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 5import { applet, inputUrl, wait } from "@scripts/applet/common"; 6 7//////////////////////////////////////////// 8// 🗂️ Applets 9//////////////////////////////////////////// 10const configurator = { 11 input: applet("/configurator/input"), 12 output: applet<ManagedOutput>("/configurator/output"), 13}; 14 15const orchestrator = { 16 queueAudio: applet("/orchestrator/queue-audio"), 17 queueTracks: applet("/orchestrator/queue-tracks"), 18 processTracks: applet("/orchestrator/process-tracks"), 19}; 20 21//////////////////////////////////////////// 22// ⚡ 23//////////////////////////////////////////// 24const amp = new Webamp({ 25 initialTracks: [], 26}); 27 28// Override 29const loadFromUrl = amp.media.loadFromUrl.bind(amp.media); 30 31async function loadOverride(uri: string, autoPlay: boolean) { 32 const resp = await inputUrl(await configurator.input, uri); 33 if (!resp) throw new Error("Failed to resolve URI"); 34 return await loadFromUrl(resp.url, autoPlay); 35} 36 37amp.media.loadFromUrl = loadOverride.bind(amp.media); 38 39// Render 40const ampNode = document.createElement("div"); 41ampNode.style = "height: 100vh; left: 0; position: absolute; top: 0; width: 100%; z-index: -1000;"; 42document.body.appendChild(ampNode); 43amp.renderWhenReady(ampNode); 44 45// Wait for tracks to load 46configurator.output 47 .then((output) => { 48 output.ondata = loadAndInsert; 49 return wait(output, (d) => d?.tracks.state === "loaded"); 50 }) 51 .then(async () => { 52 await loadAndInsert(); 53 }); 54 55// Load & insert 56let inserting = false; 57let tracksCacheId: string | undefined = undefined; 58 59async function loadAndInsert() { 60 const output = await configurator.output; 61 62 if (output.data.tracks.state !== "loaded") return; 63 if (output.data.tracks.cacheId === tracksCacheId) return; 64 if (inserting) return; 65 66 inserting = true; 67 tracksCacheId = output.data.tracks.cacheId; 68 const tracks = await loadTracks(); 69 70 // TODO: This kinda messes up the UI, 71 // but at least the active audio doesn't stop playing. 72 amp.store.dispatch({ type: "REMOVE_ALL_TRACKS" }); 73 74 // TODO: Webamp blows up if you add too much tracks 75 amp.appendTracks(tracks.slice(0, 1000)); 76 77 const status = amp.getMediaStatus(); 78 if (status !== "PLAYING") amp.nextTrack(); 79 80 inserting = false; 81} 82 83//////////////////////////////////////////// 84// 🛠️ 85//////////////////////////////////////////// 86async function loadTracks(): Promise<URLTrack[]> { 87 const input = await configurator.input; 88 const output = await configurator.output; 89 90 const groups = await input.sendAction<GroupConsult>( 91 "groupConsult", 92 output.data.tracks.collection, 93 { timeoutDuration: 60000 * 5, worker: true }, 94 ); 95 96 // Available tracks 97 let tracks: Track[] = []; 98 99 Object.values(groups).forEach((value) => { 100 if (value.available === false) return; 101 tracks = tracks.concat(value.tracks); 102 }, []); 103 104 return tracks.map((track) => { 105 const urlTrack: URLTrack = { 106 url: track.uri, 107 metaData: { 108 title: track.tags?.title || "", 109 artist: track.tags?.artist || "", 110 album: track.tags?.album, 111 }, 112 duration: track.stats?.duration, 113 }; 114 115 return urlTrack; 116 }); 117}