fix: prevent notification close on skip and background audio stall
- playCurrent: set _trackChanging flag and update media session metadata before changing src, so the pause event fired by src-swap does not set playbackState='paused' and dismiss the Samsung cover screen notification - Remove stalled->load() handler that silently paused audio with no recovery - Error handler now retries regardless of paused state (load() causes paused) - Add .catch() to audio.play() in playCurrent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
f2c77f975d
commit
89e169b70a
8 files changed
+632
-9
No files matched your search
+6
-5
@@ -126,6 +126,7 @@ window.addEventListener('load', () => {
|
||||
S.audio.addEventListener('timeupdate', updateProgress);
|
||||
S.audio.addEventListener('ended', onEnded);
|
||||
S.audio.addEventListener('play', () => {
|
||||
S._trackChanging = false;
|
||||
absInterrupt();
|
||||
setPlayIcon(true);
|
||||
document.getElementById('player-art').classList.add('spinning');
|
||||
@@ -134,16 +135,16 @@ window.addEventListener('load', () => {
|
||||
S.audio.addEventListener('pause', () => {
|
||||
setPlayIcon(false);
|
||||
document.getElementById('player-art').classList.remove('spinning');
|
||||
if(navigator.mediaSession) navigator.mediaSession.playbackState = 'paused';
|
||||
// Suppress during track change — src swap fires pause before the new track starts
|
||||
if(!S._trackChanging && navigator.mediaSession) navigator.mediaSession.playbackState = 'paused';
|
||||
});
|
||||
S.previewAudio.addEventListener('timeupdate', updatePreviewProgress);
|
||||
S.previewAudio.addEventListener('ended', stopPreview);
|
||||
|
||||
// Recovery: try to resume if audio stalls (network hiccup, background throttle)
|
||||
S.audio.addEventListener('stalled', () => { if(!S.audio.paused) S.audio.load(); });
|
||||
S.audio.addEventListener('waiting', () => { /* let browser handle buffering */ });
|
||||
// stalled = browser buffer ran out briefly; let the browser retry naturally.
|
||||
// The watchdog below catches genuine long freezes.
|
||||
S.audio.addEventListener('error', () => {
|
||||
if(!S.audio.paused && S.queue[S.qIdx]) {
|
||||
if(S.queue[S.qIdx]) {
|
||||
const t = setTimeout(() => { S.audio.load(); S.audio.play().catch(()=>{}); }, 2000);
|
||||
S.audio.addEventListener('play', () => clearTimeout(t), { once: true });
|
||||
}
|
||||
|
||||
+25
-3
@@ -17,11 +17,12 @@ export function playQueue(songs, startIdx) {
|
||||
export function playCurrent() {
|
||||
if(S.qIdx < 0 || S.qIdx >= S.queue.length) return;
|
||||
const s = S.queue[S.qIdx];
|
||||
S.audio.src = streamUrl(s.id);
|
||||
S.audio.play();
|
||||
updatePlayerUI(s);
|
||||
S._trackChanging = true;
|
||||
updatePlayerUI(s); // update metadata/notification BEFORE src change
|
||||
onNpSongChange(s.id);
|
||||
renderQueue();
|
||||
S.audio.src = streamUrl(s.id);
|
||||
S.audio.play().catch(() => {});
|
||||
}
|
||||
|
||||
export function updatePlayerUI(s) {
|
||||
@@ -122,6 +123,8 @@ export function setPlayIcon(playing) {
|
||||
const cover = document.getElementById('np-cover');
|
||||
if(cover) cover.classList.toggle('playing', playing);
|
||||
}
|
||||
// Android native MediaSession bridge
|
||||
if (window.Android) { try { window.Android.setPlaybackState(playing); } catch(_) {} }
|
||||
}
|
||||
|
||||
export function prevSong() {
|
||||
@@ -193,9 +196,16 @@ export async function fillRadioQueue() {
|
||||
}
|
||||
|
||||
// ─── PROGRESS & SCRUBBING ───
|
||||
let _androidPosTick = 0;
|
||||
export function updateProgress() {
|
||||
if(!S.audio.duration || S._scrubbing) return;
|
||||
syncMediaSessionPosition();
|
||||
// Update Android native position every ~5 seconds to keep lock screen scrubber in sync
|
||||
const now = Date.now();
|
||||
if(now - _androidPosTick > 5000) {
|
||||
_androidPosTick = now;
|
||||
if (window.Android) { try { window.Android.updatePosition(S.audio.currentTime, S.audio.duration); } catch(_) {} }
|
||||
}
|
||||
const p = (S.audio.currentTime / S.audio.duration) * 100;
|
||||
document.getElementById('progress-fill').style.width = p + '%';
|
||||
document.getElementById('time-cur').textContent = formatDur(S.audio.currentTime);
|
||||
@@ -279,6 +289,18 @@ export function setVolume(v) {
|
||||
|
||||
// ─── MEDIA SESSION ───
|
||||
export function updateMediaSession(s) {
|
||||
// Android native MediaSession bridge — use 512px art for high-DPI screens
|
||||
if (window.Android) {
|
||||
try {
|
||||
window.Android.updateTrack(
|
||||
s.title || '',
|
||||
s.artist || '',
|
||||
s.coverArt ? coverUrl(s.coverArt, 512) : '',
|
||||
s.duration || 0
|
||||
);
|
||||
} catch(_) {}
|
||||
}
|
||||
|
||||
if(!navigator.mediaSession) return;
|
||||
const artwork = s.coverArt ? [
|
||||
{ src: coverUrl(s.coverArt,96), sizes:'96x96', type:'image/jpeg' },
|
||||
|
||||
@@ -13,6 +13,7 @@ export const S = {
|
||||
previewCurrent: null,
|
||||
_scrubbing: false,
|
||||
_msPosLast: 0,
|
||||
_trackChanging: false,
|
||||
queueOpen: false,
|
||||
|
||||
// Now Playing overlay
|
||||
|
||||
Reference in new issue
Block a user