Experiment to rebuild Diffuse using web applets.
0
fork

Configure Feed

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

feat: include expire timestamp in uri resolving result

+52 -28
+1 -1
src/pages/configurator/input/_manifest.json
··· 24 24 }, 25 25 "resolve": { 26 26 "title": "Resolve", 27 - "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes. If it can be resolved that is, otherwise you'll get `undefined`.", 27 + "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`.", 28 28 "params_schema": { 29 29 "type": "object", 30 30 "properties": {
+2
src/pages/core/types.d.ts
··· 9 9 10 10 /* TRACKS */ 11 11 12 + export type ResolvedUri = undefined | { url: string; expiresAt: number }; // TODO: Streams? 13 + 12 14 export interface Track<Tags = TrackTags, Stats = TrackStats> { 13 15 id: string; 14 16
+1 -1
src/pages/input/native-fs/_applet.astro
··· 198 198 const file = await fileHandle.getFile(); 199 199 const url = URL.createObjectURL(file); 200 200 201 - return url; 201 + return { expiresAt: Infinity, url }; 202 202 }; 203 203 204 204 const mount = async () => {
+1 -1
src/pages/input/native-fs/_manifest.json
··· 27 27 }, 28 28 "resolve": { 29 29 "title": "Resolve", 30 - "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.", 30 + "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.", 31 31 "params_schema": { 32 32 "type": "object", 33 33 "properties": {
+4 -1
src/pages/input/s3/_applet.astro
··· 266 266 bucket.path.replace(/\/$/, "") + URI.unescapeComponent(parsedURI.path || "") 267 267 ).replace(/^\//, ""); 268 268 269 + const expiresInSeconds = 60 * 60 * 24 * 7; // 7 days 270 + const expiresAtSeconds = Math.round(Date.now() / 1000) + expiresInSeconds; 269 271 const url = await client.getPresignedUrl(method.toUpperCase() as any, path); 270 - return url; 272 + 273 + return { expiresAt: expiresAtSeconds, url }; 271 274 }; 272 275 273 276 const mount = async () => {};
+1 -1
src/pages/input/s3/_manifest.json
··· 34 34 }, 35 35 "resolve": { 36 36 "title": "Resolve", 37 - "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.", 37 + "description": "Potentially translates a track uri with a matching scheme into a URL pointing at the audio bytes or an audio stream. If it can be resolved that is, otherwise you'll get `undefined`. Use the `consult` action to get a more detailed answer.", 38 38 "params_schema": { 39 39 "type": "object", 40 40 "properties": {
+5 -5
src/pages/orchestrator/input-cache/_applet.astro
··· 1 1 <script> 2 2 import { applets } from "@web-applets/sdk"; 3 3 4 - import type { Track } from "@applets/core/types.d.ts"; 4 + import type { ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 5 import { applet, waitUntilAppletData, waitUntilAppletIsReady } from "@scripts/theme"; 6 6 7 7 //////////////////////////////////////////// ··· 64 64 65 65 if (track.tags && track.stats) return [...acc, track]; 66 66 67 - const getURL = await configurator.input.sendAction<string | undefined>( 67 + const resGet = await configurator.input.sendAction<ResolvedUri>( 68 68 "resolve", 69 69 { method: "GET", uri: track.uri }, 70 70 { ··· 72 72 }, 73 73 ); 74 74 75 - const headURL = await configurator.input.sendAction<string | undefined>( 75 + const resHead = await configurator.input.sendAction<ResolvedUri>( 76 76 "resolve", 77 77 { method: "HEAD", uri: track.uri }, 78 78 { ··· 80 80 }, 81 81 ); 82 82 83 - if (!getURL) return acc; 83 + if (!resGet) return acc; 84 84 85 85 const { stats, tags } = await processor.metadataFetcher.sendAction( 86 86 "extract", 87 - { urls: { get: getURL, head: headURL || getURL } }, 87 + { urls: { get: resGet.url, head: resHead?.url || resGet.url } }, 88 88 { 89 89 timeoutDuration: 60000, 90 90 },
+26 -12
src/pages/orchestrator/single-queue/_applet.astro
··· 1 1 <script> 2 2 import { applets } from "@web-applets/sdk"; 3 3 4 - import type { Track, Output } from "@applets/core/types.d.ts"; 4 + import type { ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 5 import { applet, comparable, reactive } from "@scripts/theme"; 6 6 7 7 //////////////////////////////////////////// ··· 41 41 //////////////////////////////////////////// 42 42 context.setActionHandler("fill", fill); 43 43 44 - async function fill() { 45 - const queueItems = orchestrator.output.data.tracks.collection.map(async (track: Track) => { 46 - return { 47 - expiresAt: Infinity, // TODO 48 - id: track.id, 49 - url: await configurator.input.sendAction("resolve", { method: "GET", uri: track.uri }), 50 - }; 51 - }); 44 + async function fill(tracks: Track[]) { 45 + const queueItems = await tracks.reduce( 46 + async (promise: Promise<QueueEngine.QueueItem[]>, track: Track) => { 47 + const acc = await promise; 48 + const res = await configurator.input.sendAction<ResolvedUri>("resolve", { 49 + method: "GET", 50 + uri: track.uri, 51 + }); 52 52 53 - await engine.queue.sendAction("add", await Promise.all(queueItems)); 53 + if (!res) return acc; 54 + 55 + return [ 56 + ...acc, 57 + { 58 + expiresAt: res.expiresAt, 59 + id: track.id, 60 + url: res.url, 61 + }, 62 + ]; 63 + }, 64 + Promise.resolve([]), 65 + ); 66 + 67 + await engine.queue.sendAction("add", queueItems); 54 68 } 55 69 56 70 //////////////////////////////////////////// ··· 102 116 }, 103 117 }); 104 118 105 - fill(); 119 + fill(orchestrator.output.data.tracks.collection); 106 120 }, 107 121 ); 108 122 ··· 114 128 orchestrator.output, 115 129 (data) => (data ? comparable(data.tracks) : undefined), 116 130 (hash) => { 117 - if (hash) fill(); 131 + if (hash) fill(orchestrator.output.data.tracks.collection); 118 132 }, 119 133 ); 120 134 </script>
+6 -1
src/pages/orchestrator/single-queue/_manifest.json
··· 5 5 "actions": { 6 6 "fill": { 7 7 "title": "Fill", 8 - "description": "Fill up the queue." 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 + } 9 14 } 10 15 } 11 16 }
+5 -5
src/scripts/themes/webamp/index.ts
··· 1 1 import Webamp from "webamp"; 2 2 import { URLTrack } from "webamp"; 3 3 4 - import type { Output, Track } from "@applets/core/types.d.ts"; 5 - import { applet, waitUntilAppletIsReady } from "../../theme.ts"; 4 + import type { ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 + import { applet } from "../../theme.ts"; 6 6 7 7 //////////////////////////////////////////// 8 8 // 🎨 Styles ··· 55 55 // TODO: Ideally the URL should only be resolved when needed, 56 56 // but webamp doesn't allow for that. 57 57 // Maybe you could work around it with a service worker. 58 - const url = await configurator.input.sendAction<string | undefined>( 58 + const resGet = await configurator.input.sendAction<ResolvedUri>( 59 59 "resolve", 60 60 { method: "GET", uri: track.uri }, 61 61 { ··· 63 63 }, 64 64 ); 65 65 66 - if (!url) return acc; 66 + if (!resGet) return acc; 67 67 68 68 const urlTrack: URLTrack = { 69 - url, 69 + url: resGet.url, 70 70 metaData: { 71 71 title: track.tags?.title || "", 72 72 artist: track.tags?.artist || "",