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, wait } from "@scripts/applets/common";
6
7////////////////////////////////////////////
8// 🎨 Styles
9////////////////////////////////////////////
10import "@styles/themes/webamp/index.css";
11
12////////////////////////////////////////////
13// 🗂️ Applets
14////////////////////////////////////////////
15const configurator = {
16 input: await applet("../../configurator/input"),
17 output: await applet<ManagedOutput>("../../configurator/output"),
18};
19
20const orchestrator = {
21 input: await applet("../../orchestrator/input-cache"),
22};
23
24////////////////////////////////////////////
25// ⚡
26////////////////////////////////////////////
27const amp = new Webamp({
28 initialTracks: [],
29});
30
31const ampNode = document.createElement("div");
32ampNode.style = "height: 100vh; left: 0; position: absolute; top: 0; width: 100%; z-index: -1000;";
33document.body.appendChild(ampNode);
34amp.renderWhenReady(ampNode);
35
36wait(configurator.output, (d) => d?.tracks.state === "loaded").then(loadAndInsert);
37configurator.output.ondata = loadAndInsert;
38
39let inserting = false;
40
41async function loadAndInsert() {
42 if (configurator.output.data.tracks.state !== "loaded") return;
43 if (inserting) return;
44 inserting = true;
45 const tracks = await loadTracks();
46 amp.setTracksToPlay([]);
47 amp.appendTracks(tracks);
48 amp.nextTrack();
49 inserting = false;
50}
51
52////////////////////////////////////////////
53// 🛠️
54////////////////////////////////////////////
55async function loadTracks(): Promise<URLTrack[]> {
56 return await configurator.output.data.tracks.collection.reduce(
57 async (promise: Promise<URLTrack[]>, track: Track) => {
58 const acc = await promise;
59
60 // TODO: Ideally the URL should only be resolved when needed,
61 // but webamp doesn't allow for that.
62 // Maybe you could work around it with a service worker.
63 const resGet = await configurator.input.sendAction<ResolvedUri>(
64 "resolve",
65 { method: "GET", uri: track.uri },
66 {
67 timeoutDuration: 60000 * 5,
68 },
69 );
70
71 if (!resGet) return acc;
72
73 const urlTrack: URLTrack = {
74 url: resGet.url,
75 metaData: {
76 title: track.tags?.title || "",
77 artist: track.tags?.artist || "",
78 album: track.tags?.album,
79 },
80 duration: track.stats?.duration,
81 };
82
83 return [...acc, urlTrack];
84 },
85 Promise.resolve([]),
86 );
87}