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.

at v4 116 lines 3.0 kB view raw
1import * as IDB from "idb-keyval"; 2 3import { create as createCid } from "~/common/cid.js"; 4import { ostiary, rpc, workerProxy } from "~/common/worker.js"; 5 6/** 7 * @import {Track} from "~/definitions/types.d.ts" 8 * @import {ActionsWithTunnel, ProxiedActions} from "~/common/worker.d.ts" 9 * @import {Actions} from "~/components/artwork/types.d.ts" 10 * @import {Artwork} from "./types.d.ts" 11 */ 12 13// multicodec raw bytes 14const RAW = 0x55; 15 16const IDB_PREFIX = "~/components/orchestrator/artwork"; 17const IDB_ARTWORK_PREFIX = `${IDB_PREFIX}/cache`; 18 19/** @type {Map<string, Promise<Uint8Array | null>>} */ 20const inFlight = new Map(); 21 22//////////////////////////////////////////// 23// ACTIONS 24//////////////////////////////////////////// 25 26/** 27 * @type {ActionsWithTunnel<Actions>['get']} 28 */ 29export async function get({ data: track, ports }) { 30 const existing = inFlight.get(track.id); 31 if (existing) return existing; 32 33 const promise = processRequest(track, ports).finally(() => { 34 inFlight.delete(track.id); 35 }); 36 37 inFlight.set(track.id, promise); 38 return promise; 39} 40 41//////////////////////////////////////////// 42// ⚡️ 43//////////////////////////////////////////// 44 45ostiary((context) => { 46 rpc(context, { get }); 47}); 48 49//////////////////////////////////////////// 50// 🛠️ 51//////////////////////////////////////////// 52 53/** 54 * @param {Track} track 55 * @param {Record<string, MessagePort>} ports 56 * @returns {Promise<Uint8Array | null>} 57 */ 58async function processRequest(track, ports) { 59 // Check if already processed 60 61 /** @type {string[] | undefined} */ 62 const cachedCids = await IDB.get( 63 `${IDB_ARTWORK_PREFIX}/track/${track.id}`, 64 ); 65 66 if (cachedCids?.length) { 67 /** @type {Artwork[]} */ 68 const art = await Promise.all( 69 cachedCids.map((cid) => IDB.get(`${IDB_ARTWORK_PREFIX}/image/${cid}`)), 70 ); 71 72 const found = art.filter(Boolean); 73 if (found.length) return found[0].bytes; 74 } 75 76 // 🚀 77 78 /** @type {ProxiedActions<Actions>} */ 79 const configurator = workerProxy(() => { 80 ports.artwork.start(); 81 return ports.artwork; 82 }); 83 84 const bytes = await configurator.get(track); 85 86 if (bytes === null) { 87 await IDB.set(`${IDB_ARTWORK_PREFIX}/track/${track.id}`, []); 88 return null; 89 } 90 91 const mime = detectMime(bytes); 92 93 /** @type {Artwork} */ 94 const art = { bytes, mime }; 95 96 // Save artwork to IDB — store by content CID, map track to that CID 97 const cid = await createCid(RAW, bytes); 98 const key = `${IDB_ARTWORK_PREFIX}/image/${cid}`; 99 if (!await IDB.get(key)) await IDB.set(key, art); 100 101 await IDB.set(`${IDB_ARTWORK_PREFIX}/track/${track.id}`, [cid]); 102 103 return bytes; 104} 105 106/** 107 * @param {Uint8Array} bytes 108 * @returns {string} 109 */ 110function detectMime(bytes) { 111 if (bytes[0] === 0xFF && bytes[1] === 0xD8) return "image/jpeg"; 112 if (bytes[0] === 0x89 && bytes[1] === 0x50) return "image/png"; 113 if (bytes[0] === 0x47 && bytes[1] === 0x49) return "image/gif"; 114 if (bytes[0] === 0x52 && bytes[1] === 0x49) return "image/webp"; 115 return "image/jpeg"; 116}