From a63f87410fe3b2b105689d4ef29657fbb071e3fe Mon Sep 17 00:00:00 2001 From: SamidyFR <168582143+SamidyFR@users.noreply.github.com> Date: Sun, 29 Mar 2026 15:05:42 +0000 Subject: [PATCH 01/10] style: auto-fix linting issues --- js/events.js | 5 +++-- js/settings.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/js/events.js b/js/events.js index 02ad3b7..156288c 100644 --- a/js/events.js +++ b/js/events.js @@ -1858,8 +1858,9 @@ export async function handleTrackAction( } else if (action === 'block-album') { const { contentBlockingSettings } = await import('./storage.js'); const albumId = type === 'album' ? item.id : item.album?.id; - const albumTitle = type === 'album' ? (item.title || item.name) : (item.album?.title || item.album?.name); - const albumArtist = type === 'album' ? (item.artist?.name || item.artist) : (item.album?.artist?.name || item.album?.artist); + const albumTitle = type === 'album' ? item.title || item.name : item.album?.title || item.album?.name; + const albumArtist = + type === 'album' ? item.artist?.name || item.artist : item.album?.artist?.name || item.album?.artist; if (!albumId) { showNotification('No album information available'); diff --git a/js/settings.js b/js/settings.js index e562262..9323dad 100644 --- a/js/settings.js +++ b/js/settings.js @@ -3732,7 +3732,7 @@ function initializeBlockedContentManager() { if (typeof showNotification === 'function') { showNotification(`Unblocked ${type}: ${itemName}`); } - + renderBlockedLists(); }); }); From 386c6123d85f2f45b8a7639146727d72cbfcbead Mon Sep 17 00:00:00 2001 From: IsraelGPT Date: Sun, 29 Mar 2026 21:29:24 +0000 Subject: [PATCH 02/10] add new slop --- .devcontainer/devcontainer.json | 2 +- js/ui.js | 24 +++++++++++++++- public/editors-picks.json | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bd87db0..4a6a52c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "postCreateCommand": "git config --local core.editor \"code --wait\" && git config --local commit.gpgsign false && npm install && bun install", "customizations": { "vscode": { - "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] + "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "anthropic.claude-code"] } }, "mounts": ["source=${env:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"] diff --git a/js/ui.js b/js/ui.js index 9377a43..b709182 100644 --- a/js/ui.js +++ b/js/ui.js @@ -706,7 +706,7 @@ export class UIRenderer { return this.createBaseCardHTML({ type: 'album', id: album.id, - href: `/album/${album.id}`, + href: album._href || `/album/${album.id}`, title: `${escapeHtml(album.title)} ${explicitBadge} ${qualityBadge}`, subtitle: `${escapeHtml(artistName)} • ${yearDisplay}${typeLabel}`, imageHTML: this.getCoverHTML( @@ -2546,6 +2546,28 @@ export class UIRenderer { itemsToStore.push({ el: null, data: result.album, type: 'album' }); } } + } else if (item.type === 'userplaylist') { + if (item.id && item.title) { + const playlist = { + id: item.id, + name: item.title, + cover: item.cover, + numberOfTracks: item.numberOfTracks || 0, + }; + cardsHTML.push( + this.createAlbumCardHTML({ + ...playlist, + title: item.title, + artist: item.artist, + cover: item.cover, + explicit: item.explicit, + releaseDate: item.releaseDate, + type: 'ALBUM', + _href: `/userplaylist/${item.id}`, + }) + ); + itemsToStore.push({ el: null, data: playlist, type: 'user-playlist' }); + } } else if (item.type === 'artist') { if (item.name && item.picture) { // Use cached data directly diff --git a/public/editors-picks.json b/public/editors-picks.json index 8cd41be..aa1d5df 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1,4 +1,53 @@ [ + { + "type": "userplaylist", + "id": "6i4y9s7y5hb5wyz", + "title": "BULLY", + "artist": { "id": 25022, "name": "Kanye West" }, + "releaseDate": "2026-03-28", + "cover": "https://i.imgur.gg/pLz6Vrf-d4v.jpg", + "explicit": true, + "numberOfTracks": 18 + }, + { + "type": "artist", + "id": 10932434, + "name": "black midi", + "picture": "025bd6b8-be5f-4c95-bda0-e313f94cfdf5" + }, + { + "type": "album", + "id": 310478316, + "title": "Lone Wolf", + "artist": { "id": 4225137, "name": "Jay Lewis" }, + "releaseDate": "2023-09-08", + "cover": "b1c432b6-2e3a-483f-9026-853741756ad3", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 509288326, + "title": "BBY LOBOTOMY", + "artist": { "id": 48966543, "name": "Percatric" }, + "releaseDate": "2026-03-20", + "cover": "9b0bbcef-1cc2-44ef-b741-40880cea49b9", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 101715365, + "title": "Icedancer", + "artist": { "id": 5555246, "name": "Bladee" }, + "releaseDate": "2018-12-29", + "cover": "11465814-2e0b-4d46-8a72-5df21f1ac0b8", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, { "type": "album", "id": 18083938, From cbd085644141c3a6864018c9dafaf4c1baf167ad Mon Sep 17 00:00:00 2001 From: IsraelGPT Date: Sun, 29 Mar 2026 21:34:27 +0000 Subject: [PATCH 03/10] whoops --- public/editors-picks.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/public/editors-picks.json b/public/editors-picks.json index aa1d5df..a140dd6 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1,7 +1,7 @@ [ { "type": "userplaylist", - "id": "6i4y9s7y5hb5wyz", + "id": "5fd08395-e598-4b1a-924f-460e5f2e8b06", "title": "BULLY", "artist": { "id": 25022, "name": "Kanye West" }, "releaseDate": "2026-03-28", @@ -10,10 +10,15 @@ "numberOfTracks": 18 }, { - "type": "artist", - "id": 10932434, - "name": "black midi", - "picture": "025bd6b8-be5f-4c95-bda0-e313f94cfdf5" + "type": "album", + "id": 234935928, + "title": "Hellfire", + "artist": { "id": 10932434, "name": "black midi" }, + "releaseDate": "2022-07-15", + "cover": "71e4b49b-ada3-4e08-b08c-3a7c264117f0", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", From 39163fa4b6127d221826ef937946d053aefe8d65 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:07:54 -0500 Subject: [PATCH 04/10] fix(downloads): applyAudioPostProcessing now uses lookup audio quality to determine if the available track is lossless --- js/api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/api.js b/js/api.js index c50495d..b4031e7 100644 --- a/js/api.js +++ b/js/api.js @@ -1883,7 +1883,7 @@ export class LosslessAPI { quality, onProgress, options.signal, - track?.audioQuality ?? null + lookup.info?.audioQuality ?? null ); } From 286a123e027b891da124b81d3653baf385362faf Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:56:04 -0500 Subject: [PATCH 05/10] fix(downloads): actually return the encoded blob --- js/download-utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/download-utils.ts b/js/download-utils.ts index 04f9ad2..dd991de 100644 --- a/js/download-utils.ts +++ b/js/download-utils.ts @@ -99,6 +99,8 @@ export async function applyAudioPostProcessing( if (format) { try { blob = await transcodeWithCustomFormat(blob, format, onProgress, signal); + + return blob; } catch (encodingError) { if (onProgress) { onProgress({ From 88ec8feb6bd104f0640909850eb63cf106a28585 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Tue, 31 Mar 2026 10:23:03 -0500 Subject: [PATCH 06/10] fix(downloads): improve ffmpeg logging --- js/downloads.js | 6 +++++- js/ffmpeg.js | 15 ++++++++++++++- js/ffmpeg.types.ts | 2 +- js/ffmpeg.worker.js | 4 ++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/js/downloads.js b/js/downloads.js index ed548af..0c9703c 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -238,6 +238,10 @@ export function updateDownloadProgress(trackId, progress) { progressFill.style.background = '#3b82f6'; // Blue for encoding statusEl.textContent = `Converting: ${percent}%`; } else if (progress instanceof ProgressMessage || progress.message) { + if (progress instanceof FfmpegProgress && (progress.stage == 'parsing' || progress.stage == 'stdout')) { + return; + } + progressFill.style.width = '100%'; progressFill.style.background = '#3b82f6'; statusEl.textContent = progress.message; @@ -961,7 +965,7 @@ function updateBulkDownloadProgress(notifEl, current, total, currentItem, progre } if (progress instanceof FfmpegProgress) { - if (progress.stage == 'stdout') { + if (progress.stage == 'stdout' || progress.stage == 'parsing') { return; } diff --git a/js/ffmpeg.js b/js/ffmpeg.js index f5e5f40..80f1012 100644 --- a/js/ffmpeg.js +++ b/js/ffmpeg.js @@ -55,11 +55,13 @@ async function ffmpegWorker( const assets = loadFfmpeg(); return new Promise((resolve, reject) => { + let endCategory = null; const worker = new FfmpegWorker(); // Handle abort signal const abortHandler = () => { worker.terminate(); + endCategory?.(); reject(new FfmpegError('FFMPEG aborted')); }; @@ -72,20 +74,30 @@ async function ffmpegWorker( } worker.onmessage = (e) => { - const { type, blob, message, stage, progress } = e.data; + const { type, blob, message, stage, progress, command } = e.data; if (type === 'complete') { if (signal) signal.removeEventListener('abort', abortHandler); worker.terminate(); + endCategory?.(); resolve(blob); } else if (type === 'error') { if (signal) signal.removeEventListener('abort', abortHandler); worker.terminate(); + endCategory?.(); reject(new FfmpegError(message)); } else if (type === 'progress' && message) { onProgress?.(new FfmpegProgress(stage, progress || 0, message)); } else if (type === 'progress' && stage != 'loading' && progress !== null) { onProgress?.(new FfmpegProgress(stage, progress || 0, message)); + } else if (type === 'command') { + if (logConsole) { + const consoleCategory = `ffmpeg ${command?.join(' ')}`; + // eslint-disable-next-line no-console + console.groupCollapsed(consoleCategory); + // eslint-disable-next-line no-console + endCategory = () => console.groupEnd(); + } } else if (type === 'log') { onProgress?.(new FfmpegProgress('stdout', 0, message)); if (logConsole) { @@ -97,6 +109,7 @@ async function ffmpegWorker( worker.onerror = (error) => { if (signal) signal.removeEventListener('abort', abortHandler); worker.terminate(); + endCategory?.(); reject(new FfmpegError('Worker failed: ' + error.message)); }; diff --git a/js/ffmpeg.types.ts b/js/ffmpeg.types.ts index 2eb82dd..6e9d899 100644 --- a/js/ffmpeg.types.ts +++ b/js/ffmpeg.types.ts @@ -1,6 +1,6 @@ export class FfmpegProgress implements MonochromeProgress { constructor( - public readonly stage: 'loading' | 'encoding' | 'finalizing' | 'stdout', + public readonly stage: 'loading' | 'parsing' | 'encoding' | 'finalizing' | 'stdout', public readonly progress: number, public readonly message?: string ) {} diff --git a/js/ffmpeg.worker.js b/js/ffmpeg.worker.js index b34c4ce..8da739d 100644 --- a/js/ffmpeg.worker.js +++ b/js/ffmpeg.worker.js @@ -40,7 +40,7 @@ async function loadFFmpeg(loadOptions = {}) { ffmpeg = new FFmpeg(); ffmpeg.on('log', ({ message }) => { - self.postMessage({ type: 'log', message }); + self.postMessage({ type: 'log', stage: 'stdout', message }); // Try to extract total duration from input log if (totalDurationSeconds === null) { @@ -124,7 +124,7 @@ self.onmessage = async (e) => { } const ffmpegArgs = ['-i', 'input', ...args, ...(output.name ? [output.name] : [])]; - self.postMessage({ type: 'log', message: `FFmpeg command: ffmpeg ${ffmpegArgs.join(' ')}` }); + self.postMessage({ type: 'command', command: ffmpegArgs }); const exitCode = await ffmpeg.exec(ffmpegArgs); From bb387ac30b30a6ca949b9f2c4b58f049aee2af28 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:07:31 -0500 Subject: [PATCH 07/10] fix(downloads): pass DOLBY_ATMOS to applyAudioPostProcessing if track is atmos --- js/api.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/api.js b/js/api.js index b4031e7..3b6bb89 100644 --- a/js/api.js +++ b/js/api.js @@ -1751,6 +1751,7 @@ export class LosslessAPI { const { lookup, enrichedTrack, isVideo } = await this.enrichTrack(track, { downloadQuality }); + let postProcessingQuality = lookup.info?.audioQuality ?? null; let streamUrl; let blob; @@ -1783,6 +1784,10 @@ export class LosslessAPI { const manifest = await fetch(stream.url, { signal: options.signal }); const manifestText = await manifest.text(); streamUrl = this.extractStreamUrlFromManifest(btoa(manifestText)); + + if (streamUrl) { + postProcessingQuality = 'DOLBY_ATMOS'; + } } catch (err) { console.error('Failed to extract Dolby Atmos stream URL:', err); } @@ -1883,7 +1888,7 @@ export class LosslessAPI { quality, onProgress, options.signal, - lookup.info?.audioQuality ?? null + postProcessingQuality ); } From c17c249ca9ac3841beb7a925c1313997f51c0b38 Mon Sep 17 00:00:00 2001 From: SamidyFR <168582143+SamidyFR@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:09:15 +0000 Subject: [PATCH 08/10] style: auto-fix linting issues --- js/api.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/js/api.js b/js/api.js index 3b6bb89..f2edcbd 100644 --- a/js/api.js +++ b/js/api.js @@ -1883,13 +1883,7 @@ export class LosslessAPI { } if (!isVideo) { - blob = await applyAudioPostProcessing( - blob, - quality, - onProgress, - options.signal, - postProcessingQuality - ); + blob = await applyAudioPostProcessing(blob, quality, onProgress, options.signal, postProcessingQuality); } // Add metadata if track information is provided From 0ac73db811f4dadd44116d8f73dc592d5518b9c0 Mon Sep 17 00:00:00 2001 From: edideaur Date: Wed, 1 Apr 2026 01:12:21 +0000 Subject: [PATCH 09/10] whoops --- public/editors-picks.json | 366 ++++++++++------------------------ public/editors-picks.json.old | 365 +++++++++++++++++++++++++++++++++ 2 files changed, 466 insertions(+), 265 deletions(-) create mode 100644 public/editors-picks.json.old diff --git a/public/editors-picks.json b/public/editors-picks.json index a140dd6..b7e8c8b 100644 --- a/public/editors-picks.json +++ b/public/editors-picks.json @@ -1,353 +1,189 @@ [ { - "type": "userplaylist", - "id": "5fd08395-e598-4b1a-924f-460e5f2e8b06", + "type": "album", + "id": 510893864, "title": "BULLY", "artist": { "id": 25022, "name": "Kanye West" }, "releaseDate": "2026-03-28", - "cover": "https://i.imgur.gg/pLz6Vrf-d4v.jpg", - "explicit": true, - "numberOfTracks": 18 - }, - { - "type": "album", - "id": 234935928, - "title": "Hellfire", - "artist": { "id": 10932434, "name": "black midi" }, - "releaseDate": "2022-07-15", - "cover": "71e4b49b-ada3-4e08-b08c-3a7c264117f0", + "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 310478316, - "title": "Lone Wolf", - "artist": { "id": 4225137, "name": "Jay Lewis" }, - "releaseDate": "2023-09-08", - "cover": "b1c432b6-2e3a-483f-9026-853741756ad3", + "id": 403172104, + "title": "From A Man's Perspective", + "artist": { "id": 3561564, "name": "Dax" }, + "releaseDate": "2024-12-06", + "cover": "c52a53ea-f021-44bf-8cef-fb31d3b82940", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 509288326, - "title": "BBY LOBOTOMY", - "artist": { "id": 48966543, "name": "Percatric" }, - "releaseDate": "2026-03-20", - "cover": "9b0bbcef-1cc2-44ef-b741-40880cea49b9", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 101715365, - "title": "Icedancer", - "artist": { "id": 5555246, "name": "Bladee" }, - "releaseDate": "2018-12-29", - "cover": "11465814-2e0b-4d46-8a72-5df21f1ac0b8", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS"] } - }, - { - "type": "album", - "id": 18083938, - "title": "The Glow, Pt. 2", - "artist": { "id": 3941394, "name": "The Microphones" }, - "releaseDate": "2001-09-25", - "cover": "ec648c22-9140-41d3-a7ae-0ba69ef0420e", + "id": 341529881, + "title": "FACTS", + "artist": { "id": 5691796, "name": "Tom MacDonald" }, + "releaseDate": "2024-01-26", + "cover": "34198718-cc7d-47eb-9bf1-1dc5c26fc8a1", "explicit": false, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 382839956, - "title": "my anti-aircraft friend", - "artist": { "id": 19359095, "name": "julie" }, - "releaseDate": "2024-09-13", - "cover": "ac790b52-61cc-460c-9277-bdac88722cc3", - "explicit": false, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 456475370, - "title": "It Aint Nun", - "artist": { "id": 9981740, "name": "CHRIST DILLINGER" }, - "releaseDate": "2024-09-12", - "cover": "3ffc27f6-a77c-4e68-ba44-e04f034783be", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 112547160, - "title": "R F Y", - "artist": { "id": 36042731, "name": "RFY" }, - "releaseDate": "2019-06-27", - "cover": "ffe3f6f9-5bc8-4b53-99c3-9b83676c099a", + "id": 452163300, + "title": "Tackle Box", + "artist": { "id": 44771714, "name": "JamWayne" }, + "releaseDate": "2025-09-01", + "cover": "bf7d2e55-52dc-4e41-9f53-1db31918798e", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 18658568, - "title": "100% Ghetto 4", - "artist": { "id": 4191963, "name": "DJ Clent" }, - "releaseDate": "2010-10-02", - "cover": "82b839bd-7cf6-4650-bade-192f47301ffd", + "id": 106210035, + "title": "Supermarket (Soundtrack)", + "artist": { "id": 3533999, "name": "LOGIC" }, + "releaseDate": "2019-03-26", + "cover": "bdd39738-7177-4836-bd7f-cd4fe4ccf535", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 504004321, - "title": "Half Blood (BloodLuxe)", - "artist": { "id": 50799233, "name": "slayr" }, - "releaseDate": "2025-11-05", - "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 89313048, - "title": "DAYTONA", - "artist": { "id": 3972883, "name": "Pusha T" }, - "releaseDate": "2018-05-25", - "cover": "30288caf-2bdd-4511-a95c-57117936b2b6", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 324660713, - "title": "JOECHILLWORLD", - "artist": { "id": 3972883, "name": "Devon Hendryx" }, - "releaseDate": "2010-08-10", - "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 418729278, - "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", - "artist": { "id": 7958797, "name": "JPEGMAFIA" }, - "releaseDate": "2025-02-03", - "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", + "id": 138458381, + "title": "JOKER", + "artist": { "id": 39109746, "name": "Dax" }, + "releaseDate": "2020-05-06", + "cover": "d6a39491-fece-4594-a538-9cfbce7c6c68", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 118353565, - "title": "Dyn-O-Mite", - "artist": { "id": 5755811, "name": "ZelooperZ" }, - "releaseDate": "2019-05-18", - "cover": "c42f0025-9839-4dd2-b5de-9fd05ed5e917", + "id": 318467376, + "title": "The Draco Tape 2", + "artist": { "id": 22467779, "name": "60PERCENTHOMO" }, + "releaseDate": "2023-09-30", + "cover": "9169e8ab-fdb0-49d1-a14f-82a2b541223c", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 4527433, - "title": "Flockaveli", - "artist": { "id": 3654061, "name": "Waka Flocka Flame" }, - "releaseDate": "2010-10-05", - "cover": "05702b51-45cf-4157-b9ed-dd7ca7e7b7b3", + "id": 456582219, + "title": "bbno$", + "artist": { "id": 8173944, "name": "bbno$" }, + "releaseDate": "2025-10-17", + "cover": "8162c739-4bb2-4218-8915-93cb1f0d9eea", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 90502209, - "title": "NASIR", - "artist": { "id": 1003, "name": "Nas" }, - "releaseDate": "2018-06-15", - "cover": "503ea6b2-0829-438e-8e4e-9a988154b3bc", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 413189044, - "title": "Jump Out", - "artist": { "id": 27836827, "name": "OsamaSon" }, - "releaseDate": "2025-01-24", - "cover": "ec4a4ef2-69fe-4d3c-aaba-05dc2d546e84", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 209061256, - "title": "Super Tecmo Bo", - "artist": { "id": 4839917, "name": "Boldy James" }, - "releaseDate": "2021-12-17", - "cover": "f58ca804-1da7-4953-a26d-2b3258310db5", + "id": 506216548, + "title": "Declassified", + "artist": { "id": 5691796, "name": "Tom MacDonald" }, + "releaseDate": "2026-03-13", + "cover": "5cf188e5-1660-4a01-bacc-10ca81e7af73", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 488297983, - "title": "So Much Country 'Till We Get There", - "artist": { "id": 51427239, "name": "Westside Cowboy" }, + "id": 486673299, + "title": "Novelty", + "artist": { "id": 3589848, "name": "Goofy" }, "releaseDate": "2026-01-23", - "cover": "dd9d4f23-1517-4a76-8e3f-31e341dcd525", + "cover": "dd543595-e604-440e-a24c-e2eaccda3804", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 440469641, + "title": "Tha Carter VI", + "artist": { "id": 27518, "name": "Lil Wayne" }, + "releaseDate": "2025-06-06", + "cover": "f2807cfe-6df8-4ea0-ab32-fd0ad9e91072", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 384057608, + "title": "FREE VIPER!", + "artist": { "id": 10285483, "name": "Viper" }, + "releaseDate": "2024-08-28", + "cover": "642d73d9-2a45-4598-bec3-033a09181bdf", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 460015533, + "title": "CHARLIE", + "artist": { "id": 5691796, "name": "Tom MacDonald" }, + "releaseDate": "2025-09-11", + "cover": "18180e3d-e178-4bd3-aada-e26ca32bc8f4", "explicit": false, "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 447277344, - "title": "REST IN BASS", - "artist": { "id": 50418386, "name": "Che" }, - "releaseDate": "2025-07-18", - "cover": "d1397066-72ed-4481-b508-77f7c7a03073", + "id": 123319314, + "title": "BABY G.O.A.T.", + "artist": { "id": 9160626, "name": "Kevo Muney" }, + "releaseDate": "2019-12-12", + "cover": "6c87aee6-76da-413e-a639-79b670b3e3d1", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 490209783, - "title": "My Ghosts Go Ghost", - "artist": { "id": 39920098, "name": "By Storm" }, - "releaseDate": "2026-01-30", - "cover": "bc470b88-3583-4853-976e-593855672322", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 279082597, - "title": "Noktifer's Symphony", - "artist": { "id": 22055243, "name": "axxturel" }, - "releaseDate": "2020-11-17", - "cover": "a886acd8-b915-4a77-9c1f-eecf7fa6091c", + "id": 510493672, + "title": "ADL", + "artist": { "id": 9318056, "name": "Yeat" }, + "releaseDate": "2026-03-27", + "cover": "b799025a-7ff6-494c-aa11-5e0461bbca40", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 380211863, - "title": "Sayso Says", - "artist": { "id": 50418386, "name": "Che" }, - "releaseDate": "2024-08-30", - "cover": "917c7e0f-3ebb-471e-9c88-fcdf6a9f21a5", + "id": 440096189, + "title": "Rebel", + "artist": { "id": 52081927, "name": "EsDeeKid" }, + "releaseDate": "2025-06-20", + "cover": "3433b56c-6386-4e21-90ee-f2369d4bfde7", "explicit": true, "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } }, { "type": "album", - "id": 464463900, - "title": "A-Rhythm Absolute", - "artist": { "id": 30049790, "name": "Sunday Mourners" }, - "releaseDate": "2026-01-16", - "cover": "d04e2ed8-f6d7-43c0-bcb2-fb99d3de0d5d", + "id": 85287313, + "title": "Toilet", + "artist": { "id": 9598562, "name": "Clown Core" }, + "releaseDate": "2018-03-03", + "cover": "d6d32464-864e-422f-b8c3-0c30baa563b1", "explicit": false, "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": "344201347", - "title": "Flex Musix (FLXTRA)", - "artist": { "id": 27836827, "name": "OsamaSon" }, - "releaseDate": "2024-02-16", - "cover": "5d1812fc-b9f9-4467-ac78-90d78ea542e4", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 365819314, - "title": "One Life", - "artist": { "id": "17300439", "name": "1oneam" }, - "releaseDate": "2024-05-30", - "cover": "eb5d74f6-7403-4404-8452-9b68713445fe", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS"] } - }, - { - "type": "album", - "id": 379517195, - "title": "Haram", - "artist": { "id": 5225704, "name": "Armand Hammer" }, - "releaseDate": "2021-03-26", - "cover": "69ebc3e7-bd0b-4dd7-88a5-a2e180b84f0d", - "explicit": false, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 36160524, - "title": "Nasa Gang (Remastered)", - "artist": { "id": 4611745, "name": "SpaceGhostPurrp" }, - "releaseDate": "2014-10-07", - "cover": "359d42ec-1984-4d63-a4ae-ecf286463372", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS"] } - }, - { - "type": "album", - "id": 445752470, - "title": "FOR NOTHING", - "artist": { "id": 49124576, "name": "nine vicious" }, - "releaseDate": "2025-07-04", - "cover": "8b9018dd-d3c3-46e0-be5a-c16d48180176", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS"] } - }, - { - "type": "album", - "id": 435083611, - "title": "Buddha Therapy", - "artist": { "id": 4858188, "name": "Metro Zu" }, - "releaseDate": "2017-05-01", - "cover": "5c6c367a-99ed-4fa4-8e91-ea10d6066e26", - "explicit": true, - "audioQuality": "LOSSLESS", - "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } - }, - { - "type": "album", - "id": 473188494, - "title": "Blue Flame", - "artist": { "id": 3799089, "name": "Lil B" }, - "releaseDate": "2010-09-13", - "cover": "35b19623-cb5c-491a-81ea-ebac108c58f2", - "explicit": true, - "audioQuality": "LOSSLESS", "mediaMetadata": { "tags": ["LOSSLESS"] } } ] diff --git a/public/editors-picks.json.old b/public/editors-picks.json.old new file mode 100644 index 0000000..ea2146c --- /dev/null +++ b/public/editors-picks.json.old @@ -0,0 +1,365 @@ +[ + { + "type": "album", + "id": 510893864, + "title": "BULLY", + "artist": { "id": 25022, "name": "Kanye West" }, + "releaseDate": "2026-03-28", + "cover": "cf2f2c9c-ff67-44f6-83aa-a7622f8c6b64", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 453241376, + "title": "Mercy", + "artist": { "id": 5225704, "name": "Armand Hammer" }, + "releaseDate": "2025-11-07", + "cover": "ef736301-dbd1-4a41-8668-9dfcdac8d9ad", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 234935928, + "title": "Hellfire", + "artist": { "id": 10932434, "name": "black midi" }, + "releaseDate": "2022-07-15", + "cover": "71e4b49b-ada3-4e08-b08c-3a7c264117f0", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 310478316, + "title": "Lone Wolf", + "artist": { "id": 4225137, "name": "Jay Lewis" }, + "releaseDate": "2023-09-08", + "cover": "b1c432b6-2e3a-483f-9026-853741756ad3", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 509288326, + "title": "BBY LOBOTOMY", + "artist": { "id": 48966543, "name": "Percatric" }, + "releaseDate": "2026-03-20", + "cover": "9b0bbcef-1cc2-44ef-b741-40880cea49b9", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 101715365, + "title": "Icedancer", + "artist": { "id": 5555246, "name": "Bladee" }, + "releaseDate": "2018-12-29", + "cover": "11465814-2e0b-4d46-8a72-5df21f1ac0b8", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 18083938, + "title": "The Glow, Pt. 2", + "artist": { "id": 3941394, "name": "The Microphones" }, + "releaseDate": "2001-09-25", + "cover": "ec648c22-9140-41d3-a7ae-0ba69ef0420e", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 382839956, + "title": "my anti-aircraft friend", + "artist": { "id": 19359095, "name": "julie" }, + "releaseDate": "2024-09-13", + "cover": "ac790b52-61cc-460c-9277-bdac88722cc3", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 456475370, + "title": "It Aint Nun", + "artist": { "id": 9981740, "name": "CHRIST DILLINGER" }, + "releaseDate": "2024-09-12", + "cover": "3ffc27f6-a77c-4e68-ba44-e04f034783be", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 112547160, + "title": "R F Y", + "artist": { "id": 36042731, "name": "RFY" }, + "releaseDate": "2019-06-27", + "cover": "ffe3f6f9-5bc8-4b53-99c3-9b83676c099a", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 18658568, + "title": "100% Ghetto 4", + "artist": { "id": 4191963, "name": "DJ Clent" }, + "releaseDate": "2010-10-02", + "cover": "82b839bd-7cf6-4650-bade-192f47301ffd", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 504004321, + "title": "Half Blood (BloodLuxe)", + "artist": { "id": 50799233, "name": "slayr" }, + "releaseDate": "2025-11-05", + "cover": "2767cc63-7e92-4a48-aa4b-806a3ea7ec1c", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 89313048, + "title": "DAYTONA", + "artist": { "id": 3972883, "name": "Pusha T" }, + "releaseDate": "2018-05-25", + "cover": "30288caf-2bdd-4511-a95c-57117936b2b6", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 324660713, + "title": "JOECHILLWORLD", + "artist": { "id": 3972883, "name": "Devon Hendryx" }, + "releaseDate": "2010-08-10", + "cover": "25d45544-3e82-4184-b8c2-2c2c6f0f152a", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 418729278, + "title": "I LAY DOWN MY LIFE FOR YOU: DIRECTOR'S CUT", + "artist": { "id": 7958797, "name": "JPEGMAFIA" }, + "releaseDate": "2025-02-03", + "cover": "9c84302b-2584-4c0a-9db7-e648542f459f", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 118353565, + "title": "Dyn-O-Mite", + "artist": { "id": 5755811, "name": "ZelooperZ" }, + "releaseDate": "2019-05-18", + "cover": "c42f0025-9839-4dd2-b5de-9fd05ed5e917", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 4527433, + "title": "Flockaveli", + "artist": { "id": 3654061, "name": "Waka Flocka Flame" }, + "releaseDate": "2010-10-05", + "cover": "05702b51-45cf-4157-b9ed-dd7ca7e7b7b3", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 90502209, + "title": "NASIR", + "artist": { "id": 1003, "name": "Nas" }, + "releaseDate": "2018-06-15", + "cover": "503ea6b2-0829-438e-8e4e-9a988154b3bc", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 413189044, + "title": "Jump Out", + "artist": { "id": 27836827, "name": "OsamaSon" }, + "releaseDate": "2025-01-24", + "cover": "ec4a4ef2-69fe-4d3c-aaba-05dc2d546e84", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 209061256, + "title": "Super Tecmo Bo", + "artist": { "id": 4839917, "name": "Boldy James" }, + "releaseDate": "2021-12-17", + "cover": "f58ca804-1da7-4953-a26d-2b3258310db5", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 488297983, + "title": "So Much Country 'Till We Get There", + "artist": { "id": 51427239, "name": "Westside Cowboy" }, + "releaseDate": "2026-01-23", + "cover": "dd9d4f23-1517-4a76-8e3f-31e341dcd525", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 447277344, + "title": "REST IN BASS", + "artist": { "id": 50418386, "name": "Che" }, + "releaseDate": "2025-07-18", + "cover": "d1397066-72ed-4481-b508-77f7c7a03073", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 490209783, + "title": "My Ghosts Go Ghost", + "artist": { "id": 39920098, "name": "By Storm" }, + "releaseDate": "2026-01-30", + "cover": "bc470b88-3583-4853-976e-593855672322", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 279082597, + "title": "Noktifer's Symphony", + "artist": { "id": 22055243, "name": "axxturel" }, + "releaseDate": "2020-11-17", + "cover": "a886acd8-b915-4a77-9c1f-eecf7fa6091c", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 380211863, + "title": "Sayso Says", + "artist": { "id": 50418386, "name": "Che" }, + "releaseDate": "2024-08-30", + "cover": "917c7e0f-3ebb-471e-9c88-fcdf6a9f21a5", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 464463900, + "title": "A-Rhythm Absolute", + "artist": { "id": 30049790, "name": "Sunday Mourners" }, + "releaseDate": "2026-01-16", + "cover": "d04e2ed8-f6d7-43c0-bcb2-fb99d3de0d5d", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": "344201347", + "title": "Flex Musix (FLXTRA)", + "artist": { "id": 27836827, "name": "OsamaSon" }, + "releaseDate": "2024-02-16", + "cover": "5d1812fc-b9f9-4467-ac78-90d78ea542e4", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 365819314, + "title": "One Life", + "artist": { "id": "17300439", "name": "1oneam" }, + "releaseDate": "2024-05-30", + "cover": "eb5d74f6-7403-4404-8452-9b68713445fe", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 379517195, + "title": "Haram", + "artist": { "id": 5225704, "name": "Armand Hammer" }, + "releaseDate": "2021-03-26", + "cover": "69ebc3e7-bd0b-4dd7-88a5-a2e180b84f0d", + "explicit": false, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 36160524, + "title": "Nasa Gang (Remastered)", + "artist": { "id": 4611745, "name": "SpaceGhostPurrp" }, + "releaseDate": "2014-10-07", + "cover": "359d42ec-1984-4d63-a4ae-ecf286463372", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 445752470, + "title": "FOR NOTHING", + "artist": { "id": 49124576, "name": "nine vicious" }, + "releaseDate": "2025-07-04", + "cover": "8b9018dd-d3c3-46e0-be5a-c16d48180176", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + }, + { + "type": "album", + "id": 435083611, + "title": "Buddha Therapy", + "artist": { "id": 4858188, "name": "Metro Zu" }, + "releaseDate": "2017-05-01", + "cover": "5c6c367a-99ed-4fa4-8e91-ea10d6066e26", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS", "HIRES_LOSSLESS"] } + }, + { + "type": "album", + "id": 473188494, + "title": "Blue Flame", + "artist": { "id": 3799089, "name": "Lil B" }, + "releaseDate": "2010-09-13", + "cover": "35b19623-cb5c-491a-81ea-ebac108c58f2", + "explicit": true, + "audioQuality": "LOSSLESS", + "mediaMetadata": { "tags": ["LOSSLESS"] } + } +] From c8f64a52e838c2e56a8fa15904a7db4f9c7b21d3 Mon Sep 17 00:00:00 2001 From: edideaur Date: Wed, 1 Apr 2026 14:47:37 +0000 Subject: [PATCH 10/10] tilting + background play fixes --- index.html | 26 +++++++++++++++++++++++++ js/player.js | 30 +++++++++++++++++++++++++++-- js/settings.js | 22 +++++++++++++++++++++ js/storage.js | 30 +++++++++++++++++++++++++++++ js/ui.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 9cf8929..e90deb6 100644 --- a/index.html +++ b/index.html @@ -3748,6 +3748,32 @@ +
+
+ Fullscreen Cover Tilt + 3D tilt effect on album cover in fullscreen view +
+ +
+
+
+ Preload Next Track + Seconds before track ends to start loading next +
+ +
diff --git a/js/player.js b/js/player.js index de0b850..4e0ed01 100644 --- a/js/player.js +++ b/js/player.js @@ -16,6 +16,7 @@ import { exponentialVolumeSettings, audioEffectsSettings, radioSettings, + playbackSettings, } from './storage.js'; import { audioContextManager } from './audio-context.js'; import { isIos, isSafari } from './platform-detection.js'; @@ -48,6 +49,7 @@ export class Player { this.repeatMode = REPEAT_MODE.OFF; this.preloadCache = new Map(); this.preloadAbortController = null; + this._lastPreloadTime = null; this.currentTrack = null; this.currentRgValues = null; this.userVolume = parseFloat(localStorage.getItem('volume') || '0.7'); @@ -106,7 +108,6 @@ export class Player { bufferingGoal: 30, rebufferingGoal: 2, bufferBehind: 30, - jumpLargeGaps: true, }, abr: { enabled: true, @@ -150,7 +151,6 @@ export class Player { document.addEventListener('visibilitychange', () => { const el = this.activeElement; if (document.visibilityState === 'visible' && !el.paused) { - // Ensure audio context is resumed when user returns to the app if (!audioContextManager.isReady()) { audioContextManager.init(el); } @@ -162,6 +162,17 @@ export class Player { } }); + // Time-based preload trigger for Safari background playback + this._timeUpdateHandler = this._handleTimeUpdateForPreload.bind(this); + this.audio.addEventListener('timeupdate', this._timeUpdateHandler); + if (this.video) { + this.video.addEventListener('timeupdate', this._timeUpdateHandler); + } + + window.addEventListener('preload-time-change', () => { + this._lastPreloadTime = null; + }); + this._setupVideoSync(); } @@ -516,6 +527,21 @@ export class Player { } } + _handleTimeUpdateForPreload() { + const el = this.activeElement; + if (!el || !el.duration || el.paused) return; + + const preloadTime = playbackSettings.getPreloadTime(); + const timeRemaining = el.duration - el.currentTime; + if (timeRemaining <= preloadTime && timeRemaining > 0) { + const now = Date.now(); + if (!this._lastPreloadTime || now - this._lastPreloadTime > 5000) { + this._lastPreloadTime = now; + this.preloadNextTracks(); + } + } + } + async setupHlsVideo(video, result, fallbackImg) { const url = result.videoUrl || result.hlsUrl || result; const Hls = (await import('hls.js')).default; diff --git a/js/settings.js b/js/settings.js index 9323dad..7cf8e44 100644 --- a/js/settings.js +++ b/js/settings.js @@ -18,6 +18,7 @@ import { visualizerSettings, playlistSettings, equalizerSettings, + playbackSettings, listenBrainzSettings, malojaSettings, libreFmSettings, @@ -1111,6 +1112,27 @@ export async function initializeSettings(scrobbler, player, api, ui) { }); } + // Fullscreen Cover Tilt Toggle + const fullscreenTiltToggle = document.getElementById('fullscreen-tilt-toggle'); + if (fullscreenTiltToggle) { + fullscreenTiltToggle.checked = playbackSettings.isFullscreenTiltEnabled(); + fullscreenTiltToggle.addEventListener('change', (e) => { + playbackSettings.setFullscreenTiltEnabled(e.target.checked); + window.dispatchEvent(new CustomEvent('fullscreen-tilt-toggle', { detail: { enabled: e.target.checked } })); + }); + } + + // Preload Time Input + const preloadTimeInput = document.getElementById('preload-time-input'); + if (preloadTimeInput) { + preloadTimeInput.value = playbackSettings.getPreloadTime(); + preloadTimeInput.addEventListener('change', (e) => { + const val = Math.max(5, Math.min(60, parseInt(e.target.value, 10) || 15)); + playbackSettings.setPreloadTime(val); + window.dispatchEvent(new CustomEvent('preload-time-change', { detail: { seconds: val } })); + }); + } + // ReplayGain Settings const replayGainMode = document.getElementById('replay-gain-mode'); if (replayGainMode) { diff --git a/js/storage.js b/js/storage.js index 8ba7de3..e7b2035 100644 --- a/js/storage.js +++ b/js/storage.js @@ -999,6 +999,36 @@ export const visualizerSettings = { }, }; +export const playbackSettings = { + FULLSCREEN_TILT_KEY: 'playback-fullscreen-tilt', + PRELOAD_TIME_KEY: 'playback-preload-time', + + isFullscreenTiltEnabled() { + try { + return localStorage.getItem(this.FULLSCREEN_TILT_KEY) !== 'false'; + } catch { + return true; + } + }, + + setFullscreenTiltEnabled(enabled) { + localStorage.setItem(this.FULLSCREEN_TILT_KEY, enabled ? 'true' : 'false'); + }, + + getPreloadTime() { + try { + const val = localStorage.getItem(this.PRELOAD_TIME_KEY); + return val ? parseInt(val, 10) : 15; + } catch { + return 15; + } + }, + + setPreloadTime(seconds) { + localStorage.setItem(this.PRELOAD_TIME_KEY, seconds.toString()); + }, +}; + export const equalizerSettings = { ENABLED_KEY: 'equalizer-enabled', GAINS_KEY: 'equalizer-gains', diff --git a/js/ui.js b/js/ui.js index b709182..34375fd 100644 --- a/js/ui.js +++ b/js/ui.js @@ -26,6 +26,7 @@ import { fontSettings, contentBlockingSettings, settingsUiState, + playbackSettings, } from './storage.js'; import { db } from './db.js'; import { getVibrantColorFromImage } from './vibrant-color.js'; @@ -148,6 +149,9 @@ export class UIRenderer { this.lastRecommendedTracks = []; this.currentArtistId = null; + this._handleTiltMove = this._handleTiltMove.bind(this); + this._handleTiltLeave = this._handleTiltLeave.bind(this); + // Listen for dynamic color reset events window.addEventListener('reset-dynamic-color', () => { this.resetVibrantColor(); @@ -1225,6 +1229,14 @@ export class UIRenderer { overlay.style.display = 'flex'; + // Apply vanilla-tilt effect to fullscreen cover if enabled + this._applyFullscreenTilt(overlay); + + // Listen for tilt setting changes + window.addEventListener('fullscreen-tilt-toggle', (e) => { + this._applyFullscreenTilt(overlay, e.detail.enabled); + }); + const startVisualizer = async () => { if (!visualizerSettings.isEnabled()) { if (this.visualizer) this.visualizer.stop(); @@ -1318,6 +1330,46 @@ export class UIRenderer { clearTimeout(this.uiToggleMouseTimer); this.uiToggleMouseTimer = null; } + + // Clean up vanilla-tilt if applied + this._removeFullscreenTilt(); + } + + _applyFullscreenTilt(overlay, enabled = playbackSettings.isFullscreenTiltEnabled()) { + const image = document.getElementById('fullscreen-cover-image'); + if (!image) return; + + this._removeFullscreenTilt(); + + if (!enabled) return; + + image.addEventListener('mousemove', this._handleTiltMove); + image.addEventListener('mouseleave', this._handleTiltLeave); + } + + _handleTiltMove(e) { + const image = e.target; + const rect = image.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const centerX = rect.width / 2; + const centerY = rect.height / 2; + const rotateX = ((y - centerY) / centerY) * -10; + const rotateY = ((x - centerX) / centerX) * 10; + + image.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`; + } + + _handleTiltLeave(e) { + e.target.style.transform = 'perspective(1000px) rotateX(0) rotateY(0) scale(1)'; + } + + _removeFullscreenTilt() { + const image = document.getElementById('fullscreen-cover-image'); + if (!image) return; + image.removeEventListener('mousemove', this._handleTiltMove); + image.removeEventListener('mouseleave', this._handleTiltLeave); + image.style.transform = ''; } setupUIToggleButton(overlay) {