fix: Samsung notification stays alive; retry play() on background rejection

- 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 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-03 22:29:30 +02:00
1 parent 33dc07ed5d
commit 3f5a5a4c43
2 files changed
+22 -2

No files matched your search

+6 -1
View File
@@ -152,7 +152,12 @@ window.addEventListener('load', () => {
_errRetries++; _errRetries++;
const delay = Math.min(1000 * Math.pow(2, _errRetries - 1), 8000); // 1s, 2s, 4s, 8s const delay = Math.min(1000 * Math.pow(2, _errRetries - 1), 8000); // 1s, 2s, 4s, 8s
clearTimeout(_errTimer); 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; S.audio.volume = 0.5;
+16 -1
View File
@@ -52,6 +52,8 @@ export function playCurrent() {
if(S.qIdx < 0 || S.qIdx >= S.queue.length) return; if(S.qIdx < 0 || S.qIdx >= S.queue.length) return;
const s = S.queue[S.qIdx]; const s = S.queue[S.qIdx];
S._trackChanging = true; 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 updatePlayerUI(s); // update metadata/notification BEFORE src change
onNpSongChange(s.id); onNpSongChange(s.id);
renderQueue(); renderQueue();
@@ -59,7 +61,20 @@ export function playCurrent() {
const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id); const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id);
_clearPreload(); _clearPreload();
S.audio.src = src; 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) { export function updatePlayerUI(s) {