fix: keep _trackChanging=true through entire recovery window (lock screen)
When play() fails on locked screen, Android may revoke audio focus and fire a spurious pause event. Previously we reset _trackChanging=false right in the catch, leaving a window for that pause event to set playbackState='paused' and dismiss the Samsung cover screen notification. Now _trackChanging stays true for the ENTIRE recovery (initial fail -> canplay retry -> success/fail), with a 10s Kima-style safety net as the only exit. This means: - AbortError: reload src+load(), _trackChanging stays true, canplay retries - NotAllowed/network: same — _trackChanging stays true, canplay retries - If canplay fires and play() succeeds: play event resets _trackChanging - If canplay fires and play() fails: _reset() is called - If canplay never fires (Doze): 10s timeout resets _trackChanging Net effect: Samsung lock screen notification never sees playbackState='paused' during the recovery window, so it stays open instead of dismissing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
688348545d
commit
b941672a77
1 file changed
+27
-20
+27
-20
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user