From e31ddec843dc390f930de535ecf8dc1d0eea828d Mon Sep 17 00:00:00 2001 From: Honey Date: Mon, 27 Apr 2026 18:53:25 +0200 Subject: [PATCH] fix(mobile): restore playback state + MediaSession stability - _restoreLastPlayed: set audio.src + call updateMediaSession so play button works immediately and notification shows artwork on app open - visibilitychange: re-register all MediaSession handlers after screen unlock, fold/flip, or background return (fixes vanishing notification) - Stall watchdog (3s interval): detects frozen audio in background and calls play() to recover; also catches missed 'ended' events so auto-advance works reliably when screen is off - stalled/error listeners: attempt audio reload on network hiccup Co-Authored-By: Claude Sonnet 4.6 --- src/main.js | 40 +++++++++++++++++++++++++++++++++++++++- src/player.js | 4 ++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index 30da7d6..eea0010 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,7 @@ import { initProgressScrub, updateProgress, updatePreviewProgress, stopPreview, toggleRadioMode, starCurrent, downloadCurrent, renderQueue, playPreview, toggleStar, dlSongLocal, _applyRadioBtn, _restoreLastPlayed, setPlayIcon, - npToggleStar, + npToggleStar, updateMediaSession, } from './player.js'; import { navigate, _navGoBack, _navLoad, loadHomeData, onLocalSearch, onMobileSearch, @@ -106,8 +106,46 @@ window.addEventListener('load', () => { }); 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 */ }); + S.audio.addEventListener('error', () => { + if(!S.audio.paused && S.queue[S.qIdx]) { + const t = setTimeout(() => { S.audio.load(); S.audio.play().catch(()=>{}); }, 2000); + S.audio.addEventListener('play', () => clearTimeout(t), { once: true }); + } + }); + S.audio.volume = 0.8; + // Re-register MediaSession handlers when returning from background/screen-off/flip + document.addEventListener('visibilitychange', () => { + if(document.visibilityState !== 'visible') return; + const s = S.queue[S.qIdx]; + if(!s) return; + updateMediaSession(s); + if(navigator.mediaSession) + navigator.mediaSession.playbackState = S.audio.paused ? 'paused' : 'playing'; + }); + + // Watchdog: detect when audio silently stalled in background (ended event not fired, or frozen) + let _wdLast = 0, _wdStalledAt = 0; + setInterval(() => { + if(!S.audio.src || S.audio.paused) { _wdStalledAt = 0; _wdLast = 0; return; } + const dur = S.audio.duration; + const cur = S.audio.currentTime; + // ended but event didn't fire (background throttling) + if(dur > 0 && cur >= dur - 0.5) { onEnded(); _wdStalledAt = 0; _wdLast = 0; return; } + if(cur !== _wdLast) { _wdStalledAt = 0; _wdLast = cur; return; } + if(!_wdStalledAt) { _wdStalledAt = Date.now(); return; } + // frozen for >8s → try to resume + if(Date.now() - _wdStalledAt > 8000) { + _wdStalledAt = 0; + S.audio.play().catch(() => {}); + } + }, 3000); + injectPWA(); initProgressScrub(); diff --git a/src/player.js b/src/player.js index b71d4f5..d522f34 100644 --- a/src/player.js +++ b/src/player.js @@ -353,5 +353,9 @@ export function _restoreLastPlayed() { art.innerHTML = cv ? `` : '
♪
'; updateStarBtn(); document.title = `${s.title} — NovaSonic`; + // set src so the play button works immediately without needing to re-select the song + S.audio.src = streamUrl(s.id); + S.audio.load(); + updateMediaSession(s); } catch {} }