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);
|
const src = (_preId === s.id && _preUrl) ? _preUrl : streamUrl(s.id);
|
||||||
_clearPreload();
|
_clearPreload();
|
||||||
S.audio.src = src;
|
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 _reset = () => { S._trackChanging = false; };
|
||||||
const _guard = setTimeout(() => { S._trackChanging = false; }, 10000);
|
|
||||||
|
// 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 {}
|
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
||||||
S.audio.play()
|
S.audio.play()
|
||||||
.then(() => clearTimeout(_guard))
|
.then(() => clearTimeout(_initGuard))
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
clearTimeout(_guard);
|
clearTimeout(_initGuard);
|
||||||
if (S.queue[S.qIdx]?.id !== s.id) { S._trackChanging = false; return; }
|
if (S.queue[S.qIdx]?.id !== s.id) { _reset(); return; }
|
||||||
if (err?.name === 'AbortError') {
|
if (err?.name === 'AbortError') {
|
||||||
// Audio element in bad state (OS interruption / load race) — Kima Hub pattern:
|
// Audio element in bad state (OS interruption / load race) — reload so the
|
||||||
// reload the src so the element settles, then let canplay trigger the retry
|
// element settles; _trackChanging stays true so load()'s pause is suppressed
|
||||||
S.audio.src = src;
|
S.audio.src = src;
|
||||||
S.audio.load(); // _trackChanging stays true; load()'s pause event is suppressed
|
S.audio.load();
|
||||||
} else {
|
|
||||||
// NotAllowed / network error — reset flag, wait for canplay to retry
|
|
||||||
S._trackChanging = false;
|
|
||||||
}
|
}
|
||||||
// canplay fires from the audio engine (not throttled by Android background timers).
|
// For ALL errors: keep _trackChanging=true until canplay fires (or 10s timeout).
|
||||||
// Registered ONLY here (in the failure path) — no race with the initial play() call.
|
// This prevents Android audio-focus-loss pause events from reaching Samsung
|
||||||
S.audio.addEventListener('canplay', () => {
|
// and dismissing the lock screen notification during the recovery window.
|
||||||
if (S.queue[S.qIdx]?.id !== s.id || !S.audio.paused) return;
|
_waitCanPlay();
|
||||||
S._trackChanging = true;
|
|
||||||
try { if(navigator.audioSession) navigator.audioSession.type = 'playback'; } catch {}
|
|
||||||
S.audio.play().catch(() => { S._trackChanging = false; });
|
|
||||||
}, { once: true });
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user