Experiment to rebuild Diffuse using web applets.
1import type { URLTrack } from "webamp";
2import Webamp from "webamp";
3
4import type { 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: await applet("/configurator/input"),
12 output: await applet<ManagedOutput>("/configurator/output"),
13};
14
15const orchestrator = {
16 input: await applet("/orchestrator/input-cache"),
17};
18
19////////////////////////////////////////////
20// ⚡
21////////////////////////////////////////////
22const amp = new Webamp({
23 initialTracks: [],
24});
25
26// Override
27const loadFromUrl = amp.media.loadFromUrl.bind(amp.media);
28
29async function loadOverride(uri: string, autoPlay: boolean) {
30 const resp = await inputUrl(configurator.input, uri);
31 if (!resp) throw new Error("Failed to resolve URI");
32 return await loadFromUrl(resp.url, autoPlay);
33}
34
35amp.media.loadFromUrl = loadOverride.bind(amp.media);
36
37// Render
38const ampNode = document.createElement("div");
39ampNode.style = "height: 100vh; left: 0; position: absolute; top: 0; width: 100%; z-index: -1000;";
40document.body.appendChild(ampNode);
41amp.renderWhenReady(ampNode);
42
43// Wait for tracks to load
44wait(configurator.output, (d) => d?.tracks.state === "loaded").then(loadAndInsert);
45configurator.output.ondata = loadAndInsert;
46
47// Load & insert
48let inserting = false;
49
50async function loadAndInsert() {
51 if (configurator.output.data.tracks.state !== "loaded") return;
52 if (inserting) return;
53 inserting = true;
54 const tracks = await loadTracks();
55
56 // TODO: This kinda messes up the UI,
57 // but at least the active audio doesn't stop playing.
58 amp.store.dispatch({ type: "REMOVE_ALL_TRACKS" });
59
60 // TODO: Webamp blows up if you add too much tracks
61 amp.appendTracks(tracks.slice(0, 1000));
62
63 const status = amp.getMediaStatus();
64 if (status !== "PLAYING") amp.nextTrack();
65
66 inserting = false;
67}
68
69////////////////////////////////////////////
70// 🛠️
71////////////////////////////////////////////
72async function loadTracks(): Promise<URLTrack[]> {
73 const tracks = configurator.output.data.tracks.collection;
74 return tracks.map((track) => {
75 const urlTrack: URLTrack = {
76 url: track.uri,
77 metaData: {
78 title: track.tags?.title || "",
79 artist: track.tags?.artist || "",
80 album: track.tags?.album,
81 },
82 duration: track.stats?.duration,
83 };
84
85 return urlTrack;
86 });
87}