fix(audio): gapless MSE playback - background auto-advance survives on Android
Root cause of "next track plays 1 second, then pauses and the media
notification closes" on backgrounded mobile Chrome/PWA: every track
change did pause() + audio.src=<new blob> + load() + play(), which the
browser treats as a NEW playback session - and Chrome's background media
policy pauses new sessions started in the background. Only a CONTINUING
session may keep playing.
The player now has two engines:
- MSE chain (Chrome/Firefox/Android): one MediaSource + one 'audio/mpeg'
SourceBuffer (every track is uniform transcoder mp3). The next track's
bytes are appended into the same buffer, so the whole queue is one
continuous playback session; auto-advance is just the timeline flowing
past a segment boundary - no pause/src/load/play at all. Track chrome,
per-track time display, seek, lock-screen position state and session
save/restore all work in track coordinates via a segment table. Played
data is pruned (~2 tracks buffered), ID3 tags are stripped between
appends, QuotaExceeded evicts and retries.
- Blob fallback (iOS Safari - no MSE; or runtime MSE failure): the
previous per-track objectURL behaviour, now fed from shared byte
fetches. An audio error while the MSE chain is active permanently
falls back to blobs for the session and retries the same track.
Manual actions (play/next/prev/queue click/restore) start a fresh chain -
those happen in the foreground where a new session is allowed.
Verified in-browser (desktop Chrome, two 8s test tones): auto-advance
crosses the boundary with ZERO pause/play events (old engine: one pair
per track), per-track time display correct, manual next resets the
chain, in-track seek works, queue wrap-around is gapless, no console
errors.
- src/assets/js/audio-player.js - the two-engine pipeline (fetchTrackBytes,
MSE chain: chainStart/appendSegment/ensureNextAppended/pruneBuffer/
maybeCrossBoundary/displayTimes; applyBlobBytes fallback;
updateTrackChrome extraction; MediaSession positionState)
- src/views/shell.ejs - cache-buster v31 -> v32
- CHANGELOG(.nl/.de).md - user-facing entry under Unreleased (3 languages)
Co-Authored-By: Claude <noreply@…>