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:
1 parent
33dc07ed5d
commit
3f5a5a4c43
2 files changed
+22
-2
No files matched your search
+6
-1
@@ -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
@@ -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) {
|
||||||
|
|||||||
Reference in new issue
Block a user