From 4183cef4f1c405f4224e23a05c9c5df55e24875a Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Wed, 24 Dec 2025 13:48:39 +0100 Subject: [PATCH 1/3] fix: resolve multiple bugs including playback loops and search race conditions --- js/api.js | 20 ++++++++++++-------- js/player.js | 4 ++++ js/ui.js | 16 ++++++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/js/api.js b/js/api.js index 2a14ea2..ab66413 100644 --- a/js/api.js +++ b/js/api.js @@ -219,12 +219,12 @@ export class LosslessAPI { } } - async searchTracks(query) { + async searchTracks(query, options = {}) { const cached = await this.cache.get('search_tracks', query); if (cached) return cached; try { - const response = await this.fetchWithRetry(`/search/?s=${encodeURIComponent(query)}`); + const response = await this.fetchWithRetry(`/search/?s=${encodeURIComponent(query)}`, options); const data = await response.json(); const normalized = this.normalizeSearchResponse(data, 'tracks'); const result = { @@ -235,17 +235,18 @@ export class LosslessAPI { await this.cache.set('search_tracks', query, result); return result; } catch (error) { + if (error.name === 'AbortError') throw error; console.error('Track search failed:', error); return { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 }; } } - async searchArtists(query) { + async searchArtists(query, options = {}) { const cached = await this.cache.get('search_artists', query); if (cached) return cached; try { - const response = await this.fetchWithRetry(`/search/?a=${encodeURIComponent(query)}`); + const response = await this.fetchWithRetry(`/search/?a=${encodeURIComponent(query)}`, options); const data = await response.json(); const normalized = this.normalizeSearchResponse(data, 'artists'); const result = { @@ -256,17 +257,18 @@ export class LosslessAPI { await this.cache.set('search_artists', query, result); return result; } catch (error) { + if (error.name === 'AbortError') throw error; console.error('Artist search failed:', error); return { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 }; } } - async searchAlbums(query) { + async searchAlbums(query, options = {}) { const cached = await this.cache.get('search_albums', query); if (cached) return cached; try { - const response = await this.fetchWithRetry(`/search/?al=${encodeURIComponent(query)}`); + const response = await this.fetchWithRetry(`/search/?al=${encodeURIComponent(query)}`, options); const data = await response.json(); const normalized = this.normalizeSearchResponse(data, 'albums'); const result = { @@ -277,17 +279,18 @@ export class LosslessAPI { await this.cache.set('search_albums', query, result); return result; } catch (error) { + if (error.name === 'AbortError') throw error; console.error('Album search failed:', error); return { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 }; } } - async searchPlaylists(query) { + async searchPlaylists(query, options = {}) { const cached = await this.cache.get('search_playlists', query); if (cached) return cached; try { - const response = await this.fetchWithRetry(`/search/?p=${encodeURIComponent(query)}`); + const response = await this.fetchWithRetry(`/search/?p=${encodeURIComponent(query)}`, options); const data = await response.json(); const normalized = this.normalizeSearchResponse(data, 'playlists'); const result = { @@ -298,6 +301,7 @@ export class LosslessAPI { await this.cache.set('search_playlists', query, result); return result; } catch (error) { + if (error.name === 'AbortError') throw error; console.error('Playlist search failed:', error); return { items: [], limit: 0, offset: 0, totalNumberOfItems: 0 }; } diff --git a/js/player.js b/js/player.js index 8cb5090..93532ab 100644 --- a/js/player.js +++ b/js/player.js @@ -143,6 +143,9 @@ export class Player { if (this.preloadAbortController.signal.aborted) break; this.preloadCache.set(track.id, streamUrl); + + // Warm connection/cache + fetch(streamUrl, { method: 'HEAD', signal: this.preloadAbortController.signal }).catch(() => {}); } catch (error) { if (error.name !== 'AbortError') { console.debug('Failed to get stream URL for preload:', trackTitle); @@ -250,6 +253,7 @@ export class Player { if (this.audio.paused) { this.audio.play().catch(e => { + if (e.name === 'NotAllowedError' || e.name === 'AbortError') return; console.error("Play failed, reloading track:", e); if (this.currentTrack) { this.playTrackFromQueue(); diff --git a/js/ui.js b/js/ui.js index 78c067c..b0cfb45 100644 --- a/js/ui.js +++ b/js/ui.js @@ -6,6 +6,7 @@ export class UIRenderer { constructor(api) { this.api = api; this.currentTrack = null; + this.searchAbortController = null; } setCurrentTrack(track) { @@ -371,12 +372,18 @@ export class UIRenderer { albumsContainer.innerHTML = this.createSkeletonCards(6, false); playlistsContainer.innerHTML = this.createSkeletonCards(6, false); + if (this.searchAbortController) { + this.searchAbortController.abort(); + } + this.searchAbortController = new AbortController(); + const signal = this.searchAbortController.signal; + try { const [tracksResult, artistsResult, albumsResult, playlistsResult] = await Promise.all([ - this.api.searchTracks(query), - this.api.searchArtists(query), - this.api.searchAlbums(query), - this.api.searchPlaylists(query) + this.api.searchTracks(query, { signal }), + this.api.searchArtists(query, { signal }), + this.api.searchAlbums(query, { signal }), + this.api.searchPlaylists(query, { signal }) ]); let finalTracks = tracksResult.items; @@ -430,6 +437,7 @@ export class UIRenderer { : createPlaceholder('No playlists found.'); } catch (error) { + if (error.name === 'AbortError') return; console.error("Search failed:", error); const errorMsg = createPlaceholder(`Error during search. ${error.message}`); tracksContainer.innerHTML = errorMsg; From e67a4442580db10a36de66eb24ed5859282f2b9a Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Wed, 24 Dec 2025 13:52:42 +0100 Subject: [PATCH 2/3] fix: non centered play button --- js/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/utils.js b/js/utils.js index 12aa916..112c925 100644 --- a/js/utils.js +++ b/js/utils.js @@ -26,8 +26,8 @@ export const QUALITY_TOKENS = { export const RATE_LIMIT_ERROR_MESSAGE = 'Too Many Requests. Please wait a moment and try again.'; -export const SVG_PLAY = ''; -export const SVG_PAUSE = ''; +export const SVG_PLAY = ''; +export const SVG_PAUSE = ''; export const SVG_VOLUME = ''; export const SVG_MUTE = ''; From da13a52b2a7e1c397b43a05c6c6e1f322193eb43 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Wed, 24 Dec 2025 14:04:35 +0100 Subject: [PATCH 3/3] fix: try to show progress bar in Android notification --- js/events.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/events.js b/js/events.js index 821aa2c..4259aea 100644 --- a/js/events.js +++ b/js/events.js @@ -33,12 +33,14 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler) { } playPauseBtn.innerHTML = SVG_PAUSE; player.updateMediaSessionPlaybackState(); + player.updateMediaSessionPositionState(); updateTabTitle(player); }); audioPlayer.addEventListener('pause', () => { playPauseBtn.innerHTML = SVG_PLAY; player.updateMediaSessionPlaybackState(); + player.updateMediaSessionPositionState(); }); audioPlayer.addEventListener('ended', () => {