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 {} }