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

+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' },