refactor(hls/dash): externalize hls.js and dashjs to reduce initial bundle size

This commit is contained in:
Daniel 2026-03-19 12:20:41 -05:00
parent e4afdf833c
commit 4e2a595504
10 changed files with 145 additions and 107 deletions

View file

@ -11,13 +11,15 @@
"@ffmpeg/util": "^0.12.2", "@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1", "@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0", "@neutralinojs/lib": "^6.5.0",
"@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4", "@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0", "appwrite": "^23.0.0",
"butterchurn": "^2.6.7", "butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7", "butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0", "client-zip": "^2.5.0",
"cookie-session": "^2.1.1", "cookie-session": "^2.1.1",
"dashjs": "^5.1.1", "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hls.js": "^1.6.15", "hls.js": "^1.6.15",
"jose": "^6.2.0", "jose": "^6.2.0",
@ -27,6 +29,7 @@
"pocketbase": "^0.26.8", "pocketbase": "^0.26.8",
"simple-icons": "^16.12.0", "simple-icons": "^16.12.0",
"svgo": "^4.0.1", "svgo": "^4.0.1",
"url-toolkit": "^2.2.5",
"uuid": "^13.0.0", "uuid": "^13.0.0",
}, },
"devDependencies": { "devDependencies": {
@ -537,6 +540,8 @@
"@svta/cml-xml": ["@svta/cml-xml@1.0.1", "", { "peerDependencies": { "@svta/cml-utils": "1.0.1" } }, "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g=="], "@svta/cml-xml": ["@svta/cml-xml@1.0.1", "", { "peerDependencies": { "@svta/cml-utils": "1.0.1" } }, "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g=="],
"@svta/common-media-library": ["@svta/common-media-library@0.18.1", "", {}, "sha512-VMj1jI8OWphurcozF+dezABUm9Mht6iAsSiKsFUKVT35fddOowvLoGz23Gx6lEHaAHkDy9o/aVi5s9DSp3K15Q=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
@ -687,7 +692,7 @@
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="], "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
"dashjs": ["dashjs@5.1.1", "", { "dependencies": { "@svta/cml-608": "1.0.1", "@svta/cml-cmcd": "1.0.1", "@svta/cml-cmsd": "1.0.1", "@svta/cml-dash": "1.0.1", "@svta/cml-id3": "1.0.1", "@svta/cml-request": "1.0.1", "@svta/cml-xml": "1.0.1", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.10", "fast-deep-equal": "3.1.3", "html-entities": "^2.5.2", "imsc": "^1.1.5", "localforage": "^1.10.0", "path-browserify": "^1.0.1", "ua-parser-js": "^1.0.37" } }, "sha512-BzNXlUgzEjhuZ5M5hlSp1qIyQHZ7NpXAR0loP9DAAFVZj/ntL1DHeZ7qp/L3bvI4rq50X5indkAZQ3zEHWJoCA=="], "dashjs": ["dashjs@https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz", { "dependencies": { "@svta/cml-608": "1.0.1", "@svta/cml-cmcd": "1.0.1", "@svta/cml-cmsd": "1.0.1", "@svta/cml-dash": "1.0.1", "@svta/cml-id3": "1.0.1", "@svta/cml-request": "1.0.1", "@svta/cml-xml": "1.0.1", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.10", "fast-deep-equal": "3.1.3", "html-entities": "^2.5.2", "imsc": "^1.1.5", "localforage": "^1.10.0", "path-browserify": "^1.0.1", "ua-parser-js": "^1.0.37" } }, "sha512-lhD1tvEe4PO6t086flm6WfO2Jt1EOIolDQ17F3vLomMthaL1RH96h8peIQTvrDvfSJTRXeisL+CwPj4oud5e9g=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
@ -791,6 +796,8 @@
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="], "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@ -1401,6 +1408,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"url-toolkit": ["url-toolkit@2.2.5", "", {}, "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="],
"utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="], "utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],

View file

@ -416,6 +416,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS'; const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS';
const player = new Player(audioPlayer, api, currentQuality); const player = new Player(audioPlayer, api, currentQuality);
await player.init();
window.monochromePlayer = player; window.monochromePlayer = player;
// Initialize tracker // Initialize tracker
@ -1136,7 +1137,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn'); const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active'); if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false; player.shuffleActive = false;
player.playTrackFromQueue(); await player.playTrackFromQueue();
} }
} catch (error) { } catch (error) {
console.error('Failed to play album:', error); console.error('Failed to play album:', error);
@ -1167,7 +1168,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn'); const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active'); if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false; player.shuffleActive = false;
player.playTrackFromQueue(); await player.playTrackFromQueue();
const { showNotification } = await loadDownloadsModule(); const { showNotification } = await loadDownloadsModule();
showNotification('Shuffling album'); showNotification('Shuffling album');
@ -1235,7 +1236,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const shuffleBtn = document.getElementById('shuffle-btn'); const shuffleBtn = document.getElementById('shuffle-btn');
if (shuffleBtn) shuffleBtn.classList.remove('active'); if (shuffleBtn) shuffleBtn.classList.remove('active');
player.shuffleActive = false; player.shuffleActive = false;
player.playTrackFromQueue(); await player.playTrackFromQueue();
const { showNotification } = await loadDownloadsModule(); const { showNotification } = await loadDownloadsModule();
showNotification('Shuffling artist discography'); showNotification('Shuffling artist discography');
@ -2183,7 +2184,7 @@ document.addEventListener('DOMContentLoaded', async () => {
if (tracks.length > 0) { if (tracks.length > 0) {
player.setQueue(tracks, 0); player.setQueue(tracks, 0);
document.getElementById('shuffle-btn').classList.remove('active'); document.getElementById('shuffle-btn').classList.remove('active');
player.playTrackFromQueue(); await player.playTrackFromQueue();
} }
} catch (error) { } catch (error) {
console.error('Failed to play playlist:', error); console.error('Failed to play playlist:', error);
@ -2369,7 +2370,7 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
player.setQueue(allTracks, 0); player.setQueue(allTracks, 0);
player.playTrackFromQueue(); await player.playTrackFromQueue();
} else { } else {
throw new Error('No tracks found across all albums'); throw new Error('No tracks found across all albums');
} }
@ -2398,7 +2399,7 @@ document.addEventListener('DOMContentLoaded', async () => {
} }
player.setQueue(likedTracks, 0); player.setQueue(likedTracks, 0);
document.getElementById('shuffle-btn').classList.remove('active'); document.getElementById('shuffle-btn').classList.remove('active');
player.playTrackFromQueue(); await player.playTrackFromQueue();
} }
} catch (error) { } catch (error) {
console.error('Failed to shuffle liked tracks:', error); console.error('Failed to shuffle liked tracks:', error);

View file

@ -66,15 +66,15 @@ class CommandPalette {
} }
init() { init() {
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', async (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') { if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault(); e.preventDefault();
this.toggle(); await this.toggle();
} }
}); });
this.input.addEventListener('input', () => this.handleInput()); this.input.addEventListener('input', () => this.handleInput());
this.input.addEventListener('keydown', (e) => this.handleKeydown(e)); this.input.addEventListener('keydown', async (e) => await this.handleKeydown(e));
this.overlay.addEventListener('click', (e) => { this.overlay.addEventListener('click', (e) => {
if (e.target === this.overlay) this.close(); if (e.target === this.overlay) this.close();
@ -83,17 +83,17 @@ class CommandPalette {
this.cacheAllSettings(); this.cacheAllSettings();
} }
toggle() { async toggle() {
if (this.isOpen) this.close(); if (this.isOpen) this.close();
else this.open(); else await this.open();
} }
open() { async open() {
this.isOpen = true; this.isOpen = true;
this.overlay.style.display = 'flex'; this.overlay.style.display = 'flex';
this.input.value = '>'; this.input.value = '>';
this.input.focus(); this.input.focus();
this.handleInput(); await this.handleInput();
} }
close() { close() {
@ -101,18 +101,18 @@ class CommandPalette {
this.overlay.style.display = 'none'; this.overlay.style.display = 'none';
} }
handleInput() { async handleInput() {
const value = this.input.value; const value = this.input.value;
this.selectedIndex = 0; this.selectedIndex = 0;
if (!value.startsWith('>')) { if (!value.startsWith('>')) {
this.renderResults([ await this.renderResults([
{ {
name: 'Type > to use commands', name: 'Type > to use commands',
description: 'e.g. >theme White, >play The Whole World Is Free', description: 'e.g. >theme White, >play The Whole World Is Free',
action: () => { action: async () => {
this.input.value = '>'; this.input.value = '>';
this.handleInput(); await this.handleInput();
}, },
type: 'hint', type: 'hint',
}, },
@ -124,7 +124,7 @@ class CommandPalette {
const match = fullQuery.match(/^(\S+)(?:\s+(.*))?$/); const match = fullQuery.match(/^(\S+)(?:\s+(.*))?$/);
if (!match) { if (!match) {
this.renderDefaultCommands(); await this.renderDefaultCommands();
return; return;
} }
@ -140,7 +140,7 @@ class CommandPalette {
return; return;
} }
this.renderResults([ await this.renderResults([
{ {
name: `Execute: ${command.name} ${args}`, name: `Execute: ${command.name} ${args}`,
description: args ? `Run ${command.name} for "${args}"` : command.description, description: args ? `Run ${command.name} for "${args}"` : command.description,
@ -153,11 +153,11 @@ class CommandPalette {
this.debouncedSearch(cmdName, args.trim()); this.debouncedSearch(cmdName, args.trim());
} }
} else { } else {
this.renderDefaultCommands(cmdName); await this.renderDefaultCommands(cmdName);
} }
} }
handleKeydown(e) { async handleKeydown(e) {
if (e.key === 'ArrowDown') { if (e.key === 'ArrowDown') {
e.preventDefault(); e.preventDefault();
this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1); this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
@ -168,13 +168,13 @@ class CommandPalette {
this.updateSelection(); this.updateSelection();
} else if (e.key === 'Enter') { } else if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
this.executeSelected(); await this.executeSelected();
} else if (e.key === 'Escape') { } else if (e.key === 'Escape') {
this.close(); this.close();
} }
} }
renderDefaultCommands(filter = '') { async renderDefaultCommands(filter = '') {
let cmds = this.commands; let cmds = this.commands;
if (filter) { if (filter) {
if (Fuse) { if (Fuse) {
@ -185,20 +185,20 @@ class CommandPalette {
} }
} }
this.renderResults( await this.renderResults(
cmds.map((c) => ({ cmds.map((c) => ({
name: c.name, name: c.name,
description: c.description, description: c.description,
action: () => { action: async () => {
this.input.value = `>${c.name} `; this.input.value = `>${c.name} `;
this.handleInput(); await this.handleInput();
}, },
type: 'command', type: 'command',
})) }))
); );
} }
renderResults(results) { async renderResults(results) {
this.results = results; this.results = results;
this.resultsContainer.innerHTML = ''; this.resultsContainer.innerHTML = '';
@ -222,9 +222,9 @@ class CommandPalette {
<div style="display: flex; flex-direction: column;"><span class="command-result-name" style="font-weight: 500;">${result.name}</span><span class="command-result-desc" style="font-size: 0.8rem; opacity: 0.7;">${result.description || ''}</span></div> <div style="display: flex; flex-direction: column;"><span class="command-result-name" style="font-weight: 500;">${result.name}</span><span class="command-result-desc" style="font-size: 0.8rem; opacity: 0.7;">${result.description || ''}</span></div>
</div> </div>
`; `;
div.addEventListener('click', () => { div.addEventListener('click', async () => {
this.selectedIndex = index; this.selectedIndex = index;
this.executeSelected(); await this.executeSelected();
}); });
this.resultsContainer.appendChild(div); this.resultsContainer.appendChild(div);
}); });
@ -242,16 +242,16 @@ class CommandPalette {
}); });
} }
executeSelected() { async executeSelected() {
const result = this.results[this.selectedIndex]; const result = this.results[this.selectedIndex];
if (result && result.action) { if (result && result.action) {
result.action(); await result.action();
if (result.type !== 'hint') { if (result.type !== 'hint') {
this.close(); this.close();
} }
} else if (result && result.type === 'command') { } else if (result && result.type === 'command') {
this.input.value = `>${result.name} `; this.input.value = `>${result.name} `;
this.handleInput(); await this.handleInput();
} }
} }
@ -273,7 +273,7 @@ class CommandPalette {
showNotification(message); showNotification(message);
} }
handleQueue(args) { async handleQueue(args) {
const player = window.monochromePlayer; const player = window.monochromePlayer;
const ui = window.monochromeUi; const ui = window.monochromeUi;
@ -283,7 +283,7 @@ class CommandPalette {
} }
if (!args || !args.trim()) { if (!args || !args.trim()) {
this.renderResults( await this.renderResults(
[ [
{ name: '>queue wipe', description: 'Clear the queue and stop playback' }, { name: '>queue wipe', description: 'Clear the queue and stop playback' },
{ name: '>queue like all', description: 'Like all tracks in the current queue' }, { name: '>queue like all', description: 'Like all tracks in the current queue' },
@ -291,9 +291,9 @@ class CommandPalette {
].map((c) => ({ ].map((c) => ({
...c, ...c,
type: 'command', type: 'command',
action: () => { action: async () => {
this.input.value = c.name; this.input.value = c.name;
this.handleInput(); await this.handleInput();
}, },
})) }))
); );
@ -358,11 +358,11 @@ class CommandPalette {
this.close(); this.close();
} }
handleNavigation(args) { async handleNavigation(args) {
const validPages = ['home', 'library', 'recent', 'settings', 'unreleased', 'about', 'download']; const validPages = ['home', 'library', 'recent', 'settings', 'unreleased', 'about', 'download'];
if (!args || !args.trim()) { if (!args || !args.trim()) {
this.renderResults( await this.renderResults(
validPages.map((p) => ({ validPages.map((p) => ({
name: `>go ${p}`, name: `>go ${p}`,
description: `Navigate to ${p}`, description: `Navigate to ${p}`,
@ -386,9 +386,9 @@ class CommandPalette {
} }
} }
handleSleepTimer(args) { async handleSleepTimer(args) {
if (!args || !args.trim()) { if (!args || !args.trim()) {
this.renderResults( await this.renderResults(
[15, 30, 45, 60, 120].map((m) => ({ [15, 30, 45, 60, 120].map((m) => ({
name: `>sleep ${m}`, name: `>sleep ${m}`,
description: `Set sleep timer for ${m} minutes`, description: `Set sleep timer for ${m} minutes`,
@ -418,7 +418,7 @@ class CommandPalette {
} }
} }
handleQuality(args) { async handleQuality(args) {
const qualityMap = { const qualityMap = {
low: 'LOW', low: 'LOW',
high: 'HIGH', high: 'HIGH',
@ -452,7 +452,7 @@ class CommandPalette {
action: () => {}, action: () => {},
type: 'hint', type: 'hint',
}); });
this.renderResults(results); await this.renderResults(results);
return; return;
} }
@ -524,7 +524,7 @@ class CommandPalette {
async handleVisualizer(args) { async handleVisualizer(args) {
if (!args || !args.trim()) { if (!args || !args.trim()) {
this.renderResults( await this.renderResults(
[ [
{ name: '>visualizer toggle', description: 'Toggle visualizer on/off', cmd: 'toggle' }, { name: '>visualizer toggle', description: 'Toggle visualizer on/off', cmd: 'toggle' },
{ name: '>visualizer butterchurn', description: 'Set preset to Butterchurn', cmd: 'butterchurn' }, { name: '>visualizer butterchurn', description: 'Set preset to Butterchurn', cmd: 'butterchurn' },
@ -630,7 +630,7 @@ class CommandPalette {
const query = args.trim().toLowerCase(); const query = args.trim().toLowerCase();
if (!query) { if (!query) {
this.renderResults( await this.renderResults(
this.allSettings.map((setting) => ({ this.allSettings.map((setting) => ({
name: setting.label, name: setting.label,
description: `[${setting.tab}] ${setting.description}`, description: `[${setting.tab}] ${setting.description}`,
@ -659,7 +659,7 @@ class CommandPalette {
return; return;
} }
this.renderResults( await this.renderResults(
results.map((setting) => ({ results.map((setting) => ({
name: setting.label, name: setting.label,
description: `[${setting.tab}] ${setting.description}`, description: `[${setting.tab}] ${setting.description}`,
@ -713,9 +713,9 @@ class CommandPalette {
name: track.title, name: track.title,
description: `${track.artist?.name || 'Unknown'}${track.album?.title || 'Unknown'}`, description: `${track.artist?.name || 'Unknown'}${track.album?.title || 'Unknown'}`,
image: api.getCoverUrl(track.album?.cover, 80), image: api.getCoverUrl(track.album?.cover, 80),
action: () => { action: async () => {
window.monochromePlayer.setQueue([track], 0); window.monochromePlayer.setQueue([track], 0);
window.monochromePlayer.playTrackFromQueue(); await window.monochromePlayer.playTrackFromQueue();
this.close(); this.close();
}, },
type: 'result', type: 'result',
@ -769,7 +769,7 @@ class CommandPalette {
} }
if (this.isOpen && results.length > 0) { if (this.isOpen && results.length > 0) {
this.renderResults(results); await this.renderResults(results);
} }
} }
@ -782,7 +782,7 @@ class CommandPalette {
if (results.items.length > 0) { if (results.items.length > 0) {
const track = results.items[0]; const track = results.items[0];
window.monochromePlayer.setQueue([track], 0); window.monochromePlayer.setQueue([track], 0);
window.monochromePlayer.playTrackFromQueue(); await window.monochromePlayer.playTrackFromQueue();
this.close(); this.close();
} }
} }

1
js/dash-media-player.ts Normal file
View file

@ -0,0 +1 @@
export { default as MediaPlayer } from '!/dashjs/src/streaming/MediaPlayer.js';

View file

@ -1,5 +1,4 @@
//js/player.js //js/player.js
import { MediaPlayer } from 'dashjs';
import { import {
REPEAT_MODE, REPEAT_MODE,
formatTime, formatTime,
@ -20,7 +19,8 @@ import {
} from './storage.js'; } from './storage.js';
import { audioContextManager } from './audio-context.js'; import { audioContextManager } from './audio-context.js';
import { db } from './db.js'; import { db } from './db.js';
import Hls from 'hls.js';
import('./dash-media-player.js');
import { SVG_CLOCK } from './icons.js'; import { SVG_CLOCK } from './icons.js';
export class Player { export class Player {
constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') { constructor(audioElement, api, quality = 'HI_RES_LOSSLESS') {
@ -52,7 +52,9 @@ export class Player {
this.sleepTimer = null; this.sleepTimer = null;
this.sleepTimerEndTime = null; this.sleepTimerEndTime = null;
this.sleepTimerInterval = null; this.sleepTimerInterval = null;
}
async init() {
// Apply audio effects when track is ready // Apply audio effects when track is ready
this.audio.addEventListener('canplay', () => { this.audio.addEventListener('canplay', () => {
this.applyAudioEffects(); this.applyAudioEffects();
@ -64,6 +66,7 @@ export class Player {
} }
// Initialize dash.js player // Initialize dash.js player
const { MediaPlayer } = await import('./dash-media-player.js');
this.dashPlayer = MediaPlayer().create(); this.dashPlayer = MediaPlayer().create();
this.dashPlayer.updateSettings({ this.dashPlayer.updateSettings({
streaming: { streaming: {
@ -452,8 +455,9 @@ export class Player {
} }
} }
setupHlsVideo(video, result, fallbackImg) { async setupHlsVideo(video, result, fallbackImg) {
const url = result.videoUrl || result.hlsUrl || result; const url = result.videoUrl || result.hlsUrl || result;
const Hls = (await import('hls.js')).default;
if (!url) return; if (!url) return;
if (this.hls) { if (this.hls) {
@ -471,9 +475,9 @@ export class Player {
this.hls = new Hls(); this.hls = new Hls();
this.hls.loadSource(url); this.hls.loadSource(url);
this.hls.attachMedia(video); this.hls.attachMedia(video);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.hls.on(Hls.Events.MANIFEST_PARSED, async () => {
video.play().catch(() => {}); video.play().catch(() => {});
this.setupVideoQualitySelector(); await this.setupVideoQualitySelector();
}); });
this.hls.on(Hls.Events.ERROR, (event, data) => { this.hls.on(Hls.Events.ERROR, (event, data) => {
if (data.fatal) { if (data.fatal) {
@ -490,9 +494,9 @@ export class Player {
} }
} else { } else {
video.src = url; video.src = url;
video.onerror = () => { video.onerror = async () => {
if (result && result.hlsUrl) { if (result && result.hlsUrl) {
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg); await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
} else if (fallbackImg) { } else if (fallbackImg) {
video.replaceWith(fallbackImg); video.replaceWith(fallbackImg);
} }
@ -500,8 +504,9 @@ export class Player {
} }
} }
setupVideoQualitySelector() { async setupVideoQualitySelector() {
if (!this.hls || !this.hls.levels || this.hls.levels.length === 0) return; if (!this.hls || !this.hls.levels || this.hls.levels.length === 0) return;
const Hls = (await import('hls.js')).default;
const qualityBtn = document.getElementById('fs-quality-btn'); const qualityBtn = document.getElementById('fs-quality-btn');
const qualityMenu = document.getElementById('fs-quality-menu'); const qualityMenu = document.getElementById('fs-quality-menu');
@ -802,7 +807,7 @@ export class Player {
if (this.playbackSequence !== currentSequence) return; if (this.playbackSequence !== currentSequence) return;
if (streamUrl.includes('.m3u8') || streamUrl.includes('application/vnd.apple.mpegurl')) { if (streamUrl.includes('.m3u8') || streamUrl.includes('application/vnd.apple.mpegurl')) {
this.setupHlsVideo(activeElement, streamUrl, null); await this.setupHlsVideo(activeElement, streamUrl, null);
} else if (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) { } else if (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) {
this.dashPlayer.initialize(activeElement, streamUrl, false); this.dashPlayer.initialize(activeElement, streamUrl, false);
this.dashInitialized = true; this.dashInitialized = true;

View file

@ -44,7 +44,6 @@ import {
createTrackFromSong, createTrackFromSong,
} from './tracker.js'; } from './tracker.js';
import { trackSearch, trackChangeSort } from './analytics.js'; import { trackSearch, trackChangeSort } from './analytics.js';
import Hls from 'hls.js';
fontSettings.applyFont(); fontSettings.applyFont();
fontSettings.applyFontSize(); fontSettings.applyFontSize();
@ -2652,12 +2651,13 @@ export class UIRenderer {
return items.filter((item) => !favoriteIds.has(item.id)); return items.filter((item) => !favoriteIds.has(item.id));
} }
setupHlsVideo(video, result, fallbackImg) { async setupHlsVideo(video, result, fallbackImg) {
if (!result) return; if (!result) return;
const url = typeof result === 'string' ? result : result.videoUrl || result.hlsUrl; const url = typeof result === 'string' ? result : result.videoUrl || result.hlsUrl;
if (!url) return; if (!url) return;
if (url.endsWith('.m3u8')) { if (url.endsWith('.m3u8')) {
const Hls = (await import('hls.js')).default;
if (Hls.isSupported()) { if (Hls.isSupported()) {
const hls = new Hls(); const hls = new Hls();
video._hls = hls; video._hls = hls;
@ -2692,17 +2692,17 @@ export class UIRenderer {
video.play().catch(() => {}); video.play().catch(() => {});
}); });
} }
video.onerror = () => { video.onerror = async () => {
if (result.hlsUrl) { if (result.hlsUrl) {
// HLS fallback (for some reason alot of animated covers js dont work on MP4 lol) // HLS fallback (for some reason alot of animated covers js dont work on MP4 lol)
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg); await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg);
} else { } else {
video.replaceWith(fallbackImg); video.replaceWith(fallbackImg);
} }
}; };
} }
replaceVideoArtwork(container, type, id, result) { async replaceVideoArtwork(container, type, id, result) {
const url = result.videoUrl || result.hlsUrl; const url = result.videoUrl || result.hlsUrl;
if (!url) return; if (!url) return;
@ -2722,9 +2722,9 @@ export class UIRenderer {
video.poster = img.src; video.poster = img.src;
video.onerror = () => { video.onerror = async () => {
if (video.src === result.videoUrl && result.hlsUrl) { if (video.src === result.videoUrl && result.hlsUrl) {
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img); await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
return; return;
} }
video.replaceWith(img); video.replaceWith(img);
@ -2732,9 +2732,9 @@ export class UIRenderer {
video.addEventListener( video.addEventListener(
'error', 'error',
(e) => { async (e) => {
if (video.src === result.videoUrl && result.hlsUrl) { if (video.src === result.videoUrl && result.hlsUrl) {
this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img); await this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, img);
return; return;
} }
console.warn('Video decoding error:', e); console.warn('Video decoding error:', e);
@ -2745,7 +2745,7 @@ export class UIRenderer {
img.replaceWith(video); img.replaceWith(video);
this.setupHlsVideo(video, result, img); await this.setupHlsVideo(video, result, img);
} }
} }
@ -2999,7 +2999,7 @@ export class UIRenderer {
if (!videoCoverUrl && tracks.length > 0) { if (!videoCoverUrl && tracks.length > 0) {
const firstTrack = tracks[0]; const firstTrack = tracks[0];
this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => { this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then(async (result) => {
if (result && this.currentPage === 'album' && this.currentAlbumId === albumId) { if (result && this.currentPage === 'album' && this.currentAlbumId === albumId) {
const url = result.videoUrl || result.hlsUrl; const url = result.videoUrl || result.hlsUrl;
if (!url) return; if (!url) return;
@ -3017,7 +3017,7 @@ export class UIRenderer {
video.style.opacity = '1'; video.style.opacity = '1';
video.poster = currentImageEl.src; video.poster = currentImageEl.src;
this.setupHlsVideo(video, result, currentImageEl); await this.setupHlsVideo(video, result, currentImageEl);
currentImageEl.replaceWith(video); currentImageEl.replaceWith(video);
} }
} }
@ -3036,10 +3036,10 @@ export class UIRenderer {
video.preload = 'auto'; video.preload = 'auto';
video.className = imageEl.className; video.className = imageEl.className;
video.id = imageEl.id; video.id = imageEl.id;
this.setupHlsVideo(video, videoCoverUrl, imageEl); await this.setupHlsVideo(video, videoCoverUrl, imageEl);
imageEl.replaceWith(video); imageEl.replaceWith(video);
} else { } else {
this.setupHlsVideo(imageEl, videoCoverUrl, null); await this.setupHlsVideo(imageEl, videoCoverUrl, null);
} }
} else { } else {
if (imageEl.tagName === 'VIDEO') { if (imageEl.tagName === 'VIDEO') {
@ -3788,30 +3788,32 @@ export class UIRenderer {
if (!videoCoverUrl && (firstTrack.album || firstTrack.type === 'video')) { if (!videoCoverUrl && (firstTrack.album || firstTrack.type === 'video')) {
const fetchArtwork = () => { const fetchArtwork = () => {
this.api.getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack)).then((result) => { this.api
if (result && this.currentPage === 'mix' && this.currentMixId === mixId) { .getVideoArtwork(firstTrack.title, getTrackArtists(firstTrack))
const url = result.videoUrl || result.hlsUrl; .then(async (result) => {
if (!url) return; if (result && this.currentPage === 'mix' && this.currentMixId === mixId) {
firstTrack.album = firstTrack.album || {}; const url = result.videoUrl || result.hlsUrl;
firstTrack.album.videoCoverUrl = url; if (!url) return;
const currentImageEl = document.getElementById('mix-detail-image'); firstTrack.album = firstTrack.album || {};
if (currentImageEl && currentImageEl.tagName !== 'VIDEO') { firstTrack.album.videoCoverUrl = url;
const video = document.createElement('video'); const currentImageEl = document.getElementById('mix-detail-image');
video.autoplay = true; if (currentImageEl && currentImageEl.tagName !== 'VIDEO') {
video.loop = true; const video = document.createElement('video');
video.muted = true; video.autoplay = true;
video.playsInline = true; video.loop = true;
video.preload = 'auto'; video.muted = true;
video.className = currentImageEl.className; video.playsInline = true;
video.id = currentImageEl.id; video.preload = 'auto';
video.style.opacity = '1'; video.className = currentImageEl.className;
video.poster = currentImageEl.src; video.id = currentImageEl.id;
video.style.opacity = '1';
video.poster = currentImageEl.src;
this.setupHlsVideo(video, result, currentImageEl); await this.setupHlsVideo(video, result, currentImageEl);
currentImageEl.replaceWith(video); currentImageEl.replaceWith(video);
}
} }
} });
});
}; };
if (firstTrack.type === 'video') { if (firstTrack.type === 'video') {
@ -5141,7 +5143,7 @@ export class UIRenderer {
if (!videoCoverUrl && (track.album || track.type === 'video')) { if (!videoCoverUrl && (track.album || track.type === 'video')) {
const fetchArtwork = () => { const fetchArtwork = () => {
this.api.getVideoArtwork(track.title, getTrackArtists(track)).then((result) => { this.api.getVideoArtwork(track.title, getTrackArtists(track)).then(async (result) => {
if (result && this.currentPage === 'track' && this.currentTrackPageId === track.id) { if (result && this.currentPage === 'track' && this.currentTrackPageId === track.id) {
const url = result.videoUrl || result.hlsUrl; const url = result.videoUrl || result.hlsUrl;
if (!url) return; if (!url) return;
@ -5160,7 +5162,7 @@ export class UIRenderer {
video.style.opacity = '1'; video.style.opacity = '1';
video.poster = currentImageEl.src; video.poster = currentImageEl.src;
this.setupHlsVideo(video, result, currentImageEl); await this.setupHlsVideo(video, result, currentImageEl);
currentImageEl.replaceWith(video); currentImageEl.replaceWith(video);
} }
} }
@ -5196,10 +5198,10 @@ export class UIRenderer {
video.preload = 'auto'; video.preload = 'auto';
video.className = imageEl.className; video.className = imageEl.className;
video.id = imageEl.id; video.id = imageEl.id;
this.setupHlsVideo(video, videoCoverUrl, imageEl); await this.setupHlsVideo(video, videoCoverUrl, imageEl);
imageEl.replaceWith(video); imageEl.replaceWith(video);
} else { } else {
this.setupHlsVideo(imageEl, videoCoverUrl, null); await this.setupHlsVideo(imageEl, videoCoverUrl, null);
} }
} else { } else {
if (imageEl.tagName === 'VIDEO') { if (imageEl.tagName === 'VIDEO') {

25
package-lock.json generated
View file

@ -15,13 +15,15 @@
"@ffmpeg/util": "^0.12.2", "@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1", "@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0", "@neutralinojs/lib": "^6.5.0",
"@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4", "@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0", "appwrite": "^23.0.0",
"butterchurn": "^2.6.7", "butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7", "butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0", "client-zip": "^2.5.0",
"cookie-session": "^2.1.1", "cookie-session": "^2.1.1",
"dashjs": "^5.1.1", "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hls.js": "^1.6.15", "hls.js": "^1.6.15",
"jose": "^6.2.0", "jose": "^6.2.0",
@ -31,6 +33,7 @@
"pocketbase": "^0.26.8", "pocketbase": "^0.26.8",
"simple-icons": "^16.12.0", "simple-icons": "^16.12.0",
"svgo": "^4.0.1", "svgo": "^4.0.1",
"url-toolkit": "^2.2.5",
"uuid": "^13.0.0" "uuid": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -3774,6 +3777,13 @@
"@svta/cml-utils": "1.0.1" "@svta/cml-utils": "1.0.1"
} }
}, },
"node_modules/@svta/common-media-library": {
"version": "0.18.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"dev": true, "dev": true,
@ -4598,6 +4608,8 @@
}, },
"node_modules/dashjs": { "node_modules/dashjs": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"integrity": "sha512-lhD1tvEe4PO6t086flm6WfO2Jt1EOIolDQ17F3vLomMthaL1RH96h8peIQTvrDvfSJTRXeisL+CwPj4oud5e9g==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"@svta/cml-608": "1.0.1", "@svta/cml-608": "1.0.1",
@ -5327,6 +5339,10 @@
"es5-ext": "~0.10.14" "es5-ext": "~0.10.14"
} }
}, },
"node_modules/eventemitter3": {
"version": "5.0.4",
"license": "MIT"
},
"node_modules/ext": { "node_modules/ext": {
"version": "1.7.0", "version": "1.7.0",
"dev": true, "dev": true,
@ -5518,11 +5534,8 @@
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.4.2", "version": "3.4.2",
<<<<<<< svg-refactor
=======
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
>>>>>>> main
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -10813,6 +10826,10 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/url-toolkit": {
"version": "2.2.5",
"license": "Apache-2.0"
},
"node_modules/utf-8-validate": { "node_modules/utf-8-validate": {
"version": "5.0.10", "version": "5.0.10",
"dev": true, "dev": true,

View file

@ -57,13 +57,15 @@
"@ffmpeg/util": "^0.12.2", "@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1", "@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0", "@neutralinojs/lib": "^6.5.0",
"@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4", "@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0", "appwrite": "^23.0.0",
"butterchurn": "^2.6.7", "butterchurn": "^2.6.7",
"butterchurn-presets": "^2.4.7", "butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0", "client-zip": "^2.5.0",
"cookie-session": "^2.1.1", "cookie-session": "^2.1.1",
"dashjs": "^5.1.1", "dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"eventemitter3": "^5.0.4",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hls.js": "^1.6.15", "hls.js": "^1.6.15",
"jose": "^6.2.0", "jose": "^6.2.0",
@ -73,6 +75,7 @@
"pocketbase": "^0.26.8", "pocketbase": "^0.26.8",
"simple-icons": "^16.12.0", "simple-icons": "^16.12.0",
"svgo": "^4.0.1", "svgo": "^4.0.1",
"url-toolkit": "^2.2.5",
"uuid": "^13.0.0" "uuid": "^13.0.0"
} }
} }

1
stream-stub.js Normal file
View file

@ -0,0 +1 @@
export function Stream() {}

View file

@ -5,7 +5,6 @@ import authGatePlugin from './vite-plugin-auth-gate.js';
import path from 'path'; import path from 'path';
import uploadPlugin from './vite-plugin-upload.js'; import uploadPlugin from './vite-plugin-upload.js';
import blobAssetPlugin from './vite-plugin-blob.js'; import blobAssetPlugin from './vite-plugin-blob.js';
import injectHTML from 'vite-plugin-html-inject';
import svgUse from './vite-plugin-svg-use.js'; import svgUse from './vite-plugin-svg-use.js';
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
@ -23,6 +22,7 @@ export default defineConfig(({ mode }) => {
'!': '/node_modules', '!': '/node_modules',
pocketbase: '/node_modules/pocketbase/dist/pocketbase.es.js', pocketbase: '/node_modules/pocketbase/dist/pocketbase.es.js',
stream: path.resolve(__dirname, 'stream-stub.js'), // Stub for stream module
}, },
}, },
optimizeDeps: { optimizeDeps: {
@ -50,7 +50,6 @@ export default defineConfig(({ mode }) => {
uploadPlugin(), uploadPlugin(),
blobAssetPlugin(), blobAssetPlugin(),
svgUse(), svgUse(),
injectHTML(),
VitePWA({ VitePWA({
registerType: 'prompt', registerType: 'prompt',
workbox: { workbox: {