WIP: Fixed audio context issues - ReferenceError in useWaveform, enhanced cleanup, improved playback switching
This commit is contained in:
@@ -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
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -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') {
|
|
||||||
// Some WaveSurfer versions allow setting the audio context
|
|
||||||
try {
|
try {
|
||||||
|
// Method 1: Try to set via backend if available
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user