WIP: Fixed audio context issues - ReferenceError in useWaveform, enhanced cleanup, improved playback switching

This commit is contained in:
Mistral Vibe
2026-04-08 17:00:28 +02:00
parent 5f95d88741
commit 9c032d0774
3 changed files with 79 additions and 14 deletions

View File

@@ -35,12 +35,16 @@ export function useWaveform(
currentTime: globalCurrentTime, currentTime: globalCurrentTime,
currentSongId, currentSongId,
currentBandId: globalBandId, currentBandId: globalBandId,
currentPlayingSongId,
currentPlayingBandId,
setCurrentSong setCurrentSong
} = usePlayerStore(state => ({ } = usePlayerStore(state => ({
isPlaying: state.isPlaying, isPlaying: state.isPlaying,
currentTime: state.currentTime, currentTime: state.currentTime,
currentSongId: state.currentSongId, currentSongId: state.currentSongId,
currentBandId: state.currentBandId, currentBandId: state.currentBandId,
currentPlayingSongId: state.currentPlayingSongId,
currentPlayingBandId: state.currentPlayingBandId,
setCurrentSong: state.setCurrentSong setCurrentSong: state.setCurrentSong
})); }));

View File

@@ -327,16 +327,22 @@ private readonly PLAY_DEBOUNCE_MS: number = 100;
this.log(LogLevel.DEBUG, 'AudioService.play called', { songId, bandId }); this.log(LogLevel.DEBUG, 'AudioService.play called', { songId, bandId });
try { try {
// Always stop current playback first to ensure only one audio plays at a time
if (this.isPlaying()) {
this.log(LogLevel.INFO, 'Stopping current playback before starting new one');
this.pause();
// Small delay to ensure cleanup
await new Promise(resolve => setTimeout(resolve, 50));
}
// Check if we need to switch songs // Check if we need to switch songs
const isDifferentSong = songId && bandId && const isDifferentSong = songId && bandId &&
(this.currentPlayingSongId !== songId || this.currentPlayingBandId !== bandId); (this.currentPlayingSongId !== songId || this.currentPlayingBandId !== bandId);
// If switching to a different song, stop current playback first // If switching to a different song, perform cleanup
if (isDifferentSong && this.isPlaying()) { if (isDifferentSong) {
this.log(LogLevel.INFO, 'Switching songs - stopping current playback first'); this.log(LogLevel.INFO, 'Switching to different song - performing cleanup');
this.pause(); this.cleanup();
// Small delay to ensure cleanup
await new Promise(resolve => setTimeout(resolve, 50));
} }
// Ensure we have a valid audio context // Ensure we have a valid audio context
@@ -444,6 +450,10 @@ private readonly PLAY_DEBOUNCE_MS: number = 100;
if (this.wavesurfer) { if (this.wavesurfer) {
try { try {
// Always stop playback first
if (this.wavesurfer.isPlaying()) {
this.wavesurfer.pause();
}
// Disconnect audio nodes but keep audio context alive // Disconnect audio nodes but keep audio context alive
this.wavesurfer.unAll(); this.wavesurfer.unAll();
this.wavesurfer.destroy(); this.wavesurfer.destroy();
@@ -458,9 +468,10 @@ private readonly PLAY_DEBOUNCE_MS: number = 100;
this.currentPlayingSongId = null; this.currentPlayingSongId = null;
this.currentPlayingBandId = null; this.currentPlayingBandId = null;
// Reset player store // Reset player store completely
const playerStore = usePlayerStore.getState(); const playerStore = usePlayerStore.getState();
playerStore.setCurrentPlayingSong(null, null); playerStore.setCurrentPlayingSong(null, null);
playerStore.batchUpdate({ isPlaying: false, currentTime: 0 });
// Note: We intentionally don't nullify audioContext to keep it alive // Note: We intentionally don't nullify audioContext to keep it alive
} }
@@ -504,18 +515,35 @@ private readonly PLAY_DEBOUNCE_MS: number = 100;
private setupAudioContext(ws: WaveSurferWithBackend) { private setupAudioContext(ws: WaveSurferWithBackend) {
// Simplified audio context setup - we now manage audio context centrally // Simplified audio context setup - we now manage audio context centrally
try { try {
// If we already have an audio context, use it for WaveSurfer // If we already have an audio context, ensure WaveSurfer uses it
if (this.audioContext) { if (this.audioContext) {
// Try to set the audio context for WaveSurfer if possible // Try multiple ways to share the audio context with WaveSurfer
if (ws.backend && typeof ws.backend.getAudioContext === 'function') { try {
// Some WaveSurfer versions allow setting the audio context // Method 1: Try to set via backend if available
try { if (ws.backend) {
// @ts-expect-error - WaveSurfer typing doesn't expose this // @ts-expect-error - WaveSurfer typing doesn't expose this
ws.backend.audioContext = this.audioContext; ws.backend.audioContext = this.audioContext;
this.log(LogLevel.DEBUG, 'Shared audio context with WaveSurfer backend'); this.log(LogLevel.DEBUG, 'Shared audio context with WaveSurfer backend');
} catch (error) {
this.log(LogLevel.DEBUG, 'Could not share audio context with WaveSurfer, but continuing');
} }
// Method 2: Try to access and replace the audio context
if (ws.backend?.getAudioContext) {
const originalGetAudioContext = ws.backend.getAudioContext;
// @ts-expect-error - Replace the method
ws.backend.getAudioContext = () => this.audioContext;
this.log(LogLevel.DEBUG, 'Overrode backend.getAudioContext with shared context');
}
// Method 3: Try top-level getAudioContext
if (typeof ws.getAudioContext === 'function') {
const originalGetAudioContext = ws.getAudioContext;
// @ts-expect-error - Replace the method
ws.getAudioContext = () => this.audioContext;
this.log(LogLevel.DEBUG, 'Overrode ws.getAudioContext with shared context');
}
} catch (error) {
this.log(LogLevel.WARN, 'Could not share audio context with WaveSurfer, but continuing:', error);
} }
return; return;
} }

View File

@@ -223,4 +223,37 @@ describe('initializeAudioContext', () => {
}); });
}); });
describe('cleanup', () => {
it('should stop playback and clean up properly', () => {
// Mock a playing wavesurfer instance
const mockWavesurfer = {
isPlaying: vi.fn(() => true),
pause: vi.fn(),
unAll: vi.fn(),
destroy: vi.fn()
};
audioService['wavesurfer'] = mockWavesurfer;
audioService['currentPlayingSongId'] = 'song-123';
audioService['currentPlayingBandId'] = 'band-456';
audioService.cleanup();
expect(mockWavesurfer.pause).toHaveBeenCalled();
expect(mockWavesurfer.unAll).toHaveBeenCalled();
expect(mockWavesurfer.destroy).toHaveBeenCalled();
expect(audioService['wavesurfer']).toBeNull();
expect(audioService['currentPlayingSongId']).toBeNull();
expect(audioService['currentPlayingBandId']).toBeNull();
});
it('should handle cleanup when no wavesurfer instance exists', () => {
audioService['wavesurfer'] = null;
audioService['currentPlayingSongId'] = 'song-123';
expect(() => audioService.cleanup()).not.toThrow();
expect(audioService['currentPlayingSongId']).toBeNull();
});
});
}); });