From 3f5a5a4c43b0e9c4590ed04cb016708151a38444 Mon Sep 17 00:00:00 2001 From: Honey Date: Sun, 3 May 2026 22:29:30 +0200 Subject: [PATCH] fix: Samsung notification stays alive; retry play() on background rejection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set playbackState='playing' explicitly before MediaMetadata swap so Samsung One UI never sees a gap where state is ambiguous during skip - Add retry loop (500ms→1s→2s→4s→8s) in playCurrent() catch — handles Android Doze rejecting play() in background; stops retrying if the song changes mid-flight (AbortError) - Fix error handler: set _trackChanging=true before audio.load() so the load()'s pause event can't slip past the Samsung notification guard Co-Authored-By: Claude Sonnet 4.6 --- src/main.js | 7 ++++++- src/player.js | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index 44ca135..81a1efd 100644 --- a/src/main.js +++ b/src/main.js @@ -152,7 +152,12 @@ window.addEventListener('load', () => { _errRetries++; const delay = Math.min(1000 * Math.pow(2, _errRetries - 1), 8000); // 1s, 2s, 4s, 8s clearTimeout(_errTimer); - _errTimer = setTimeout(() => { S.audio.load(); S.audio.play().catch(() => {}); }, delay); + _errTimer = setTimeout(() => { + if(!S.queue[S.qIdx]) return; + S._trackChanging = true; // prevent load()'s pause event from dismissing Samsung notification + S.audio.load(); + S.audio.play().catch(() => { S._trackChanging = false; }); + }, delay); }); S.audio.volume = 0.5; diff --git a/src/player.js b/src/player.js index d483603..d7aef7d 100644 --- a/src/player.js +++ b/src/player.js @@ -52,6 +52,8 @@ export function playCurrent() { if(S.qIdx < 0 || S.qIdx >= S.queue.length) return; const s = S.queue[S.qIdx]; S._trackChanging = true; + // Explicitly keep Samsung notification alive — set playing BEFORE metadata swap + if(navigator.mediaSession) navigator.mediaSession.playbackState = 'playing'; updatePlayerUI(s); // update metadata/notification BEFORE src change onNpSongChange(s.id); renderQueue(); @@ -59,7 +61,20 @@ export function playCurrent() { const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id); _clearPreload(); S.audio.src = src; - S.audio.play().catch(() => { S._trackChanging = false; }); + const _tryPlay = (attempt) => { + S.audio.play().catch(err => { + S._trackChanging = false; + if (err?.name === 'AbortError') return; // src changed mid-flight, stop + if (attempt < 4 && S.queue[S.qIdx]?.id === s.id) { + setTimeout(() => { + if (S.queue[S.qIdx]?.id !== s.id) return; + S._trackChanging = true; + _tryPlay(attempt + 1); + }, Math.min(500 * Math.pow(2, attempt), 8000)); // 500ms, 1s, 2s, 4s, 8s + } + }); + }; + _tryPlay(0); } export function updatePlayerUI(s) {