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 131 lines 2.8 kB view raw
1import * as IDB from "idb-keyval"; 2 3import { ostiary, rpc } from "~/common/worker.js"; 4import { CACHE_KEY_PREFIX, SCHEME } from "./constants.js"; 5 6/** 7 * @import { InputActions as Actions } from "~/components/input/types.d.ts" 8 * @import { Track } from "~/definitions/types.d.ts" 9 */ 10 11//////////////////////////////////////////// 12// STATE 13//////////////////////////////////////////// 14 15/** @type {Map<string, string>} */ 16const blobUrls = new Map(); 17 18//////////////////////////////////////////// 19// ACTIONS 20//////////////////////////////////////////// 21 22/** 23 * @type {Actions['artwork']} 24 */ 25export async function artwork(_uri) { 26 return null; 27} 28 29/** 30 * @type {Actions['consult']} 31 */ 32export async function consult(uriOrScheme) { 33 if (!uriOrScheme.includes("://")) { 34 return { supported: true, consult: "undetermined" }; 35 } 36 37 const cached = await IDB.get(CACHE_KEY_PREFIX + uriOrScheme); 38 return { supported: true, consult: cached !== undefined }; 39} 40 41/** 42 * @type {Actions['detach']} 43 */ 44export async function detach({ fileUriOrScheme, tracks }) { 45 if (!fileUriOrScheme.includes("://")) { 46 if (fileUriOrScheme === SCHEME) { 47 await removeBlobs(tracks.map((t) => t.uri)); 48 return []; 49 } 50 return tracks; 51 } 52 53 const remaining = tracks.filter((t) => t.uri !== fileUriOrScheme); 54 await removeBlobs([fileUriOrScheme]); 55 return remaining; 56} 57 58/** 59 * @type {Actions['groupConsult']} 60 */ 61export async function groupConsult(uris) { 62 const cached = await Promise.all( 63 uris.map((uri) => IDB.get(CACHE_KEY_PREFIX + uri)), 64 ); 65 66 return { 67 [SCHEME]: { 68 available: true, 69 scheme: SCHEME, 70 uris: uris.filter((_, i) => cached[i] !== undefined), 71 }, 72 }; 73} 74 75/** 76 * @type {Actions['list']} 77 */ 78export async function list(tracks) { 79 return tracks; 80} 81 82/** 83 * @type {Actions['resolve']} 84 */ 85export async function resolve({ uri }) { 86 const blob = 87 /** @type {Blob | undefined} */ (await IDB.get(CACHE_KEY_PREFIX + uri)); 88 if (!blob) return undefined; 89 90 let blobUrl = blobUrls.get(uri); 91 92 if (!blobUrl) { 93 blobUrl = URL.createObjectURL(blob); 94 blobUrls.set(uri, blobUrl); 95 } 96 97 return { expiresAt: Infinity, url: blobUrl }; 98} 99 100//////////////////////////////////////////// 101// 🛠️ 102//////////////////////////////////////////// 103 104/** 105 * @param {string[]} uris 106 */ 107async function removeBlobs(uris) { 108 await Promise.all(uris.map(async (uri) => { 109 const blobUrl = blobUrls.get(uri); 110 if (blobUrl) { 111 URL.revokeObjectURL(blobUrl); 112 blobUrls.delete(uri); 113 } 114 await IDB.del(CACHE_KEY_PREFIX + uri); 115 })); 116} 117 118//////////////////////////////////////////// 119// ⚡️ 120//////////////////////////////////////////// 121 122ostiary((context) => { 123 rpc(context, { 124 artwork, 125 consult, 126 detach, 127 groupConsult, 128 list, 129 resolve, 130 }); 131});