Phases 1+2 of the background-play resilience work. Net effect on
desktop: gap-free track transitions on slow networks, automatic
recovery from transient stalls / network errors mid-track.
- preload="metadata" → "auto" on the main audio element so the
browser pre-buffers more aggressively.
- attachStallRetry() — listens for stalled / waiting / network-error
events. Schedules a reload-from-current-position after delayMs
(3s default) if the buffer hasn't recovered. MEDIA_ERR_NETWORK
triggers immediate retry. Bounded by maxRetries (3 default) to
prevent tight loops on persistent failures.
- audioLoader seam — `lib/player/audioLoader.ts` exposes a
resolve(track) → URL + optional prefetch(track) hint. Default
returns track.stream_url; the planned Tauri shell can swap via
setAudioLoader() to return blob URLs backed by a Rust cache,
mirroring the Flutter drift+LockCachingAudioSource pattern.
- Hidden prefetch <audio> element. When current track passes 50%,
start loading queue[index+1]. On track-end the swap is gap-free
via browser HTTP cache + audio buffer warm-up.
Phase 3 (service worker chunk cache) deliberately deferred — that
work belongs in the Tauri shell as native Rust caching, not as a
web-only investment that would compete with it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>