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

Configure Feed

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

feat: webamp

+1054 -182
-30
_backup/pages/theme/webamp/index.astro
··· 1 - --- 2 - import "@styles/theme/webamp/index.css"; 3 - --- 4 - 5 - <html lang="en"> 6 - <head> 7 - <meta charset="UTF-8" /> 8 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 9 - 10 - <title>Diffuse</title> 11 - </head> 12 - <body> 13 - <main> 14 - <div class="desktop"> 15 - <!-- INPUT --> 16 - <a href="/configurator/input/" target="_blank" class="button desktop__item"> 17 - <img src="/images/icons/windows_98/cd_audio_cd_a-4.png" width="32" /> 18 - <label>Manage audio inputs</label> 19 - </a> 20 - 21 - <!-- OUTPUT --> 22 - <a href="/configurator/output/" target="_blank" class="button desktop__item"> 23 - <img src="/images/icons/windows_98/directory_open_file_mydocs_2k-2.png" width="32" /> 24 - <label>Manage user data</label> 25 - </a> 26 - </div> 27 - </main> 28 - <script src="../../../scripts/theme/webamp/index.js"></script> 29 - </body> 30 - </html>
-116
_backup/scripts/theme/webamp/index.ts
··· 1 - import type { URLTrack } from "webamp"; 2 - import Webamp from "webamp"; 3 - 4 - import type { GroupConsult, ManagedOutput, ResolvedUri, Track } from "@applets/core/types.d.ts"; 5 - import { applet, inputUrl, wait } from "@scripts/applet/common"; 6 - 7 - //////////////////////////////////////////// 8 - // 🗂️ Applets 9 - //////////////////////////////////////////// 10 - const configurator = { 11 - input: applet("/configurator/input"), 12 - output: applet<ManagedOutput>("/configurator/output"), 13 - }; 14 - 15 - const orchestrator = { 16 - processTracks: applet("/orchestrator/process-tracks"), 17 - }; 18 - 19 - //////////////////////////////////////////// 20 - // ⚡ 21 - //////////////////////////////////////////// 22 - const amp = new Webamp({ 23 - enableMediaSession: true, 24 - initialTracks: [], 25 - }); 26 - 27 - // Override 28 - const loadFromUrl = amp.media.loadFromUrl.bind(amp.media); 29 - 30 - async function loadOverride(uri: string, autoPlay: boolean) { 31 - const resp = await inputUrl(await configurator.input, uri); 32 - if (!resp) throw new Error("Failed to resolve URI"); 33 - return await loadFromUrl(resp.url, autoPlay); 34 - } 35 - 36 - amp.media.loadFromUrl = loadOverride.bind(amp.media); 37 - 38 - // Render 39 - const ampNode = document.createElement("div"); 40 - ampNode.style = "height: 100vh; left: 0; position: absolute; top: 0; width: 100%; z-index: -1000;"; 41 - document.body.appendChild(ampNode); 42 - amp.renderWhenReady(ampNode); 43 - 44 - // Wait for tracks to load 45 - configurator.output 46 - .then((output) => { 47 - output.ondata = loadAndInsert; 48 - return wait(output, (d) => d?.tracks.state === "loaded"); 49 - }) 50 - .then(async () => { 51 - await loadAndInsert(); 52 - }); 53 - 54 - // Load & insert 55 - let inserting = false; 56 - let tracksCacheId: string | undefined = undefined; 57 - 58 - async function loadAndInsert() { 59 - const output = await configurator.output; 60 - 61 - if (output.data.tracks.state !== "loaded") return; 62 - if (output.data.tracks.cacheId === tracksCacheId) return; 63 - if (inserting) return; 64 - 65 - inserting = true; 66 - tracksCacheId = output.data.tracks.cacheId; 67 - const tracks = await loadTracks(); 68 - 69 - // TODO: This kinda messes up the UI, 70 - // but at least the active audio doesn't stop playing. 71 - amp.store.dispatch({ type: "REMOVE_ALL_TRACKS" }); 72 - 73 - // TODO: Webamp blows up if you add too much tracks 74 - amp.appendTracks(tracks.slice(0, 1000)); 75 - 76 - const status = amp.getMediaStatus(); 77 - if (status !== "PLAYING") amp.nextTrack(); 78 - 79 - inserting = false; 80 - } 81 - 82 - //////////////////////////////////////////// 83 - // 🛠️ 84 - //////////////////////////////////////////// 85 - async function loadTracks(): Promise<URLTrack[]> { 86 - const input = await configurator.input; 87 - const output = await configurator.output; 88 - 89 - const groups = await input.sendAction<GroupConsult>( 90 - "groupConsult", 91 - output.data.tracks.collection, 92 - { timeoutDuration: 60000 * 5, worker: true }, 93 - ); 94 - 95 - // Available tracks 96 - let tracks: Track[] = []; 97 - 98 - Object.values(groups).forEach((value) => { 99 - if (value.available === false) return; 100 - tracks = tracks.concat(value.tracks); 101 - }, []); 102 - 103 - return tracks.map((track) => { 104 - const urlTrack: URLTrack = { 105 - url: track.uri, 106 - metaData: { 107 - title: track.tags?.title || "", 108 - artist: track.tags?.artist || "", 109 - album: track.tags?.album, 110 - }, 111 - duration: track.stats?.duration, 112 - }; 113 - 114 - return urlTrack; 115 - }); 116 - }
+16 -3
_config.ts
··· 59 59 } 60 60 }); 61 61 62 - // site.addEventListener("afterBuild", () => { 63 - // site.run("copy-type-defs"); 64 - // }); 62 + site.script("copy-win98-fonts", () => { 63 + Deno.copyFileSync( 64 + "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 65 + "./_site/fonts/ms_sans_serif.woff2", 66 + ); 67 + 68 + Deno.copyFileSync( 69 + "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 70 + "./_site/fonts/ms_sans_serif_bold.woff2", 71 + ); 72 + }); 73 + 74 + site.addEventListener("afterBuild", () => { 75 + // site.run("copy-type-defs"); 76 + site.run("copy-win98-fonts"); 77 + });
+5
deno.jsonc
··· 3 3 "version": "4.0.0", 4 4 "vendor": true, 5 5 "imports": { 6 + "98.css": "npm:98.css@^0.1.21", 6 7 "@fry69/deep-diff": "jsr:@fry69/deep-diff@^0.1.10", 8 + "@mary/ds-queue": "jsr:@mary/ds-queue@^0.1.3", 7 9 "@mys/m-rpc": "jsr:@mys/m-rpc@^0.12.2", 8 10 "@mys/worker-fn": "jsr:@mys/worker-fn@^3.2.1", 9 11 "@okikio/transferables": "jsr:@okikio/transferables@^1.0.2", 10 12 "@orama/orama": "jsr:@orama/orama@^2.0.6", 13 + "@vicary/debounce-microtask": "jsr:@vicary/debounce-microtask@^0.1.8", 11 14 "alien-signals": "npm:alien-signals@^3.0.0", 12 15 "idb-keyval": "npm:idb-keyval@^6.2.2", 13 16 "morphdom": "npm:morphdom@^2.7.7/dist/morphdom.js", 14 17 "query-string": "npm:query-string@^9.3.1", 15 18 "subsonic-api": "npm:subsonic-api@^3.2.0", 19 + "throttle-debounce": "npm:throttle-debounce@^5.0.2", 16 20 "uint8arrays": "npm:uint8arrays@^5.1.0", 17 21 "uri-js": "npm:uri-js@^4.4.1", 22 + "webamp": "npm:webamp@^2.2.0", 18 23 "xxh32": "npm:xxh32@^2.0.5", 19 24 20 25 // music-metadata
+693
deno.lock
··· 3 3 "specifiers": { 4 4 "jsr:@deno/loader@0.3.6": "0.3.6", 5 5 "jsr:@fry69/deep-diff@~0.1.10": "0.1.10", 6 + "jsr:@mary/ds-queue@~0.1.3": "0.1.3", 6 7 "jsr:@mys/m-rpc@~0.12.2": "0.12.2", 7 8 "jsr:@mys/worker-fn@^3.2.1": "3.2.1", 8 9 "jsr:@okikio/transferables@^1.0.2": "1.0.2", ··· 34 35 "jsr:@std/toml@^1.0.3": "1.0.10", 35 36 "jsr:@std/yaml@1.0.9": "1.0.9", 36 37 "jsr:@std/yaml@^1.0.5": "1.0.9", 38 + "jsr:@vicary/debounce-microtask@~0.1.8": "0.1.8", 39 + "npm:98.css@~0.1.21": "0.1.21", 37 40 "npm:alien-signals@3": "3.0.3", 38 41 "npm:autoprefixer@10.4.21": "10.4.21_postcss@8.5.6", 39 42 "npm:idb-keyval@^6.2.2": "6.2.2", ··· 46 49 "npm:postcss@8.5.6": "8.5.6", 47 50 "npm:query-string@^9.3.1": "9.3.1", 48 51 "npm:subsonic-api@^3.2.0": "3.2.0", 52 + "npm:throttle-debounce@^5.0.2": "5.0.2", 49 53 "npm:uint8arrays@^5.1.0": "5.1.0", 50 54 "npm:uri-js@^4.4.1": "4.4.1", 55 + "npm:webamp@^2.2.0": "2.2.0_redux@5.0.1_react@19.2.0_react-dom@19.2.0__react@19.2.0", 51 56 "npm:xxh32@^2.0.5": "2.0.5" 52 57 }, 53 58 "jsr": { ··· 56 61 }, 57 62 "@fry69/deep-diff@0.1.10": { 58 63 "integrity": "cdd88fefaef1ac896a038a5f3c0895038d8c725e61bac50489c455156e0275f5" 64 + }, 65 + "@mary/ds-queue@0.1.3": { 66 + "integrity": "a743caa397b924cb08b0bbdffc526eb1ea2d3fc9e675da6edc137c437fc93c76" 59 67 }, 60 68 "@mys/m-rpc@0.12.2": { 61 69 "integrity": "36599d3d4708db9f5c0f7da35a17b7e7da1fafddb69de6cfcdc6afe94cd4f084", ··· 153 161 }, 154 162 "@std/yaml@1.0.9": { 155 163 "integrity": "6bad3dc766dd85b4b37eabcba81b6aa4eac7a392792ae29abcfb0f90602d55bb" 164 + }, 165 + "@vicary/debounce-microtask@0.1.8": { 166 + "integrity": "fe180e0c599903ccf7a93e719ea986c48affc1ff78951a1bc0ccb874aa30fd0e" 156 167 } 157 168 }, 158 169 "npm": { 170 + "98.css@0.1.21": { 171 + "integrity": "sha512-ddk5qtUWyapM0Bzd5jwGExoE5fdSEGrP+F5VbYjyZLf2c9UVmn6w2NPTvCsoD4BWdGsjdLjlkQGhWwWTJcYQJQ==" 172 + }, 173 + "@assemblyscript/loader@0.17.14": { 174 + "integrity": "sha512-+PVTOfla/0XMLRTQLJFPg4u40XcdTfon6GGea70hBGi8Pd7ZymIXyVUR+vK8wt5Jb4MVKTKPIz43Myyebw5mZA==" 175 + }, 176 + "@babel/runtime@7.28.4": { 177 + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" 178 + }, 179 + "@borewit/text-codec@0.1.1": { 180 + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==" 181 + }, 182 + "@borewit/text-codec@0.2.0": { 183 + "integrity": "sha512-X999CKBxGwX8wW+4gFibsbiNdwqmdQEXmUejIWaIqdrHBgS5ARIOOeyiQbHjP9G58xVEPcuvP6VwwH3A0OFTOA==" 184 + }, 185 + "@redux-devtools/extension@3.3.0_redux@5.0.1": { 186 + "integrity": "sha512-X34S/rC8S/M1BIrkYD1mJ5f8vlH0BDqxXrs96cvxSBo4FhMdbhU+GUGsmNYov1xjSyLMHgo8NYrUG8bNX7525g==", 187 + "dependencies": [ 188 + "@babel/runtime", 189 + "immutable", 190 + "redux" 191 + ] 192 + }, 193 + "@sentry/browser@5.9.1": { 194 + "integrity": "sha512-7AOabwp9yAH9h6Xe6TfDwlLxHbUSWs+SPWHI7bPlht2yDSAqkXYGSzRr5X0XQJX9oBQdx2cEPMqHyJrbNaP/og==", 195 + "dependencies": [ 196 + "@sentry/core", 197 + "@sentry/types", 198 + "@sentry/utils", 199 + "tslib" 200 + ] 201 + }, 202 + "@sentry/core@5.8.0": { 203 + "integrity": "sha512-aAh2KLidIXJVGrxmHSVq2eVKbu7tZiYn5ylW6yzJXFetS5z4MA+JYaSBaG2inVYDEEqqMIkb17TyWxxziUDieg==", 204 + "dependencies": [ 205 + "@sentry/hub", 206 + "@sentry/minimal", 207 + "@sentry/types", 208 + "@sentry/utils", 209 + "tslib" 210 + ] 211 + }, 212 + "@sentry/hub@5.8.0": { 213 + "integrity": "sha512-VdApn1ZCNwH1wwQwoO6pu53PM/qgHG+DQege0hbByluImpLBhAj9w50nXnF/8KzV4UoMIVbzCb6jXzMRmqqp9A==", 214 + "dependencies": [ 215 + "@sentry/types", 216 + "@sentry/utils", 217 + "tslib" 218 + ] 219 + }, 220 + "@sentry/minimal@5.8.0": { 221 + "integrity": "sha512-MIlFOgd+JvAUrBBmq7vr9ovRH1HvckhnwzHdoUPpKRBN+rQgTyZy1o6+kA2fASCbrRqFCP+Zk7EHMACKg8DpIw==", 222 + "dependencies": [ 223 + "@sentry/hub", 224 + "@sentry/types", 225 + "tslib" 226 + ] 227 + }, 228 + "@sentry/types@5.7.1": { 229 + "integrity": "sha512-tbUnTYlSliXvnou5D4C8Zr+7/wJrHLbpYX1YkLXuIJRU0NSi81bHMroAuHWILcQKWhVjaV/HZzr7Y/hhWtbXVQ==" 230 + }, 231 + "@sentry/utils@5.8.0": { 232 + "integrity": "sha512-KDxUvBSYi0/dHMdunbxAxD3389pcQioLtcO6CI6zt/nJXeVFolix66cRraeQvqupdLhvOk/el649W4fCPayTHw==", 233 + "dependencies": [ 234 + "@sentry/types", 235 + "tslib" 236 + ] 237 + }, 238 + "@tokenizer/inflate@0.2.7": { 239 + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", 240 + "dependencies": [ 241 + "debug", 242 + "fflate", 243 + "token-types@6.1.1" 244 + ] 245 + }, 246 + "@tokenizer/token@0.3.0": { 247 + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" 248 + }, 249 + "@types/hoist-non-react-statics@3.3.7_@types+react@19.2.2": { 250 + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", 251 + "dependencies": [ 252 + "@types/react", 253 + "hoist-non-react-statics" 254 + ] 255 + }, 256 + "@types/react@19.2.2": { 257 + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", 258 + "dependencies": [ 259 + "csstype" 260 + ] 261 + }, 262 + "@types/use-sync-external-store@0.0.3": { 263 + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" 264 + }, 159 265 "alien-signals@3.0.3": { 160 266 "integrity": "sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==" 161 267 }, 268 + "ani-cursor@0.0.5": { 269 + "integrity": "sha512-gGxst72lG9TOwEfbVpX9vHhzUGw+4Ee2XB6AfYq5JP+bxBtpAjgnTBepCVxYF5t1TPrWHN23nWqLTflJOA3/ag==", 270 + "dependencies": [ 271 + "byte-data", 272 + "riff-file" 273 + ] 274 + }, 162 275 "argparse@2.0.1": { 163 276 "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 277 + }, 278 + "assert@1.5.1": { 279 + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", 280 + "dependencies": [ 281 + "object.assign", 282 + "util" 283 + ] 164 284 }, 165 285 "autoprefixer@10.4.21_postcss@8.5.6": { 166 286 "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", ··· 175 295 ], 176 296 "bin": true 177 297 }, 298 + "babel-runtime@6.26.0": { 299 + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", 300 + "dependencies": [ 301 + "core-js", 302 + "regenerator-runtime" 303 + ] 304 + }, 305 + "base64-js@1.5.1": { 306 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 307 + }, 178 308 "baseline-browser-mapping@2.8.19": { 179 309 "integrity": "sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==", 180 310 "bin": true ··· 190 320 ], 191 321 "bin": true 192 322 }, 323 + "buffer@5.7.1": { 324 + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 325 + "dependencies": [ 326 + "base64-js", 327 + "ieee754" 328 + ] 329 + }, 330 + "butterchurn-presets@3.0.0-beta.4": { 331 + "integrity": "sha512-TbQLUPvGOYMZAtWKoCmBtludh9aQZ6NaMGQU4lvPeadBPy3Du3yNmwBjlTMLP5c5mRWElxQPjTL1PtR7FZK3OQ==", 332 + "dependencies": [ 333 + "@babel/runtime" 334 + ] 335 + }, 336 + "butterchurn@3.0.0-beta.5": { 337 + "integrity": "sha512-BStK4OAbBb9Pvt8PuQlS4WVmYBwU1KuDMRHF1V89QjoIFauAqq7tpV4EpYXj7K563r5daLrMX+2y5DBhZZ9Xig==", 338 + "dependencies": [ 339 + "@assemblyscript/loader", 340 + "ecma-proposal-math-extensions", 341 + "eel-wasm" 342 + ] 343 + }, 344 + "byte-data@18.1.1": { 345 + "integrity": "sha512-Kv/B0r7adgnCcrs/y703sac2XFLdHW5kPfis1j8+Ij/hmEcWhBKf+1pNTv+vsNqXb207Uiyri8bpnogNxR/4Lg==", 346 + "dependencies": [ 347 + "endianness", 348 + "ieee754-buffer", 349 + "utf8-buffer" 350 + ] 351 + }, 352 + "call-bind-apply-helpers@1.0.2": { 353 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 354 + "dependencies": [ 355 + "es-errors", 356 + "function-bind" 357 + ] 358 + }, 359 + "call-bind@1.0.8": { 360 + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", 361 + "dependencies": [ 362 + "call-bind-apply-helpers", 363 + "es-define-property", 364 + "get-intrinsic", 365 + "set-function-length" 366 + ] 367 + }, 368 + "call-bound@1.0.4": { 369 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 370 + "dependencies": [ 371 + "call-bind-apply-helpers", 372 + "get-intrinsic" 373 + ] 374 + }, 193 375 "caniuse-lite@1.0.30001751": { 194 376 "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==" 195 377 }, 378 + "chainsaw@0.0.9": { 379 + "integrity": "sha512-nG8PYH+/4xB+8zkV4G844EtfvZ5tTiLFoX3dZ4nhF4t3OCKIb9UvaFyNmeZO2zOSmRWzBoTD+napN6hiL+EgcA==", 380 + "dependencies": [ 381 + "traverse" 382 + ] 383 + }, 384 + "classnames@2.5.1": { 385 + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" 386 + }, 387 + "content-type@1.0.5": { 388 + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 389 + }, 390 + "core-js@2.6.12": { 391 + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", 392 + "deprecated": true, 393 + "scripts": true 394 + }, 395 + "core-util-is@1.0.3": { 396 + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 397 + }, 398 + "csstype@3.1.3": { 399 + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 400 + }, 401 + "debug@4.4.3": { 402 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 403 + "dependencies": [ 404 + "ms" 405 + ] 406 + }, 196 407 "decode-uri-component@0.4.1": { 197 408 "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==" 198 409 }, 410 + "define-data-property@1.1.4": { 411 + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 412 + "dependencies": [ 413 + "es-define-property", 414 + "es-errors", 415 + "gopd" 416 + ] 417 + }, 418 + "define-properties@1.2.1": { 419 + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", 420 + "dependencies": [ 421 + "define-data-property", 422 + "has-property-descriptors", 423 + "object-keys" 424 + ] 425 + }, 426 + "dunder-proto@1.0.1": { 427 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 428 + "dependencies": [ 429 + "call-bind-apply-helpers", 430 + "es-errors", 431 + "gopd" 432 + ] 433 + }, 434 + "ecma-proposal-math-extensions@0.0.2": { 435 + "integrity": "sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==" 436 + }, 437 + "eel-wasm@0.0.16": { 438 + "integrity": "sha512-1tkId7I7E1Vs4fXTRsH83Sjn2S/AbzrVQKLBRGys6NLc3eVH4NBffJsdEeLHOWWUgQpVXBEP3CV/srUZNIuBnw==" 439 + }, 199 440 "electron-to-chromium@1.5.237": { 200 441 "integrity": "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg==" 201 442 }, 443 + "endianness@8.0.2": { 444 + "integrity": "sha512-IU+77+jJ7lpw2qZ3NUuqBZFy3GuioNgXUdsL1L9tooDNTaw0TgOnwNuc+8Ns+haDaTifK97QLzmOANJtI/rGvw==" 445 + }, 202 446 "entities@4.5.0": { 203 447 "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" 204 448 }, 449 + "es-define-property@1.0.1": { 450 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" 451 + }, 452 + "es-errors@1.3.0": { 453 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" 454 + }, 455 + "es-object-atoms@1.1.1": { 456 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 457 + "dependencies": [ 458 + "es-errors" 459 + ] 460 + }, 205 461 "escalade@3.2.0": { 206 462 "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" 207 463 }, 464 + "fflate@0.8.2": { 465 + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" 466 + }, 467 + "file-type@11.1.0": { 468 + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==" 469 + }, 470 + "file-type@21.0.0": { 471 + "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", 472 + "dependencies": [ 473 + "@tokenizer/inflate", 474 + "strtok3@10.3.4", 475 + "token-types@6.1.1", 476 + "uint8array-extras" 477 + ] 478 + }, 208 479 "filter-obj@5.1.0": { 209 480 "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==" 210 481 }, 211 482 "fraction.js@4.3.7": { 212 483 "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==" 213 484 }, 485 + "fscreen@1.2.0": { 486 + "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==" 487 + }, 214 488 "function-bind@1.1.2": { 215 489 "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" 216 490 }, 491 + "get-intrinsic@1.3.0": { 492 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 493 + "dependencies": [ 494 + "call-bind-apply-helpers", 495 + "es-define-property", 496 + "es-errors", 497 + "es-object-atoms", 498 + "function-bind", 499 + "get-proto", 500 + "gopd", 501 + "has-symbols", 502 + "hasown", 503 + "math-intrinsics" 504 + ] 505 + }, 506 + "get-proto@1.0.1": { 507 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 508 + "dependencies": [ 509 + "dunder-proto", 510 + "es-object-atoms" 511 + ] 512 + }, 513 + "glsl-optimizer-js@0.0.2": { 514 + "integrity": "sha512-SMkVILyc1LeBEBgiHOe+4Bh8MEqxLNyAns0NfgmxJTxZZdj7oCoZt+n846rbdB8OLGsg16f5C9nmhi9XEuM8SQ==" 515 + }, 516 + "gopd@1.2.0": { 517 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" 518 + }, 519 + "has-property-descriptors@1.0.2": { 520 + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 521 + "dependencies": [ 522 + "es-define-property" 523 + ] 524 + }, 525 + "has-symbols@1.1.0": { 526 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" 527 + }, 528 + "hashish@0.0.4": { 529 + "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", 530 + "dependencies": [ 531 + "traverse" 532 + ] 533 + }, 217 534 "hasown@2.0.2": { 218 535 "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 219 536 "dependencies": [ 220 537 "function-bind" 221 538 ] 222 539 }, 540 + "hoist-non-react-statics@3.3.2": { 541 + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", 542 + "dependencies": [ 543 + "react-is@16.13.1" 544 + ] 545 + }, 223 546 "idb-keyval@6.2.2": { 224 547 "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==" 548 + }, 549 + "ieee754-buffer@2.0.0": { 550 + "integrity": "sha512-AXUAT0nMEi7h1Is8HXGXof3eejl/GabZFKSj8Ym6kVRUSwrAb52EkAXywiCQYSHGQMRn7lvfY7vhPMjVc+Kybg==" 551 + }, 552 + "ieee754@1.2.1": { 553 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 554 + }, 555 + "immediate@3.0.6": { 556 + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 557 + }, 558 + "immutable@4.3.7": { 559 + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" 560 + }, 561 + "inherits@2.0.3": { 562 + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" 563 + }, 564 + "inherits@2.0.4": { 565 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 566 + }, 567 + "invariant@2.2.4": { 568 + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 569 + "dependencies": [ 570 + "loose-envify" 571 + ] 225 572 }, 226 573 "is-core-module@2.16.1": { 227 574 "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", ··· 229 576 "hasown" 230 577 ] 231 578 }, 579 + "is-typedarray@1.0.0": { 580 + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" 581 + }, 582 + "isarray@1.0.0": { 583 + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 584 + }, 585 + "js-tokens@4.0.0": { 586 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 587 + }, 588 + "jszip@3.10.1": { 589 + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 590 + "dependencies": [ 591 + "lie", 592 + "pako", 593 + "readable-stream@2.3.8", 594 + "setimmediate" 595 + ] 596 + }, 597 + "lie@3.3.0": { 598 + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 599 + "dependencies": [ 600 + "immediate" 601 + ] 602 + }, 232 603 "lightningcss-wasm@1.30.1": { 233 604 "integrity": "sha512-KJTnKEn0REV6DoJzxG0m5EKVEFA1CVE1isDYpXjsuqWXwLKFPJtA9Z9BSzPZJwAZFN2KaUzy+IWGP59p5bm2sA==" 234 605 }, ··· 238 609 "uc.micro" 239 610 ] 240 611 }, 612 + "lodash@4.17.21": { 613 + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 614 + }, 615 + "loose-envify@1.4.0": { 616 + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 617 + "dependencies": [ 618 + "js-tokens" 619 + ], 620 + "bin": true 621 + }, 241 622 "markdown-it-attrs@4.3.1_markdown-it@14.1.0": { 242 623 "integrity": "sha512-/ko6cba+H6gdZ0DOw7BbNMZtfuJTRp9g/IrGIuz8lYc/EfnmWRpaR3CFPnNbVz0LDvF8Gf1hFGPqrQqq7De0rg==", 243 624 "dependencies": [ ··· 259 640 ], 260 641 "bin": true 261 642 }, 643 + "math-intrinsics@1.1.0": { 644 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" 645 + }, 262 646 "mdurl@2.0.0": { 263 647 "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" 264 648 }, 649 + "media-typer@0.3.0": { 650 + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 651 + }, 652 + "media-typer@1.1.0": { 653 + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" 654 + }, 655 + "milkdrop-eel-parser@0.0.4": { 656 + "integrity": "sha512-4PsOdTMDB7GM3UFzqXQQXf8MBeoolOhsBLMlhug+IIMZ+yNkvqLbdqDbrueGZc8P8tLRJP8pbAxna1yjFr06HQ==" 657 + }, 658 + "milkdrop-preset-converter-aws@0.1.6": { 659 + "integrity": "sha512-nr89LRZYgdrDn17vGQCvUK/LM9d90mywElL7zlzXBTgkxWAs/Kamn1Yl9676ugt4L4BAGo6PTEipIqeYXFSM7g==", 660 + "dependencies": [ 661 + "babel-runtime", 662 + "glsl-optimizer-js", 663 + "milkdrop-eel-parser", 664 + "milkdrop-preset-utils" 665 + ] 666 + }, 667 + "milkdrop-preset-utils@0.1.0": { 668 + "integrity": "sha512-yK5y03SN8INC+ssLLYGGsaAHgNxXEUK6PQVV44rg9OAA27F2aPM0tA5uGsDdASH9sgPaAaRVMV5NoEvEkh66Sw==", 669 + "dependencies": [ 670 + "babel-runtime", 671 + "lodash" 672 + ] 673 + }, 265 674 "morphdom@2.7.7": { 266 675 "integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==" 267 676 }, 677 + "ms@2.1.3": { 678 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 679 + }, 268 680 "multiformats@13.4.1": { 269 681 "integrity": "sha512-VqO6OSvLrFVAYYjgsr8tyv62/rCQhPgsZUXLTqoFLSgdkgiUYKYeArbt1uWLlEpkjxQe+P0+sHlbPEte1Bi06Q==" 270 682 }, 683 + "music-metadata-browser@0.6.6": { 684 + "integrity": "sha512-14KFz4HR6rM6RATcLtJoBDRbehU/dKdVzElCdeI8CjP7Un2HtSf0WiT7f7Lz+XNkcBMZUjthmC6Wy4+NNayCRw==", 685 + "dependencies": [ 686 + "assert", 687 + "buffer", 688 + "debug", 689 + "music-metadata@3.8.0", 690 + "readable-stream@3.6.2", 691 + "remove", 692 + "typedarray-to-buffer" 693 + ], 694 + "deprecated": true 695 + }, 696 + "music-metadata@11.9.0": { 697 + "integrity": "sha512-J7VqD8FY6KRcm75Fzj86FPsckiD/EdvO5OS3P+JiMf/2krP3TcAseZYfkic6eFeJ0iBhhzcdxgfu8hLW95aXXw==", 698 + "dependencies": [ 699 + "@borewit/text-codec@0.2.0", 700 + "@tokenizer/token", 701 + "content-type", 702 + "debug", 703 + "file-type@21.0.0", 704 + "media-typer@1.1.0", 705 + "strtok3@10.3.4", 706 + "token-types@6.1.1", 707 + "uint8array-extras" 708 + ] 709 + }, 710 + "music-metadata@3.8.0": { 711 + "integrity": "sha512-aIADbp3uCS+ANr4nnFEHzTzMy81OT7PR7WBMW73SJ28Y7P94nnEugmTOj1ICP2JmxBBDlo+MeYVgiPnxVN69tg==", 712 + "dependencies": [ 713 + "debug", 714 + "file-type@11.1.0", 715 + "media-typer@0.3.0", 716 + "strtok3@2.3.0", 717 + "token-types@1.3.2" 718 + ] 719 + }, 271 720 "nanoid@3.3.11": { 272 721 "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 273 722 "bin": true ··· 278 727 "normalize-range@0.1.2": { 279 728 "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" 280 729 }, 730 + "object-keys@1.1.1": { 731 + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" 732 + }, 733 + "object.assign@4.1.7": { 734 + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", 735 + "dependencies": [ 736 + "call-bind", 737 + "call-bound", 738 + "define-properties", 739 + "es-object-atoms", 740 + "has-symbols", 741 + "object-keys" 742 + ] 743 + }, 744 + "pako@1.0.11": { 745 + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 746 + }, 281 747 "path-parse@1.0.7": { 282 748 "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" 283 749 }, ··· 307 773 "source-map-js" 308 774 ] 309 775 }, 776 + "process-nextick-args@2.0.1": { 777 + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 778 + }, 310 779 "punycode.js@2.3.1": { 311 780 "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" 312 781 }, ··· 321 790 "split-on-first" 322 791 ] 323 792 }, 793 + "react-dom@19.2.0_react@19.2.0": { 794 + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", 795 + "dependencies": [ 796 + "react", 797 + "scheduler" 798 + ] 799 + }, 800 + "react-is@16.13.1": { 801 + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" 802 + }, 803 + "react-is@18.3.1": { 804 + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" 805 + }, 806 + "react-redux@8.1.3_react@19.2.0_react-dom@19.2.0__react@19.2.0_redux@5.0.1": { 807 + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", 808 + "dependencies": [ 809 + "@babel/runtime", 810 + "@types/hoist-non-react-statics", 811 + "@types/use-sync-external-store", 812 + "hoist-non-react-statics", 813 + "react", 814 + "react-dom", 815 + "react-is@18.3.1", 816 + "redux", 817 + "use-sync-external-store" 818 + ], 819 + "optionalPeers": [ 820 + "react-dom", 821 + "redux" 822 + ] 823 + }, 824 + "react@19.2.0": { 825 + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==" 826 + }, 324 827 "read-cache@1.0.0": { 325 828 "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", 326 829 "dependencies": [ 327 830 "pify" 328 831 ] 329 832 }, 833 + "readable-stream@2.3.8": { 834 + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 835 + "dependencies": [ 836 + "core-util-is", 837 + "inherits@2.0.4", 838 + "isarray", 839 + "process-nextick-args", 840 + "safe-buffer", 841 + "string_decoder", 842 + "util-deprecate" 843 + ] 844 + }, 845 + "readable-stream@3.6.2": { 846 + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 847 + "dependencies": [ 848 + "inherits@2.0.4", 849 + "string_decoder", 850 + "util-deprecate" 851 + ] 852 + }, 853 + "redux-sentry-middleware@0.1.8": { 854 + "integrity": "sha512-xubpYH9RgE31tZUESeRW5agwQa19Yd6Gy+4iO09raW/2TITPO5fhJdXpVwJfpGMbIYhEmHFqE2wD5Lnz7YtAeA==" 855 + }, 856 + "redux-thunk@2.4.2_redux@5.0.1": { 857 + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", 858 + "dependencies": [ 859 + "redux" 860 + ] 861 + }, 862 + "redux@5.0.1": { 863 + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" 864 + }, 865 + "regenerator-runtime@0.11.1": { 866 + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 867 + }, 868 + "remove@0.1.5": { 869 + "integrity": "sha512-AJMA9oWvJzdTjwIGwSQZsjGQiRx73YTmiOWmfCp1fpLa/D4n7jKcpoA+CZiVLJqKcEKUuh1Suq80c5wF+L/qVQ==", 870 + "dependencies": [ 871 + "seq" 872 + ] 873 + }, 874 + "reselect@3.0.1": { 875 + "integrity": "sha512-b/6tFZCmRhtBMa4xGqiiRp9jh9Aqi2A687Lo265cN0/QohJQEBPiQ52f4QB6i0eF3yp3hmLL21LSGBcML2dlxA==" 876 + }, 330 877 "resolve@1.22.11": { 331 878 "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", 332 879 "dependencies": [ ··· 336 883 ], 337 884 "bin": true 338 885 }, 886 + "riff-file@1.0.3": { 887 + "integrity": "sha512-Vv8wwGr0BCks7VMI3Lv0houZee4DaHFjjTT0LMhMJKio2YmLncLeIVpK63ydSverngNk8XQPU3fbeP3bWgSIig==", 888 + "dependencies": [ 889 + "byte-data" 890 + ] 891 + }, 892 + "safe-buffer@5.1.2": { 893 + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 894 + }, 895 + "scheduler@0.27.0": { 896 + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" 897 + }, 898 + "seq@0.3.5": { 899 + "integrity": "sha512-sisY2Ln1fj43KBkRtXkesnRHYNdswIkIibvNe/0UKm2GZxjMbqmccpiatoKr/k2qX5VKiLU8xm+tz/74LAho4g==", 900 + "dependencies": [ 901 + "chainsaw", 902 + "hashish" 903 + ] 904 + }, 905 + "set-function-length@1.2.2": { 906 + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 907 + "dependencies": [ 908 + "define-data-property", 909 + "es-errors", 910 + "function-bind", 911 + "get-intrinsic", 912 + "gopd", 913 + "has-property-descriptors" 914 + ] 915 + }, 916 + "setimmediate@1.0.5": { 917 + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" 918 + }, 339 919 "source-map-js@1.2.1": { 340 920 "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" 341 921 }, 342 922 "split-on-first@3.0.0": { 343 923 "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==" 344 924 }, 925 + "string_decoder@1.1.1": { 926 + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 927 + "dependencies": [ 928 + "safe-buffer" 929 + ] 930 + }, 931 + "strtok3@10.3.4": { 932 + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", 933 + "dependencies": [ 934 + "@tokenizer/token" 935 + ] 936 + }, 937 + "strtok3@2.3.0": { 938 + "integrity": "sha512-AA67/1atBh7X0fUTDevjW89by2ZkY9RZAnkwusx5Yc1COYf0ruUbpYOOIs03SnRA1CF9K3+BtRXKOEtKhAXVaQ==", 939 + "dependencies": [ 940 + "debug", 941 + "then-read-stream", 942 + "token-types@1.3.2" 943 + ] 944 + }, 345 945 "subsonic-api@3.2.0": { 346 946 "integrity": "sha512-BADBQ2hONdLb3agCiSDzNzTIFLWJAuxJTUJvC2zDFvXUVfnK3yy7r8xFu3NkrQl8p5UVI7q8Qfm62N1lFxWbww==" 347 947 }, 348 948 "supports-preserve-symlinks-flag@1.0.0": { 349 949 "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" 350 950 }, 951 + "then-read-stream@1.5.1": { 952 + "integrity": "sha512-I+iiemYWhp1ysJQEioqpEICgvHlqHS5WrQGZkboFLs7Jm350Kvq4cN3qRCzHpETUuq5+NsdrdWEg6M0NFxtwtQ==", 953 + "deprecated": true 954 + }, 955 + "throttle-debounce@5.0.2": { 956 + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==" 957 + }, 958 + "tinyqueue@1.2.3": { 959 + "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==" 960 + }, 961 + "token-types@1.3.2": { 962 + "integrity": "sha512-LemYprKRfZPUiwVEMIL8fIP/cvZBpMds1PklsyoQyLZdKk7SQlldNGzw4TTrg2MnWLGSkMM6gUa1EW0h1d72fg==", 963 + "dependencies": [ 964 + "ieee754" 965 + ] 966 + }, 967 + "token-types@6.1.1": { 968 + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", 969 + "dependencies": [ 970 + "@borewit/text-codec@0.1.1", 971 + "@tokenizer/token", 972 + "ieee754" 973 + ] 974 + }, 975 + "traverse@0.3.9": { 976 + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" 977 + }, 978 + "tslib@1.14.1": { 979 + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" 980 + }, 981 + "typedarray-to-buffer@3.1.5": { 982 + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", 983 + "dependencies": [ 984 + "is-typedarray" 985 + ] 986 + }, 351 987 "uc.micro@2.1.0": { 352 988 "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" 353 989 }, 990 + "uint8array-extras@1.5.0": { 991 + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==" 992 + }, 354 993 "uint8arrays@5.1.0": { 355 994 "integrity": "sha512-vA6nFepEmlSKkMBnLBaUMVvAC4G3CTmO58C12y4sq6WPDOR7mOFYOi7GlrQ4djeSbP6JG9Pv9tJDM97PedRSww==", 356 995 "dependencies": [ ··· 371 1010 "dependencies": [ 372 1011 "punycode" 373 1012 ] 1013 + }, 1014 + "use-sync-external-store@1.6.0_react@19.2.0": { 1015 + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", 1016 + "dependencies": [ 1017 + "react" 1018 + ] 1019 + }, 1020 + "utf8-buffer@1.0.0": { 1021 + "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==" 1022 + }, 1023 + "util-deprecate@1.0.2": { 1024 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 1025 + }, 1026 + "util@0.10.4": { 1027 + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 1028 + "dependencies": [ 1029 + "inherits@2.0.3" 1030 + ] 1031 + }, 1032 + "webamp@2.2.0_redux@5.0.1_react@19.2.0_react-dom@19.2.0__react@19.2.0": { 1033 + "integrity": "sha512-XzKr65Z4d+4rxA1J//aPkZRqvPS0aqAxpryNKaWt/EDQ4uCJadxjr966QElagH+iZxWMCDekW5dV/dTx5b+WPQ==", 1034 + "dependencies": [ 1035 + "@redux-devtools/extension", 1036 + "@sentry/browser", 1037 + "ani-cursor", 1038 + "butterchurn", 1039 + "butterchurn-presets", 1040 + "classnames", 1041 + "fscreen", 1042 + "invariant", 1043 + "jszip", 1044 + "lodash", 1045 + "milkdrop-preset-converter-aws", 1046 + "music-metadata@11.9.0", 1047 + "music-metadata-browser", 1048 + "react", 1049 + "react-dom", 1050 + "react-redux", 1051 + "redux", 1052 + "redux-sentry-middleware", 1053 + "redux-thunk", 1054 + "reselect", 1055 + "strtok3@10.3.4", 1056 + "tinyqueue", 1057 + "winamp-eqf" 1058 + ] 1059 + }, 1060 + "winamp-eqf@1.0.0": { 1061 + "integrity": "sha512-yUIb4+lTYBKP4L6nPXdDj1CQBXlJ+/PrNAkT1VbTAgeFjX8lPxAthsUE5NxQP4s8SO4YMJemsrErZ49Bh+/Veg==" 374 1062 }, 375 1063 "xxh32@2.0.5": { 376 1064 "integrity": "sha512-glQIaPvLHV4xG2Sn0E4mZWY25JT34+XcG4e2c8OMIH2SXxVrm6MmJ8miCsqGBLtf+rn2YcaeS11vq/66vkXGUQ==" ··· 693 1381 "workspace": { 694 1382 "dependencies": [ 695 1383 "jsr:@fry69/deep-diff@~0.1.10", 1384 + "jsr:@mary/ds-queue@~0.1.3", 696 1385 "jsr:@mys/m-rpc@~0.12.2", 697 1386 "jsr:@mys/worker-fn@^3.2.1", 698 1387 "jsr:@okikio/transferables@^1.0.2", 699 1388 "jsr:@orama/orama@^2.0.6", 700 1389 "jsr:@std/fs@^1.0.19", 701 1390 "jsr:@std/path@^1.1.2", 1391 + "jsr:@vicary/debounce-microtask@~0.1.8", 1392 + "npm:98.css@~0.1.21", 702 1393 "npm:alien-signals@3", 703 1394 "npm:idb-keyval@^6.2.2", 704 1395 "npm:morphdom@^2.7.7", 705 1396 "npm:query-string@^9.3.1", 706 1397 "npm:subsonic-api@^3.2.0", 1398 + "npm:throttle-debounce@^5.0.2", 707 1399 "npm:uint8arrays@^5.1.0", 708 1400 "npm:uri-js@^4.4.1", 1401 + "npm:webamp@^2.2.0", 709 1402 "npm:xxh32@^2.0.5" 710 1403 ] 711 1404 }
+18 -1
src/common/signal.js
··· 1 1 import deepDiff from "@fry69/deep-diff"; 2 - import { setActiveSub, signal as alienSignal } from "alien-signals"; 2 + import { 3 + endBatch, 4 + setActiveSub, 5 + signal as alienSignal, 6 + startBatch, 7 + } from "alien-signals"; 3 8 4 9 export * from "alien-signals"; 5 10 6 11 /** 7 12 * @import {Signal, SignalReader, SignalWriter} from "./signal.d.ts" 8 13 */ 14 + 15 + /** 16 + * @param {function(): void} fn 17 + */ 18 + export const batch = (fn) => { 19 + startBatch(); 20 + try { 21 + fn(); 22 + } finally { 23 + endBatch(); 24 + } 25 + }; 9 26 10 27 /** 11 28 * @template T
+84 -16
src/common/worker.js
··· 1 + import Queue from "@mary/ds-queue"; 1 2 import { defineWorkerFn, useWorkerFn } from "@mys/worker-fn"; 2 3 import { getTransferables } from "@okikio/transferables"; 3 - 4 + import { debounceMicrotask } from "@vicary/debounce-microtask"; 4 5 import { xxh32 } from "xxh32"; 6 + import { batch } from "./signal.js"; 5 7 6 8 /** 7 9 * @import {MRpcCallOptions, WorkerGlobalScope} from "@mys/m-rpc"; ··· 57 59 export function announce( 58 60 name, 59 61 args, 60 - context = /** @type {WorkerGlobalScope} */ (globalThis), 62 + context, 61 63 ) { 62 - const transferables = getTransferables(args); 63 - context.postMessage(constructMsg(name, args), { transfer: transferables }); 64 + outgoing.enqueue(announcement(name, args)); 65 + flushOutgoingAnnouncements(context); 64 66 } 65 67 66 68 /** ··· 74 76 fn, 75 77 context = /** @type {WorkerGlobalScope} */ (globalThis), 76 78 ) { 77 - context.addEventListener( 78 - "message", 79 - /** @param {any} event */ (event) => { 80 - const announcement = /** @type {Announcement<T>} */ (event.data); 81 - const { ns, type } = announcement; 79 + if (!context.incoming) { 80 + context.addEventListener("message", incomingAnnouncementsHandler(context)); 81 + context.incoming = {}; 82 + } 82 83 83 - if (announcement.name !== name) return; 84 - if (ns !== ANNOUNCEMENT || type !== ANNOUNCEMENT) return; 85 - 86 - fn(announcement.args); 87 - }, 88 - ); 84 + context.incoming[name] = debounceMicrotask(fn, { updateArguments: true }); 89 85 } 90 86 91 87 //////////////////////////////////////////// ··· 136 132 * @param {T} args 137 133 * @returns {Announcement<T>} 138 134 */ 139 - function constructMsg(name, args) { 135 + function announcement(name, args) { 140 136 return { 141 137 ns: ANNOUNCEMENT, 142 138 name, ··· 146 142 args, 147 143 }; 148 144 } 145 + 146 + /** 147 + * Process incoming announcements. 148 + */ 149 + const flushIncomingAnnouncements = debounceMicrotask( 150 + /** 151 + * @param {MessagePort | Worker | WorkerGlobalScope} [context] Uses `globalThis` by default. 152 + */ 153 + (context = /** @type {WorkerGlobalScope} */ (globalThis)) => { 154 + /** @type {Announcement<any>[]} */ 155 + const arr = []; 156 + 157 + for (const a of incoming.drain()) { 158 + arr.push(a); 159 + } 160 + 161 + batch(() => { 162 + arr.forEach((announcement) => { 163 + context.incoming[announcement.name]?.(announcement.args); 164 + }); 165 + }); 166 + }, 167 + ); 168 + 169 + /** 170 + * Process outgoing announcements. 171 + */ 172 + const flushOutgoingAnnouncements = debounceMicrotask( 173 + /** 174 + * @param {MessagePort | Worker | WorkerGlobalScope} [context] Uses `globalThis` by default. 175 + */ 176 + (context = /** @type {WorkerGlobalScope} */ (globalThis)) => { 177 + /** @type {Announcement<any>[]} */ 178 + const arr = []; 179 + 180 + for (const a of outgoing.drain()) { 181 + arr.push(a); 182 + } 183 + 184 + const transferables = getTransferables(arr); 185 + context.postMessage(arr, { transfer: transferables }); 186 + }, 187 + ); 188 + 189 + /** 190 + * @type {Queue<Announcement<any>>} 191 + */ 192 + const incoming = new Queue(); 193 + 194 + /** 195 + * @param {MessagePort | Worker | WorkerGlobalScope} context 196 + */ 197 + function incomingAnnouncementsHandler(context) { 198 + /** @param {any} event */ 199 + return (event) => { 200 + const arr = /** @type {Announcement<any>[]} */ (event.data); 201 + 202 + if (Array.isArray(arr)) { 203 + arr.forEach((announcement) => { 204 + const { ns, type } = announcement; 205 + if (ns !== ANNOUNCEMENT || type !== ANNOUNCEMENT) return; 206 + incoming.enqueue(announcement); 207 + flushIncomingAnnouncements(context); 208 + }); 209 + } 210 + }; 211 + } 212 + 213 + /** 214 + * @type {Queue<Announcement<any>>} 215 + */ 216 + const outgoing = new Queue();
+2 -2
src/component/engine/queue/element.js
··· 3 3 import { listen, use } from "@common/worker.js"; 4 4 5 5 /** 6 - * @import {Actions, ActionsProxied, Item} from "./types.d.ts" 6 + * @import {ActionsProxied, Item} from "./types.d.ts" 7 7 */ 8 8 9 9 //////////////////////////////////////////// ··· 11 11 //////////////////////////////////////////// 12 12 13 13 /** 14 - * @implements {Actions} 14 + * @implements {ActionsProxied} 15 15 */ 16 16 class QueueEngine extends DiffuseElement { 17 17 constructor() {
+16 -11
src/component/engine/queue/worker.js
··· 1 1 import { announce, define, ostiary } from "@common/worker.js"; 2 - import { effect, signal } from "@common/signal.js"; 2 + import { batch, effect, signal } from "@common/signal.js"; 3 3 import { arrayShuffle } from "@common/index.js"; 4 4 5 5 /** ··· 40 40 // 41 41 // What about past queue items? 42 42 43 - $future.value = fill([]); 44 - 45 43 // Automatically insert track if there isn't any 46 - if (!$now.value) return shift(); 44 + if (!$now.value) _shift(fill([])); 45 + else $future.value = fill([]); 47 46 } 48 47 49 48 /** 50 49 * @type {Actions['shift']} 51 50 */ 52 51 export function shift() { 53 - const n = $now.value; 54 - const f = $future.value; 55 - 56 - $now.value = f[0] ?? null; 57 - 58 - if (n) $past.value = [...$past.value, n]; 59 - $future.value = fill(f.slice(1)); 52 + return _shift(); 60 53 } 61 54 62 55 /** ··· 132 125 133 126 return [...future, ...poolSelection]; 134 127 } 128 + 129 + /** 130 + * @param {Item[]} [future] 131 + */ 132 + export function _shift(future) { 133 + const n = $now.value; 134 + const f = future ?? $future.value; 135 + 136 + if (n) $past.value = [...$past.value, n]; 137 + $future.value = fill(f.slice(1)); 138 + $now.value = f[0] ?? null; 139 + }
+2 -1
src/component/orchestrator/queue-tracks/element.js
··· 1 1 import { DiffuseElement, query } from "@common/element.js"; 2 + import { untracked } from "@common/signal.js"; 2 3 3 4 /** 4 5 * @import {InputElement, OutputElement, Track} from "@component/core/types.d.ts" ··· 41 42 // Watch tracks collection 42 43 this.effect(() => { 43 44 const tracks = this.output.tracks.collection(); 44 - this.poolAvailable(tracks); 45 + untracked(() => this.poolAvailable(tracks)); 45 46 }); 46 47 } 47 48
+2 -2
src/styles/theme/webamp/index.css
··· 1 1 @font-face { 2 2 font-family: "Pixelated MS Sans Serif"; 3 - src: url("/node_modules/98.css/fonts/converted/ms_sans_serif.woff2") format("woff2"); 3 + src: url("/fonts/ms_sans_serif.woff2") format("woff2"); 4 4 font-weight: normal; 5 5 font-style: normal; 6 6 } 7 7 8 8 @font-face { 9 9 font-family: "Pixelated MS Sans Serif"; 10 - src: url("/node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2") format("woff2"); 10 + src: url("/fonts/ms_sans_serif_bold.woff2") format("woff2"); 11 11 font-weight: 700; 12 12 font-style: normal; 13 13 }
+149
src/theme/webamp/index.js
··· 1 + import Webamp from "webamp/lazy"; 2 + import { throttle } from "throttle-debounce"; 3 + 4 + // import "@component/orchestrator/process-tracks/element.js"; 5 + import "@component/orchestrator/queue-tracks/element.js"; 6 + import "@component/output/indexed-db/element.js"; 7 + import "@component/processor/metadata/element.js"; 8 + 9 + import * as Input from "@component/input/opensubsonic/element.js"; 10 + import * as Queue from "@component/engine/queue/element.js"; 11 + 12 + import { component } from "@common/element.js"; 13 + import { effect, signal, untracked } from "@common/signal.js"; 14 + import deepDiff from "@fry69/deep-diff"; 15 + import { debounceMicrotask } from "@vicary/debounce-microtask"; 16 + 17 + /** 18 + * @import {Diff} from "@fry69/deep-diff" 19 + * @import {URLTrack} from "webamp" 20 + * 21 + * @import {Track} from "@component/core/types.d.ts" 22 + * @import {Item} from "@component/engine/queue/types.d.ts" 23 + */ 24 + 25 + const input = component(Input); 26 + const queue = component(Queue); 27 + 28 + globalThis.queue = queue; 29 + 30 + //////////////////////////////////////////// 31 + // ⚡ 32 + //////////////////////////////////////////// 33 + 34 + /** @type {import("webamp/lazy").default} */ 35 + const amp = new /** @type {any} */ (Webamp)({ 36 + enableMediaSession: true, 37 + initialTracks: [], 38 + 39 + /** */ 40 + handleLoadListEvent: async () => { 41 + // TODO 42 + return [ 43 + /* Array of Tracks */ 44 + ]; 45 + }, 46 + 47 + /** 48 + * @param {any} tracks 49 + */ 50 + handleSaveListEvent: (tracks) => { 51 + // TODO 52 + }, 53 + }); 54 + 55 + // Override 56 + const loadFromUrl = amp.media.loadFromUrl.bind(amp.media); 57 + 58 + /** 59 + * @param {string} uri 60 + * @param {boolean} autoPlay 61 + */ 62 + async function loadOverride(uri, autoPlay) { 63 + const resp = await input.resolve({ method: "GET", uri }); 64 + if (!resp) throw new Error("Failed to resolve URI"); 65 + return await loadFromUrl(resp.url, autoPlay); 66 + } 67 + 68 + amp.media.loadFromUrl = loadOverride.bind(amp.media); 69 + 70 + // Render 71 + const ampNode = document.createElement("div"); 72 + ampNode.style = 73 + "height: 100vh; left: 0; position: absolute; top: 0; width: 100%; z-index: -1000;"; 74 + document.body.appendChild(ampNode); 75 + amp.renderWhenReady(ampNode); 76 + 77 + //////////////////////////////////////////// 78 + // 🌊 79 + //////////////////////////////////////////// 80 + 81 + const $currTrack = signal(/** @type {null | number} */ (null)); 82 + const $playlist = signal(/** @type {Item[]} */ ([])); 83 + 84 + /** 85 + * Observe changes in Webamp's internal store. 86 + */ 87 + amp.store.subscribe(() => { 88 + const state = amp.store.getState(); 89 + $currTrack.value = state.playlist.currentTrack; 90 + }); 91 + 92 + /** 93 + * Whenever the queue changes update the playlist. 94 + */ 95 + effect(() => { 96 + const now = queue.now(); 97 + const past = queue.past(); 98 + const future = queue.future(); 99 + 100 + const playlist = [ 101 + ...past, 102 + ...(now ? [now] : []), 103 + ...future, 104 + ]; 105 + 106 + const diff = deepDiff.diff($playlist.value, playlist, () => true); 107 + 108 + diff?.forEach((d) => { 109 + // TODO: Handle case where an item is inserted into queue at a position that's not the end. 110 + // console.log(d); 111 + 112 + if (d.kind !== "A") return; 113 + if (d.item.kind === "N") { 114 + const item = /** @type {Item} */ (/** @type {unknown} */ (d.item.rhs)); 115 + if (!item) return; 116 + 117 + /** @type {URLTrack} */ 118 + const urlTrack = { 119 + url: item.uri, 120 + metaData: { 121 + title: item.tags?.title || "", 122 + artist: item.tags?.artist || "", 123 + album: item.tags?.album, 124 + }, 125 + duration: item.stats?.duration, 126 + }; 127 + 128 + amp.appendTracks([urlTrack]); 129 + } 130 + }); 131 + 132 + if (!diff) return; 133 + 134 + $playlist.value = playlist; 135 + 136 + if (untracked($currTrack.get) === null) { 137 + amp.setCurrentTrack(past.length); 138 + } 139 + }); 140 + 141 + /** 142 + * Whenever Webamp's queue changes, 143 + * reflect the change in our queue too. 144 + */ 145 + effect(() => { 146 + if (($currTrack.value ?? 0) > untracked(queue.past).length) { 147 + queue.shift(); 148 + } 149 + });
+67
src/theme/webamp/index.vto
··· 1 + <html lang="en"> 2 + <head> 3 + <meta charset="UTF-8" /> 4 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 5 + 6 + <title>Diffuse</title> 7 + 8 + <link href="/styles/theme/webamp/index.css" rel="stylesheet" /> 9 + </head> 10 + <body> 11 + <!-- 12 + 13 + UI 14 + 15 + --> 16 + <main> 17 + <div class="desktop"> 18 + <!-- INPUT --> 19 + <!--<a href="/configurator/input/" target="_blank" class="button desktop__item"> 20 + <img src="/images/icons/windows_98/cd_audio_cd_a-4.png" width="32" /> 21 + <label>Manage audio inputs</label> 22 + </a>--> 23 + 24 + <!-- OUTPUT --> 25 + <!--<a href="/configurator/output/" target="_blank" class="button desktop__item"> 26 + <img src="/images/icons/windows_98/directory_open_file_mydocs_2k-2.png" width="32" /> 27 + <label>Manage user data</label> 28 + </a>--> 29 + 30 + <!-- BROWSE --> 31 + <!-- TODO --> 32 + </div> 33 + </main> 34 + 35 + <!-- 36 + 37 + COMPONENTS 38 + 39 + --> 40 + <de-queue></de-queue> 41 + 42 + <!-- Inputs, Outputs & Processors --> 43 + <di-opensubsonic></di-opensubsonic> 44 + <do-indexed-db></do-indexed-db> 45 + <dp-metadata></dp-metadata> 46 + 47 + <!-- Orchestrators --> 48 + <do-process-tracks 49 + input-selector="di-opensubsonic" 50 + metadata-processor-selector="dp-metadata" 51 + output-selector="do-indexed-db" 52 + ></do-process-tracks> 53 + 54 + <do-queue-tracks 55 + input-selector="di-opensubsonic" 56 + output-selector="do-indexed-db" 57 + queue-engine-selector="de-queue" 58 + ></do-queue-tracks> 59 + 60 + <!-- 61 + 62 + SCRIPTS 63 + 64 + --> 65 + <script src="/theme/webamp/index.js" type="module"></script> 66 + </body> 67 + </html>