alpha
Login
or
Join now
tokono.ma
/
diffuse-applets
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Experiment to rebuild Diffuse using web applets.
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
feat: hook up orchestrators to webamp
author
Steven Vandevelde
date
1 year ago
(May 23, 2025, 4:26 PM +0200)
commit
b94f83b4
b94f83b43e8d94cea67b6372564a716ec9550766
parent
56600d6e
56600d6ed9603ebef4501283a8a7eeb04c0db567
+126
-97
8 changed files
Expand all
Collapse all
Unified
Split
src
pages
configurator
input
_applet.astro
output
_applet.astro
input
native-fs
_applet.astro
orchestrator
input-cache
_applet.astro
output-management
_applet.astro
_manifest.json
types.d.ts
scripts
themes
webamp
index.ts
+19
-5
src/pages/configurator/input/_applet.astro
Reviewed
···
33
33
import { applets } from "@web-applets/sdk";
34
34
35
35
import type { Track } from "@applets/core/types.d.ts";
36
36
-
import { applet } from "@scripts/theme";
36
36
+
import { applet, waitUntilAppletIsReady } from "@scripts/theme";
37
37
38
38
////////////////////////////////////////////
39
39
// SETUP
40
40
////////////////////////////////////////////
41
41
const container = document.querySelector("#iframes") || undefined;
42
42
43
43
+
// Register applet
44
44
+
const context = applets.register<{ ready: boolean }>();
45
45
+
46
46
+
// Initial state
47
47
+
context.data = {
48
48
+
ready: false,
49
49
+
};
50
50
+
43
51
// Applet connections
44
52
const input = {
45
53
nativeFs: await applet("../../input/native-fs", { container }),
46
54
};
47
55
48
48
-
// Register applet
49
49
-
const context = applets.register<Track[]>();
50
50
-
51
56
////////////////////////////////////////////
52
57
// ACTIONS
53
58
////////////////////////////////////////////
54
59
55
60
const list = async (cachedTracks: Track[] = []) => {
61
61
+
await waitUntilAppletIsReady(input.nativeFs);
62
62
+
56
63
const groups = cachedTracks.reduce(
57
64
(acc: Record<string, Track[]>, track: Track) => {
58
65
const scheme = track.uri.split(":", 1)[0];
···
67
74
async ([scheme, cachedTracksGroup]: [string, Track[]]) => {
68
75
switch (scheme) {
69
76
case input.nativeFs.manifest.input_properties.scheme:
70
70
-
return await input.nativeFs.sendAction("list", cachedTracksGroup);
77
77
+
return await input.nativeFs.sendAction("list", cachedTracksGroup, {
78
78
+
timeoutDuration: 60000 * 60 * 24,
79
79
+
});
71
80
72
81
default:
73
82
return cachedTracks;
···
95
104
96
105
context.setActionHandler("list", list);
97
106
context.setActionHandler("resolve", resolve);
107
107
+
108
108
+
////////////////////////////////////////////
109
109
+
// 🚦
110
110
+
////////////////////////////////////////////
111
111
+
context.data = { ready: true };
98
112
</script>
+1
-4
src/pages/configurator/output/_applet.astro
Reviewed
···
65
65
////////////////////////////////////////////
66
66
const context = applets.register<{ ready: boolean }>();
67
67
68
68
+
// Applets container
68
69
const container = document.createElement("div");
69
70
container.id = "iframes";
70
71
document.body.appendChild(container);
···
338
339
const get: OutputGetter = async (args) => {
339
340
let data: Uint8Array | undefined;
340
341
341
341
-
console.log("------>", active());
342
342
-
343
342
switch (active()) {
344
343
case "browser": {
345
344
return await storage.output.indexedDB.sendAction<Uint8Array | undefined>("get", args);
···
370
369
break;
371
370
}
372
371
};
373
373
-
374
374
-
console.log("BIND GET");
375
372
376
373
context.setActionHandler("get", get);
377
374
context.setActionHandler("put", put);
+7
-1
src/pages/input/native-fs/_applet.astro
Reviewed
···
41
41
const IDB_HANDLES = `${IDB_PREFIX}/handles`;
42
42
const SCHEME = manifest.input_properties.scheme;
43
43
44
44
-
const context = applets.register();
44
44
+
// Register applet
45
45
+
const context = applets.register<{ ready: boolean }>();
45
46
46
47
////////////////////////////////////////////
47
48
// UI
···
286
287
287
288
return tracks;
288
289
}
290
290
+
291
291
+
////////////////////////////////////////////
292
292
+
// 🚦
293
293
+
////////////////////////////////////////////
294
294
+
context.data = { ready: true };
289
295
</script>
+53
-37
src/pages/orchestrator/input-cache/_applet.astro
Reviewed
···
2
2
import { applets } from "@web-applets/sdk";
3
3
4
4
import type { Output, Track, TrackStats, TrackTags } from "@applets/core/types.d.ts";
5
5
-
import { applet } from "@scripts/theme";
5
5
+
import { applet, waitUntilAppletIsReady } from "@scripts/theme";
6
6
7
7
////////////////////////////////////////////
8
8
// SETUP
9
9
////////////////////////////////////////////
10
10
-
// Register applet
11
11
-
const context = applets.register<Output>();
10
10
+
const context = applets.register<{ ready: boolean }>();
11
11
+
12
12
+
// Initial data
13
13
+
context.data = {
14
14
+
ready: false,
15
15
+
};
12
16
13
17
// Applet connections
14
18
const configurator = {
···
16
20
};
17
21
18
22
const orchestrator = {
19
19
-
output: await applet<Output>("../../orchestrator/output-management", { context: self.parent }),
23
23
+
output: await applet<Output>("../../orchestrator/output-management", {
24
24
+
context: self.parent,
25
25
+
}),
20
26
};
21
27
22
28
const processor = {
23
29
metadataFetcher: await applet("../../processor/metadata-fetcher", { context: self.parent }),
24
30
};
25
31
26
26
-
console.log("🔮", orchestrator.output.data);
27
27
-
28
28
-
orchestrator.output.ondata = () => console.log("🚀", orchestrator.output.data);
29
29
-
30
30
-
orchestrator.output.addEventListener(
31
31
-
"data",
32
32
-
() => {
33
33
-
console.log("BEFORE", orchestrator.output.data.tracks);
34
34
-
processInputs();
35
35
-
},
36
36
-
{ once: true },
37
37
-
);
32
32
+
// 🚀
33
33
+
process();
38
34
39
35
////////////////////////////////////////////
40
36
// ACTIONS
41
37
////////////////////////////////////////////
42
42
-
async function processInputs() {
38
38
+
context.setActionHandler("process", process);
39
39
+
40
40
+
async function process() {
41
41
+
await waitUntilAppletIsReady(configurator.input);
42
42
+
43
43
const cachedTracks = orchestrator.output.data.tracks;
44
44
const tracks = await configurator.input.sendAction<Track[]>("list", cachedTracks, {
45
45
-
timeoutDuration: Infinity,
45
45
+
timeoutDuration: 60000 * 60 * 24,
46
46
});
47
47
48
48
// Process
49
49
-
tracks.reduce(async (promise: Promise<Track[]>, track: Track) => {
50
50
-
const acc = await promise;
51
51
-
if (track.tags) return [...acc, track];
49
49
+
const tracksWithMetadata = await tracks.reduce(
50
50
+
async (promise: Promise<Track[]>, track: Track) => {
51
51
+
const acc = await promise;
52
52
53
53
-
await configurator.input.sendAction("resolve", track.uri);
54
54
-
const url = configurator.input.data as string | undefined;
53
53
+
if (track.tags) return [...acc, track];
55
54
56
56
-
await processor.metadataFetcher.sendAction("extract", url);
57
57
-
const meta: any = processor.metadataFetcher.data;
55
55
+
const url = await configurator.input.sendAction<string | undefined>("resolve", track.uri, {
56
56
+
timeoutDuration: 60000,
57
57
+
});
58
58
59
59
-
const stats: TrackStats = {
60
60
-
duration: meta.format.duration,
61
61
-
};
59
59
+
if (!url) return acc;
60
60
+
61
61
+
const meta = await processor.metadataFetcher.sendAction("extract", url, {
62
62
+
timeoutDuration: 60000,
63
63
+
});
62
64
63
63
-
const tags: TrackTags = {
64
64
-
album: meta.common.album,
65
65
-
artist: meta.common.artist,
66
66
-
title: meta.common.title,
67
67
-
};
65
65
+
const stats: TrackStats = {
66
66
+
duration: meta.format.duration,
67
67
+
};
68
68
69
69
-
return [...acc, { ...track, stats, tags }];
70
70
-
}, Promise.resolve([]));
69
69
+
const tags: TrackTags = {
70
70
+
album: meta.common.album,
71
71
+
artist: meta.common.artist,
72
72
+
title: meta.common.title,
73
73
+
};
71
74
72
72
-
console.log("AFTER", tracks);
75
75
+
return [...acc, { ...track, stats, tags }];
76
76
+
},
77
77
+
Promise.resolve([]),
78
78
+
);
73
79
74
80
// Save
75
75
-
await orchestrator.output.sendAction("saveTracks", tracks);
81
81
+
await orchestrator.output.sendAction("tracks", tracksWithMetadata, {
82
82
+
timeoutDuration: 60000 * 2,
83
83
+
});
84
84
+
85
85
+
// Log
86
86
+
console.log("🪵 PROCESSING COMPLETED");
76
87
}
88
88
+
89
89
+
////////////////////////////////////////////
90
90
+
// 🚦
91
91
+
////////////////////////////////////////////
92
92
+
context.data = { ready: true };
77
93
</script>
+24
-34
src/pages/orchestrator/output-management/_applet.astro
Reviewed
···
3
3
import { applets } from "@web-applets/sdk";
4
4
import { debounce } from "throttle-debounce";
5
5
6
6
-
import type { Output, Track } from "@applets/core/types.d.ts";
6
6
+
import type { Track } from "@applets/core/types.d.ts";
7
7
+
import type { State } from "./types.d.ts";
7
8
import { applet, waitUntilAppletIsReady } from "@scripts/theme";
8
9
9
10
////////////////////////////////////////////
10
11
// SETUP
11
12
////////////////////////////////////////////
12
12
-
// Register applet
13
13
-
const context = applets.register<Output>();
13
13
+
const context = applets.register<State>();
14
14
+
15
15
+
// Initial data
16
16
+
context.data = {
17
17
+
ready: false,
18
18
+
tracks: [],
19
19
+
};
14
20
15
21
// Applet connections
16
22
const configurator = {
17
23
output: await applet("../../configurator/output", { context: self.parent }),
18
24
};
19
25
20
20
-
// Sample content
21
21
-
const SAMPLE_TRACKS: Track[] = [
22
22
-
{
23
23
-
id: crypto.randomUUID(),
24
24
-
uri: "https://archive.org/download/SUSPENSE_Radio_Digitally_Restored_Collection/%2040-07-22%20The%20Lodger%20%28audition%29%20%28Herbert%20Marshall%2C%20Alfred%20Hitchcock%2C%20Edmund%20Gwenn%29.mp3",
25
25
-
tags: {
26
26
-
title: "Yours Truly, Johnny Dollar",
27
27
-
},
28
28
-
},
29
29
-
{
30
30
-
id: crypto.randomUUID(),
31
31
-
uri: "https://archive.org/download/OTRR_Dimension_X_Singles/Dimension_X_1950-04-08__01_OuterLimit.mp3",
32
32
-
tags: {
33
33
-
title: "Dimension X",
34
34
-
},
35
35
-
},
36
36
-
];
37
37
-
38
38
-
// Initial state
26
26
+
// Load tracks
39
27
context.data = {
28
28
+
ready: false,
40
29
tracks: await loadTracks(),
41
30
};
42
31
43
32
// State helpers
44
44
-
function update(partial: Partial<Output>): void {
33
33
+
function update(partial: Partial<State>): void {
45
34
context.data = { ...context.data, ...partial };
46
35
}
47
36
···
49
38
// LOADERS
50
39
////////////////////////////////////////////
51
40
async function loadTracks(): Promise<Track[]> {
52
52
-
console.log("🚧🚧🚧 LOADING TRACKS");
53
53
-
54
41
await waitUntilAppletIsReady(configurator.output);
55
42
56
56
-
// TODO: This is not concurrency safe!
57
57
-
await configurator.output.sendAction(
43
43
+
const data = await configurator.output.sendAction(
58
44
"get",
59
45
{
60
46
name: "tracks.json",
61
47
},
62
48
{
63
63
-
timeoutDuration: 60000,
49
49
+
timeoutDuration: 120000,
64
50
},
65
51
);
66
52
67
67
-
const data = configurator.output.data;
68
68
-
69
69
-
console.log("🥝🥝🥝 TRACKS LOADED", data);
70
70
-
71
53
if (!data) {
72
54
return [];
73
55
}
···
78
60
////////////////////////////////////////////
79
61
// ACTIONS
80
62
////////////////////////////////////////////
81
81
-
const saveTracks = (tracks: Track[]) => {
63
63
+
const tracksHandler = (tracks: Track[]) => {
82
64
update({ tracks });
65
65
+
66
66
+
// Save tracks to output, but only the ones that need to be saved.
67
67
+
// TODO: For each track.uri scheme ask the output configurator if it needs to be cached.
83
68
saveTracksToOutput(tracks);
84
69
};
85
70
···
92
77
});
93
78
});
94
79
95
95
-
context.setActionHandler("saveTracks", saveTracks);
80
80
+
context.setActionHandler("tracks", tracksHandler);
96
81
97
82
////////////////////////////////////////////
98
83
// 🛠️
···
104
89
function encode(data: Object) {
105
90
return new TextEncoder().encode(JSON.stringify(data));
106
91
}
92
92
+
93
93
+
////////////////////////////////////////////
94
94
+
// 🚦
95
95
+
////////////////////////////////////////////
96
96
+
update({ ready: true });
107
97
</script>
+2
-2
src/pages/orchestrator/output-management/_manifest.json
Reviewed
···
3
3
"title": "Diffuse Orchestrator | Output management",
4
4
"entrypoint": "index.html",
5
5
"actions": {
6
6
-
"saveTracks": {
7
7
-
"description": "Save tracks to output."
6
6
+
"tracks": {
7
7
+
"description": "Manage tracks."
8
8
}
9
9
}
10
10
}
+3
src/pages/orchestrator/output-management/types.d.ts
Reviewed
···
1
1
+
import { Output } from "@applets/core/types";
2
2
+
3
3
+
export type State = Output & { ready: boolean };
+17
-14
src/scripts/themes/webamp/index.ts
Reviewed
···
2
2
import { URLTrack } from "webamp";
3
3
4
4
import type { Output, Track } from "@applets/core/types.d.ts";
5
5
-
import { applet } from "../../theme.ts";
5
5
+
import { applet, waitUntilAppletIsReady } from "../../theme.ts";
6
6
7
7
////////////////////////////////////////////
8
8
// 🎨 Styles
···
19
19
20
20
const orchestrator = {
21
21
output: await applet<Output>("../../orchestrator/output-management"),
22
22
-
};
23
22
24
24
-
// setTimeout(async () => {
25
25
-
// await applet("../../orchestrator/input-cache");
26
26
-
// }, 15000);
27
27
-
28
28
-
// await applet("../../orchestrator/input-cache");
23
23
+
// TODO: Should this be explicitely be ran after the output orchestrator is loaded?
24
24
+
input: await applet("../../orchestrator/input-cache"),
25
25
+
};
29
26
30
27
////////////////////////////////////////////
31
28
// ⚡
···
39
36
document.body.appendChild(ampNode);
40
37
amp.renderWhenReady(ampNode);
41
38
42
42
-
// orchestrator.output.ondata = async () => {
43
43
-
// console.log("DATA CHANGED");
44
44
-
// const tracks = await loadTracks();
45
45
-
// amp.setTracksToPlay(tracks);
46
46
-
// };
39
39
+
orchestrator.output.ondata = async () => {
40
40
+
const tracks = await loadTracks();
41
41
+
amp.setTracksToPlay([]);
42
42
+
amp.appendTracks(tracks);
43
43
+
amp.nextTrack();
44
44
+
};
47
45
48
46
////////////////////////////////////////////
49
47
// 🛠️
···
53
51
async (promise: Promise<URLTrack[]>, track: Track) => {
54
52
const acc = await promise;
55
53
56
56
-
await configurator.input.sendAction("resolve", track.uri);
57
57
-
const url = configurator.input.data as string | undefined;
54
54
+
// TODO: Ideally the URL should only be resolved when needed,
55
55
+
// but webamp doesn't allow for that.
56
56
+
// Maybe you could work around it with a service worker.
57
57
+
const url = await configurator.input.sendAction<string | undefined>("resolve", track.uri, {
58
58
+
timeoutDuration: 60000,
59
59
+
});
60
60
+
58
61
if (!url) return acc;
59
62
60
63
const urlTrack: URLTrack = {