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:
HoneyandClaude Sonnet 4.6 committed 2026-05-03 20:23:51 +02:00
1 parent f2c77f975d
commit 89e169b70a
8 files changed
+632 -9

No files matched your search

+6 -5
View File
@@ -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
View File
@@ -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' },
+1
View File
@@ -13,6 +13,7 @@ export const S = {
previewCurrent: null,
_scrubbing: false,
_msPosLast: 0,
_trackChanging: false,
queueOpen: false,
// Now Playing overlay