A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

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

feat: process tracks

+76 -181
-121
_backup/pages/orchestrator/process-tracks/_applet.astro
··· 1 - <script> 2 - import type { Applet } from "@web-applets/sdk"; 3 - import type { ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 4 - import { applet, register, wait } from "@scripts/applet/common"; 5 - import { tracksCacheId } from "@scripts/output/common"; 6 - 7 - //////////////////////////////////////////// 8 - // SETUP 9 - //////////////////////////////////////////// 10 - const context = register<{ isProcessing: boolean }>(); 11 - 12 - // Initial data 13 - context.data = { 14 - isProcessing: false, 15 - }; 16 - 17 - // Applet connections 18 - const configurator = { 19 - input: applet("/configurator/input"), 20 - input_2: undefined as Applet | undefined, 21 - output: applet<ManagedOutput>("/configurator/output"), 22 - }; 23 - 24 - const processor = { 25 - metadata: applet("/processor/metadata"), 26 - }; 27 - 28 - //////////////////////////////////////////// 29 - // PROCESS 30 - //////////////////////////////////////////// 31 - context.setActionHandler("processInputs", processInputs); 32 - 33 - async function processInputs() { 34 - if (context.data.isProcessing) return; 35 - context.data = { ...context.data, isProcessing: true }; 36 - console.log("🪵 Processing initiated"); 37 - 38 - const input = configurator.input_2 39 - ? configurator.input_2 40 - : await applet("/configurator/input", { context: self, newInstance: true }); 41 - 42 - if (!configurator.input_2) configurator.input_2 = input; 43 - 44 - const output = await configurator.output; 45 - const cachedTracks = output.data.tracks.collection; 46 - 47 - await input.sendAction("contextualize", cachedTracks, { 48 - timeoutDuration: 60000 * 5, 49 - worker: true, 50 - }); 51 - 52 - const tracks = await input.sendAction<Track[]>("list", cachedTracks, { 53 - timeoutDuration: 60000 * 60 * 24, 54 - worker: true, 55 - }); 56 - 57 - // Process 58 - const tracksWithMetadata = await tracks.reduce( 59 - async (promise: Promise<Track[]>, track: Track) => { 60 - const acc = await promise; 61 - 62 - if (track.tags && track.stats) return [...acc, track]; 63 - 64 - const resGet = await input.sendAction<ResolvedUri>( 65 - "resolve", 66 - { method: "GET", uri: track.uri }, 67 - { 68 - timeoutDuration: 60000 * 5, 69 - worker: true, 70 - }, 71 - ); 72 - 73 - const resHead = await input.sendAction<ResolvedUri>( 74 - "resolve", 75 - { method: "HEAD", uri: track.uri }, 76 - { 77 - timeoutDuration: 60000 * 5, 78 - worker: true, 79 - }, 80 - ); 81 - 82 - if (!resGet) return [...acc, track]; 83 - 84 - const metadataProcessor = await processor.metadata; 85 - const { stats, tags } = await metadataProcessor.sendAction( 86 - "supply", 87 - { urls: { get: resGet.url, head: resHead?.url || resGet.url } }, 88 - { 89 - timeoutDuration: 60000 * 15, 90 - worker: true, 91 - }, 92 - ); 93 - 94 - return [...acc, { ...track, stats, tags }]; 95 - }, 96 - Promise.resolve([]), 97 - ); 98 - 99 - // Save 100 - const changed = tracksCacheId(tracksWithMetadata) !== output.data.tracks.cacheId; 101 - 102 - if (changed) 103 - await output.sendAction("tracks", tracksWithMetadata, { 104 - timeoutDuration: 60000 * 5, 105 - }); 106 - 107 - // Fin 108 - console.log("🪵 Processing completed"); 109 - context.data = { ...context.data, isProcessing: false }; 110 - } 111 - 112 - //////////////////////////////////////////// 113 - // 🚀 114 - //////////////////////////////////////////// 115 - context.settled().then(async () => { 116 - if (context.isMainInstance()) { 117 - await wait(await configurator.output, (d) => d?.tracks.state === "loaded"); 118 - processInputs(); 119 - } 120 - }); 121 - </script>
-6
_backup/pages/orchestrator/process-tracks/_manifest.json
··· 1 - { 2 - "name": "diffuse/orchestrator/process-tracks", 3 - "title": "Diffuse Orchestrator | Process Tracks", 4 - "entrypoint": "index.html", 5 - "actions": {} 6 - }
-9
_backup/pages/orchestrator/process-tracks/index.astro
··· 1 - --- 2 - import Layout from "@layouts/applet.astro"; 3 - import Applet from "./_applet.astro"; 4 - import { title } from "./_manifest.json"; 5 - --- 6 - 7 - <Layout title={title}> 8 - <Applet /> 9 - </Layout>
+5 -4
src/common/index.js
··· 1 - import * as Uint8 from "uint8arrays"; 1 + // import * as Uint8 from "uint8arrays"; 2 2 3 3 /** 4 4 * @import {Track} from "@component/core/types.d.ts" ··· 105 105 * @returns {Promise<string>} 106 106 */ 107 107 export async function trackArtworkCacheId(track) { 108 - return await crypto.subtle 109 - .digest("SHA-256", new TextEncoder().encode(track.uri)) 110 - .then((a) => Uint8.toString(new Uint8Array(a), "base64url")); 108 + return ""; 109 + // return await crypto.subtle 110 + // .digest("SHA-256", new TextEncoder().encode(track.uri)) 111 + // .then((a) => Uint8.toString(new Uint8Array(a), "base64url")); 111 112 }
+1 -1
src/component/core/types.d.ts
··· 47 47 tracks: { 48 48 collection: SignalReader<Track[]>; 49 49 reload: () => Promise<void>; 50 + save: (tracks: Track[]) => Promise<void>; 50 51 state: SignalReader<"loading" | "loaded">; 51 - store: (tracks: Track[]) => Promise<void>; 52 52 }; 53 53 }; 54 54
+6
src/component/input/opensubsonic/common.js
··· 107 107 } 108 108 109 109 /** 110 + * Parse an opensubsonic URI. 111 + * 112 + * ``` 113 + * opensubsonic://username:password@server-host:port/path?tls=f 114 + * ``` 115 + * 110 116 * @param {string} uriString 111 117 * @returns {{ path: string | undefined; server: Server; songId: string | undefined } | undefined} 112 118 */
+52 -13
src/component/orchestrator/process-tracks/element.js
··· 1 + import deepDiff from "@fry69/deep-diff"; 2 + 1 3 import { DiffuseElement } from "@common/element.js"; 2 4 import { signal } from "@common/signal.js"; 3 5 ··· 47 49 /** 48 50 * @override 49 51 */ 50 - connectedCallback() { 52 + async connectedCallback() { 51 53 super.connectedCallback(); 52 54 53 55 /** @type {OutputElement | null} */ ··· 56 58 // Check output element presence 57 59 if (!output) throw new Error("Missing required `output` element"); 58 60 61 + // Wait until defined 62 + await customElements.whenDefined(output.localName); 63 + 59 64 // Process whenever tracks are loaded 60 - this.effect(async () => { 61 - // TODO: Make configurable 62 - await customElements.whenDefined("do-indexed-db"); 63 - 65 + this.effect(() => { 64 66 const state = output.tracks.state(); 65 - console.log(state); 66 67 if (state !== "loaded") return; 67 68 68 - this.process(output.tracks.collection()); 69 + this.process(output); 69 70 }); 70 71 } 71 72 72 73 /** 73 - * @param {Track[]} cachedTracks 74 + * @param {OutputElement} output 74 75 */ 75 - async process(cachedTracks) { 76 - this.#isProcessing.value = true; 77 - console.log("🪵 Processing initiated"); 78 - 76 + async process(output) { 79 77 /** @type {InputElement | null} */ 80 78 const input = document.querySelector(this.inputSelector); 81 79 ··· 91 89 throw new Error("Missing required `metadata-processor` element"); 92 90 } 93 91 92 + // Wait until defined 93 + await customElements.whenDefined(input.localName); 94 + await customElements.whenDefined(metadataProcessor.localName); 95 + 96 + // Start 97 + this.#isProcessing.value = true; 98 + console.log("🪵 Processing initiated"); 99 + 100 + const cachedTracks = output.tracks.collection(); 101 + 94 102 // Contextualize 95 103 await input.contextualize(cachedTracks); 96 104 97 105 // List 98 106 const tracks = await input.list(cachedTracks); 99 107 100 - console.log(tracks); 108 + // Fetch metadata if needed 109 + // TODO: Parallelisation 110 + const tracksWithMetadata = await tracks.reduce( 111 + /** 112 + * @param {Promise<Track[]>} promise 113 + * @param {Track} track 114 + */ 115 + async (promise, track) => { 116 + const acc = await promise; 117 + 118 + if (track.tags && track.stats) return [...acc, track]; 119 + 120 + const resGet = await input.resolve({ method: "GET", uri: track.uri }); 121 + const resHead = await input.resolve({ method: "HEAD", uri: track.uri }); 122 + 123 + if (!resGet) return [...acc, track]; 124 + 125 + const { stats, tags } = await metadataProcessor.supply({ 126 + urls: { get: resGet.url, head: resHead?.url || resGet.url }, 127 + }); 128 + 129 + return [...acc, { ...track, stats, tags }]; 130 + }, 131 + Promise.resolve([]), 132 + ); 133 + 134 + // Changed? 135 + const diff = deepDiff.diff(tracksWithMetadata, cachedTracks); 136 + const changed = !!diff; 137 + 138 + // Save if changed 139 + if (changed) await output.tracks.save(tracksWithMetadata); 101 140 102 141 // Fin 103 142 console.log("🪵 Processing completed");
+4 -8
src/component/output/common.js
··· 1 - import { signal } from "@common/signal.js"; 1 + import { effect, signal } from "@common/signal.js"; 2 2 3 3 /** 4 4 * @import {OutputManager, Track} from "@component/core/types.d.ts" ··· 13 13 const ts = signal(/** @type {"loading" | "loaded"} */ ("loading")); 14 14 15 15 async function loadTracks() { 16 - console.log("..."); 17 16 if (init && (await init()) === false) return; 18 - console.log("start load"); 19 - const a = await tracks.get(); 20 - console.log(a); 21 - t.value = a; 17 + t.value = await tracks.get(); 22 18 ts.value = "loaded"; 23 19 } 24 20 ··· 28 24 tracks: { 29 25 collection: t.get, 30 26 reload: loadTracks, 31 - state: ts.get, 32 - store: async (newTracks) => { 27 + save: async (newTracks) => { 33 28 t.value = newTracks; 34 29 await tracks.put(newTracks); 35 30 }, 31 + state: ts.get, 36 32 }, 37 33 }; 38 34 }
+1 -5
src/component/output/indexed-db/element.js
··· 25 25 // Manager 26 26 const manager = outputManager({ 27 27 tracks: { 28 - get: () => { 29 - const fn = use("getTracks", worker); 30 - console.log("Call", fn, worker); 31 - return fn(); 32 - }, 28 + get: use("getTracks", worker), 33 29 put: use("putTracks", worker), 34 30 }, 35 31 });
-2
src/component/output/indexed-db/worker.js
··· 16 16 * @type {OutputActions['getTracks']} 17 17 */ 18 18 export async function getTracks() { 19 - console.log("GET"); 20 - 21 19 const encoded = await get({ name: "tracks.json" }); 22 20 if (!encoded) return []; 23 21
+4 -9
src/theme/blur/index.js
··· 1 1 import "@component/input/opensubsonic/element.js"; 2 - import "@component/output/indexed-db/element.js"; 3 2 import "@component/processor/metadata/element.js"; 4 3 5 4 import * as Audio from "@component/engine/audio/element.js"; 5 + import * as Output from "@component/output/indexed-db/element.js"; 6 6 import * as Queue from "@component/engine/queue/element.js"; 7 7 8 8 import "@component/orchestrator/process-tracks/element.js"; ··· 15 15 */ 16 16 17 17 const audio = component(Audio); 18 + const output = component(Output); 18 19 const queue = component(Queue); 19 20 21 + globalThis.output = output; 22 + 20 23 // QUEUE 21 24 22 25 effect(() => { ··· 33 36 ], 34 37 }); 35 38 }); 36 - 37 - queue.pool([ 38 - { 39 - id: "test", 40 - uri: 41 - "https://archive.org/download/deathofsalesmans00mill/01_Side_1_Death_of_a_salesman_-_Introduction_Act_1__Part_1.mp3", 42 - }, 43 - ]);
+3 -3
src/theme/blur/index.vto
··· 4 4 <link rel="stylesheet" href="../../styles/theme/blur/index.css" /> 5 5 </head> 6 6 <body> 7 - <de-audio id="deck-a"></de-audio> 8 - <de-queue id="deck-a"></de-queue> 7 + <de-audio></de-audio> 8 + <de-queue></de-queue> 9 9 10 10 <di-opensubsonic></di-opensubsonic> 11 - <do-indexed-db id="output"></do-indexed-db> 11 + <do-indexed-db></do-indexed-db> 12 12 <dp-metadata></dp-metadata> 13 13 14 14 <do-process-tracks