A music player that connects to your cloud/distributed storage.
at v4 179 lines 4.2 kB view raw
1import * as URI from "uri-js"; 2 3import { groupTracksPerScheme } from "@common/utils.js"; 4import { ostiary, rpc, workerProxy } from "@common/worker.js"; 5 6/** 7 * @import {Track} from "@definitions/types.d.ts"; 8 * @import {GroupConsult, InputActions} from "@components/input/types.d.ts" 9 * @import {ActionsWithTunnel, ProxiedActions} from "@common/worker.d.ts" 10 */ 11 12//////////////////////////////////////////// 13// INPUT ACTIONS 14//////////////////////////////////////////// 15 16/** 17 * @type {ActionsWithTunnel<InputActions>['consult']} 18 */ 19export async function consult({ data, ports }) { 20 const fileUriOrScheme = data; 21 const scheme = fileUriOrScheme.includes(":") 22 ? URI.parse(fileUriOrScheme).scheme || fileUriOrScheme 23 : fileUriOrScheme; 24 25 const input = grabInput(scheme, ports); 26 27 if (!input) { 28 return { supported: false, reason: "Unsupported scheme" }; 29 } 30 31 return await input.consult(fileUriOrScheme); 32} 33 34/** 35 * @type {ActionsWithTunnel<InputActions>['detach']} 36 */ 37export async function detach({ data, ports }) { 38 const cachedTracks = data.tracks; 39 const groups = groupTracks(cachedTracks, ports); 40 41 const promises = Object.entries(groups).map( 42 async ([scheme, tracksGroup]) => { 43 const input = grabInput(scheme, ports); 44 if (!input || tracksGroup.length === 0) return tracksGroup; 45 if ( 46 data.fileUriOrScheme.includes("://") 47 ? data.fileUriOrScheme.startsWith(`${scheme}://`) === false 48 : data.fileUriOrScheme !== scheme 49 ) return tracksGroup; 50 51 return await input.detach({ 52 fileUriOrScheme: data.fileUriOrScheme, 53 tracks: tracksGroup, 54 }); 55 }, 56 ); 57 58 const nested = await Promise.all(promises); 59 const tracks = nested.flat(1); 60 61 return tracks; 62} 63 64/** 65 * @type {ActionsWithTunnel<InputActions>['groupConsult']} 66 */ 67export async function groupConsult({ data, ports }) { 68 const tracks = data; 69 const groups = groupTracksPerScheme(tracks); 70 71 /** @type {GroupConsult[]} */ 72 const consultations = await Promise.all( 73 Object.keys(groups).map(async (scheme) => { 74 const input = grabInput(scheme, ports); 75 76 if (!input) { 77 return { 78 [scheme]: { 79 available: false, 80 reason: "Unsupported scheme", 81 scheme, 82 tracks: groups[scheme] ?? [], 83 }, 84 }; 85 } 86 87 return await input.groupConsult(groups[scheme] ?? {}); 88 }), 89 ); 90 91 return consultations.reduce((acc, c) => { 92 return { ...acc, ...c }; 93 }, {}); 94} 95 96/** 97 * @type {ActionsWithTunnel<InputActions>['list']} 98 */ 99export async function list({ data, ports }) { 100 const groups = await groupConsult({ data, ports }); 101 102 const promises = Object.values(groups).map( 103 async ({ available, scheme, tracks }) => { 104 if (!available) return tracks; 105 106 const input = grabInput(scheme, ports); 107 if (!input) return tracks; 108 return await input.list(tracks); 109 }, 110 ); 111 112 const nested = await Promise.all(promises); 113 const tracks = nested.flat(1); 114 115 return tracks; 116} 117 118/** 119 * @type {ActionsWithTunnel<InputActions>['resolve']} 120 */ 121export async function resolve({ data, ports }) { 122 const uri = data.uri; 123 const scheme = uri.split(":", 1)[0]; 124 const input = grabInput(scheme, ports); 125 if (!input) return undefined; 126 127 const result = await input.resolve(data); 128 return result; 129} 130 131//////////////////////////////////////////// 132// ⚡️ 133//////////////////////////////////////////// 134 135ostiary((context) => { 136 rpc(context, { 137 consult, 138 detach, 139 groupConsult, 140 list, 141 resolve, 142 }); 143}); 144 145//////////////////////////////////////////// 146// 🛠️ 147//////////////////////////////////////////// 148 149/** 150 * @param {string} scheme 151 * @param {Record<string, MessagePort>} ports 152 * @returns {ProxiedActions<InputActions> | null} 153 */ 154function grabInput(scheme, ports) { 155 const port = ports[scheme]; 156 if (!port) return null; 157 158 return workerProxy(() => { 159 port.start(); 160 return port; 161 }); 162} 163 164/** 165 * @param {Track[]} tracks 166 * @param {Record<string, MessagePort>} ports 167 */ 168function groupTracks(tracks, ports) { 169 const grouped = groupTracksPerScheme( 170 tracks, 171 Object.fromEntries( 172 Object.keys(ports).map((k) => { 173 return [k, []]; 174 }), 175 ), 176 ); 177 178 return grouped; 179}