0ef8e9fbd24278d1e4f0843da73b1b95a628825f
The player used a single <audio> element and advanced by swapping .src, which re-fetches + re-buffers the next file — an audible gap between tracks. For a continuous DJ mix (silence already trimmed at the edges) that's the whole problem. Replace the <audio> element with a Web Audio engine that decodes each track to an AudioBuffer and schedules the NEXT track's source to start() at the exact AudioContext time the current track ends → sample-accurate, zero gap on auto-advance. - gapless-engine.ts (new): AudioContext + gain, LRU-capped decoded-buffer cache, fetch-whole-file → decodeAudioData, boundary scheduling, seek/skip/play-pause (pause = ctx.suspend so the clock + scheduled next freeze together), a generation counter to invalidate stale onended/async, and a gesture unlock for autoplay policy. Callbacks: onIndex/onTime/onEndOfQueue/onLoadingChange. - MusicPlayerHost.tsx: drives the engine instead of an <audio> element. React keeps the queue/index (useMusicPlayer); user actions (new album, jump, prev/ next) command the engine, and the engine's own natural advance mirrors back via syncIndex WITHOUT restarting playback (that's what keeps the seam gapless). Preserves restore/persist/heartbeat/album-nav/volume/heart; adds a decode spinner on the play button (startup/skip has fetch+decode latency by nature). - useMusicPlayer.ts: syncIndex() — set index without touching `playing`. Trade-off (chosen deliberately over near-gapless preloading): true gapless needs the whole next file decoded to PCM ahead of time (~200MB per 10-min track), so the buffer cache is capped at 3. Verified the scheduler state machine with a mocked AudioContext: next track scheduled at the current's exact end sample, advance/promote/seek/skip/end-of-queue all correct. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Description
No description provided
42 MiB
Languages
TypeScript
90.9%
Shell
4.7%
JavaScript
4.1%
CSS
0.2%
HTML
0.1%