diff --git a/src/player.js b/src/player.js index 75c0caa..beac6d4 100644 --- a/src/player.js +++ b/src/player.js @@ -79,32 +79,39 @@ export function playCurrent() { const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id); _clearPreload(); S.audio.src = src; - // 10s safety net: if neither play-event nor catch fires (e.g. Doze killed everything), - // reset _trackChanging so the pause-event guard doesn't stay stuck forever - const _guard = setTimeout(() => { S._trackChanging = false; }, 10000); + + const _reset = () => { S._trackChanging = false; }; + + // After play() fails: wait for canplay (audio engine, not throttled by Android timers). + // _trackChanging stays TRUE throughout so spurious pause events from Android revoking + // audio focus on locked screen can't slip through and dismiss the Samsung cover screen. + function _waitCanPlay() { + const guard = setTimeout(_reset, 10000); // 10s Kima-style failsafe + S.audio.addEventListener('canplay', () => { + clearTimeout(guard); + if (S.queue[S.qIdx]?.id !== s.id || !S.audio.paused) { _reset(); return; } + try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {} + S.audio.play().catch(_reset); + }, { once: true }); + } + + const _initGuard = setTimeout(_reset, 10000); try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {} S.audio.play() - .then(() => clearTimeout(_guard)) + .then(() => clearTimeout(_initGuard)) .catch(err => { - clearTimeout(_guard); - if (S.queue[S.qIdx]?.id !== s.id) { S._trackChanging = false; return; } + clearTimeout(_initGuard); + if (S.queue[S.qIdx]?.id !== s.id) { _reset(); return; } if (err?.name === 'AbortError') { - // Audio element in bad state (OS interruption / load race) — Kima Hub pattern: - // reload the src so the element settles, then let canplay trigger the retry + // Audio element in bad state (OS interruption / load race) — reload so the + // element settles; _trackChanging stays true so load()'s pause is suppressed S.audio.src = src; - S.audio.load(); // _trackChanging stays true; load()'s pause event is suppressed - } else { - // NotAllowed / network error — reset flag, wait for canplay to retry - S._trackChanging = false; + S.audio.load(); } - // canplay fires from the audio engine (not throttled by Android background timers). - // Registered ONLY here (in the failure path) — no race with the initial play() call. - S.audio.addEventListener('canplay', () => { - if (S.queue[S.qIdx]?.id !== s.id || !S.audio.paused) return; - S._trackChanging = true; - try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {} - S.audio.play().catch(() => { S._trackChanging = false; }); - }, { once: true }); + // For ALL errors: keep _trackChanging=true until canplay fires (or 10s timeout). + // This prevents Android audio-focus-loss pause events from reaching Samsung + // and dismissing the lock screen notification during the recovery window. + _waitCanPlay(); }); }