refactor(audio): Phase 4 — unify song tracking, remove compat aliases

playerStore: remove currentPlayingSongId/currentPlayingBandId/setCurrentPlayingSong.
Single pair (currentSongId/currentBandId) now set exclusively when play() is
called, not when the page opens. This means MiniPlayer and sidebar links only
appear after audio has been started — correct UX for a "now playing" widget.

audioService: play() calls setCurrentSong instead of setCurrentPlayingSong;
cleanup() clears it. Remove isReadyForPlayback() and canAttemptPlayback()
aliases — all callers now use isWaveformReady() directly.

useWaveform: remove setCurrentSong call from init (store updated by play()
now); restore-playback snapshot reads currentSongId/currentBandId.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mistral Vibe
2026-04-08 20:58:16 +02:00
parent 7508d78a86
commit 25dca3c788
4 changed files with 19 additions and 39 deletions

View File

@@ -41,17 +41,12 @@ export function useWaveform(
try { try {
await audioService.initialize(containerRef.current!, options.url!); await audioService.initialize(containerRef.current!, options.url!);
// Update global song context
if (options.songId && options.bandId) {
usePlayerStore.getState().setCurrentSong(options.songId, options.bandId);
}
// Restore playback if this song was already playing when the page loaded. // Restore playback if this song was already playing when the page loaded.
// Read as a one-time snapshot — these values must NOT be reactive deps or // Read as a one-time snapshot — these values must NOT be reactive deps or
// the effect would re-run on every time update (re-initializing WaveSurfer). // the effect would re-run on every time update (re-initializing WaveSurfer).
const { const {
currentPlayingSongId, currentSongId,
currentPlayingBandId, currentBandId,
isPlaying: wasPlaying, isPlaying: wasPlaying,
currentTime: savedTime, currentTime: savedTime,
} = usePlayerStore.getState(); } = usePlayerStore.getState();
@@ -59,8 +54,8 @@ export function useWaveform(
if ( if (
options.songId && options.songId &&
options.bandId && options.bandId &&
currentPlayingSongId === options.songId && currentSongId === options.songId &&
currentPlayingBandId === options.bandId && currentBandId === options.bandId &&
wasPlaying && wasPlaying &&
audioService.isWaveformReady() audioService.isWaveformReady()
) { ) {

View File

@@ -86,7 +86,7 @@ class AudioService {
} }
await this.wavesurfer.play(); await this.wavesurfer.play();
if (songId && bandId) { if (songId && bandId) {
usePlayerStore.getState().setCurrentPlayingSong(songId, bandId); usePlayerStore.getState().setCurrentSong(songId, bandId);
} }
} }
@@ -116,19 +116,10 @@ class AudioService {
return this.isReady && !!this.wavesurfer; return this.isReady && !!this.wavesurfer;
} }
// Aliases kept for callers until Phase 3 cleans them up
public isReadyForPlayback(): boolean {
return this.isWaveformReady();
}
public canAttemptPlayback(): boolean {
return this.isWaveformReady();
}
public cleanup(): void { public cleanup(): void {
this.destroyWaveSurfer(); this.destroyWaveSurfer();
const store = usePlayerStore.getState(); const store = usePlayerStore.getState();
store.setCurrentPlayingSong(null, null); store.setCurrentSong(null, null);
store.batchUpdate({ isPlaying: false, currentTime: 0 }); store.batchUpdate({ isPlaying: false, currentTime: 0 });
} }

View File

@@ -4,17 +4,16 @@ interface PlayerState {
isPlaying: boolean; isPlaying: boolean;
currentTime: number; currentTime: number;
duration: number; duration: number;
// Set when audio starts playing; cleared on cleanup.
// Drives MiniPlayer visibility and sidebar "go to now playing" links.
currentSongId: string | null; currentSongId: string | null;
currentBandId: string | null; currentBandId: string | null;
currentPlayingSongId: string | null; // Track which song is actively playing
currentPlayingBandId: string | null; // Track which band's song is actively playing
setPlaying: (isPlaying: boolean) => void; setPlaying: (isPlaying: boolean) => void;
setCurrentTime: (currentTime: number) => void; setCurrentTime: (currentTime: number) => void;
setDuration: (duration: number) => void; setDuration: (duration: number) => void;
setCurrentSong: (songId: string | null, bandId: string | null) => void; setCurrentSong: (songId: string | null, bandId: string | null) => void;
setCurrentPlayingSong: (songId: string | null, bandId: string | null) => void;
reset: () => void; reset: () => void;
batchUpdate: (updates: Partial<Omit<PlayerState, 'setPlaying' | 'setCurrentTime' | 'setDuration' | 'setCurrentSong' | 'setCurrentPlayingSong' | 'reset' | 'batchUpdate'>>) => void; batchUpdate: (updates: Partial<Omit<PlayerState, 'setPlaying' | 'setCurrentTime' | 'setDuration' | 'setCurrentSong' | 'reset' | 'batchUpdate'>>) => void;
} }
export const usePlayerStore = create<PlayerState>()((set) => ({ export const usePlayerStore = create<PlayerState>()((set) => ({
@@ -23,21 +22,16 @@ export const usePlayerStore = create<PlayerState>()((set) => ({
duration: 0, duration: 0,
currentSongId: null, currentSongId: null,
currentBandId: null, currentBandId: null,
currentPlayingSongId: null,
currentPlayingBandId: null,
setPlaying: (isPlaying) => set({ isPlaying }), setPlaying: (isPlaying) => set({ isPlaying }),
setCurrentTime: (currentTime) => set({ currentTime }), setCurrentTime: (currentTime) => set({ currentTime }),
setDuration: (duration) => set({ duration }), setDuration: (duration) => set({ duration }),
setCurrentSong: (songId, bandId) => set({ currentSongId: songId, currentBandId: bandId }), setCurrentSong: (songId, bandId) => set({ currentSongId: songId, currentBandId: bandId }),
setCurrentPlayingSong: (songId, bandId) => set({ currentPlayingSongId: songId, currentPlayingBandId: bandId }),
batchUpdate: (updates) => set(updates), batchUpdate: (updates) => set(updates),
reset: () => set({ reset: () => set({
isPlaying: false, isPlaying: false,
currentTime: 0, currentTime: 0,
duration: 0, duration: 0,
currentSongId: null, currentSongId: null,
currentBandId: null, currentBandId: null,
currentPlayingSongId: null,
currentPlayingBandId: null
}) })
})); }));

View File

@@ -164,19 +164,19 @@ describe('AudioService', () => {
expect(mockWs.play).toHaveBeenCalled(); expect(mockWs.play).toHaveBeenCalled();
}); });
it('updates currentPlayingSongId/BandId in the store', async () => { it('updates currentSongId/BandId in the store', async () => {
await initService(service); await initService(service);
await service.play('song-1', 'band-1'); await service.play('song-1', 'band-1');
const state = usePlayerStore.getState(); const state = usePlayerStore.getState();
expect(state.currentPlayingSongId).toBe('song-1'); expect(state.currentSongId).toBe('song-1');
expect(state.currentPlayingBandId).toBe('band-1'); expect(state.currentBandId).toBe('band-1');
}); });
it('does not update store ids when called without ids', async () => { it('does not update store ids when called without ids', async () => {
await initService(service); await initService(service);
await service.play(); await service.play();
const state = usePlayerStore.getState(); const state = usePlayerStore.getState();
expect(state.currentPlayingSongId).toBeNull(); expect(state.currentSongId).toBeNull();
}); });
}); });