fix(audio): survive navigation, clear stale state, silence noisy logs
Bug 1 — playback stops on navigation: WaveSurfer v7 creates its <audio> element inside the container div. When SongPage unmounts, the container is removed from the DOM, taking the audio element with it and stopping playback. Fix: AudioService owns a persistent hidden <audio> element on document.body and passes it to WaveSurfer via the `media` option. WaveSurfer uses it for playback but does not destroy it on WaveSurfer.destroy(), so audio survives any number of navigations. Bug 2 — stale playhead/duration when opening a new song: initialize() called destroyWaveSurfer() but never reset the store, so the previous song's currentTime, duration, and isPlaying leaked into the new song's load sequence. Fix: reset those three fields in the store immediately after tearing down the old WaveSurfer instance. cleanup() also now resets duration. Bug 3 — excessive console noise on mobile: Remove console.warn from play() (silent return when not ready) and from useWaveform's play() wrapper. Only console.error on actual errors remains. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,22 @@ describe('AudioService', () => {
|
||||
expect(vi.mocked(WaveSurfer.create).mock.calls.length).toBe(createCallCount); // no new instance
|
||||
});
|
||||
|
||||
it('resets store state when URL changes', async () => {
|
||||
await initService(service, { url: 'http://example.com/a.mp3', duration: 180 });
|
||||
usePlayerStore.getState().batchUpdate({ isPlaying: true, currentTime: 45 });
|
||||
|
||||
const WaveSurfer = (await import('wavesurfer.js')).default;
|
||||
const mockWs2 = createMockWaveSurfer();
|
||||
vi.mocked(WaveSurfer.create).mockReturnValue(mockWs2 as any);
|
||||
const p2 = service.initialize(makeContainer(), 'http://example.com/b.mp3');
|
||||
mockWs2._emit('ready');
|
||||
await p2;
|
||||
|
||||
// State should be reset, not carry over from the previous song
|
||||
expect(usePlayerStore.getState().currentTime).toBe(0);
|
||||
expect(usePlayerStore.getState().isPlaying).toBe(false);
|
||||
});
|
||||
|
||||
it('destroys old instance when URL changes', async () => {
|
||||
const WaveSurfer = (await import('wavesurfer.js')).default;
|
||||
|
||||
@@ -152,10 +168,8 @@ describe('AudioService', () => {
|
||||
|
||||
describe('play()', () => {
|
||||
it('does nothing if not ready', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
await service.play('song-1', 'band-1');
|
||||
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('before ready'));
|
||||
warnSpy.mockRestore();
|
||||
// play() silently returns when not ready — no throw, no warn
|
||||
await expect(service.play('song-1', 'band-1')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('calls wavesurfer.play()', async () => {
|
||||
@@ -225,23 +239,14 @@ describe('AudioService', () => {
|
||||
expect(service.isWaveformReady()).toBe(false);
|
||||
});
|
||||
|
||||
it('resets isPlaying and currentTime in the store', async () => {
|
||||
await initService(service);
|
||||
it('resets isPlaying, currentTime, and duration in the store', async () => {
|
||||
await initService(service, { duration: 180 });
|
||||
usePlayerStore.getState().batchUpdate({ isPlaying: true, currentTime: 30 });
|
||||
service.cleanup();
|
||||
const state = usePlayerStore.getState();
|
||||
expect(state.isPlaying).toBe(false);
|
||||
expect(state.currentTime).toBe(0);
|
||||
});
|
||||
|
||||
it('pauses before destroying if playing', async () => {
|
||||
const callOrder: string[] = [];
|
||||
const mockWs = await initService(service);
|
||||
mockWs.isPlaying.mockReturnValue(true);
|
||||
mockWs.pause.mockImplementation(() => callOrder.push('pause'));
|
||||
mockWs.destroy.mockImplementation(() => callOrder.push('destroy'));
|
||||
service.cleanup();
|
||||
expect(callOrder).toEqual(['pause', 'destroy']);
|
||||
expect(state.duration).toBe(0);
|
||||
});
|
||||
|
||||
it('is safe to call when not initialized', () => {
|
||||
|
||||
Reference in New Issue
Block a user