fix(audio): re-attach waveform canvas on re-navigation to same song

When navigating away from SongPage and back to the same song, the container
div is a new DOM element but the URL is unchanged. The previous early-return
(currentUrl === url) would skip initialization entirely, leaving WaveSurfer
pointing at the detached old container — nothing rendered.

Fix: track currentContainer alongside currentUrl. When URL matches but container
has changed, call wavesurfer.setOptions({ container }) which moves the existing
canvas into the new container without reloading audio or interrupting playback.
WaveSurfer v7 renderer.setOptions() supports this: it calls
newParent.appendChild(this.container) to relocate the canvas div.

Three paths in initialize():
  1. Same URL + same container → no-op
  2. Same URL + new container  → setOptions re-attach (no reload)
  3. Different URL             → full teardown and reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mistral Vibe
2026-04-08 21:31:08 +02:00
parent 8b7415954c
commit a0cc10ffca
2 changed files with 40 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ class AudioService {
private static instance: AudioService;
private wavesurfer: WaveSurfer | null = null;
private currentUrl: string | null = null;
private currentContainer: HTMLElement | null = null;
private isReady = false;
private lastTimeUpdate = 0;
// Persistent audio element attached to document.body so playback survives
@@ -44,10 +45,18 @@ class AudioService {
if (!container) throw new Error('Container element is required');
if (!url) throw new Error('Valid audio URL is required');
// Reuse the existing instance when the URL hasn't changed
if (this.currentUrl === url && this.wavesurfer) return;
// Same URL and same container — nothing to do
if (this.currentUrl === url && this.wavesurfer && this.currentContainer === container) return;
// Tear down the previous instance and clear stale store state
// Same URL, different container: navigated away and back to the same song.
// Move the waveform canvas to the new container without reloading audio.
if (this.currentUrl === url && this.wavesurfer) {
this.wavesurfer.setOptions({ container });
this.currentContainer = container;
return;
}
// Different URL — tear down the previous instance and clear stale store state
if (this.wavesurfer) {
this.destroyWaveSurfer();
usePlayerStore.getState().batchUpdate({ isPlaying: false, currentTime: 0, duration: 0 });
@@ -72,6 +81,7 @@ class AudioService {
this.wavesurfer = ws;
this.currentUrl = url;
this.currentContainer = container;
this.setupEventHandlers(ws);
await new Promise<void>((resolve, reject) => {
@@ -162,6 +172,7 @@ class AudioService {
this.mediaElement = null;
this.wavesurfer = null;
this.currentUrl = null;
this.currentContainer = null;
this.isReady = false;
}
}