5.5 KiB
5.5 KiB
Static Player Debug Analysis
Issue Identified
- Player button appears in UI
- Playback stops when changing views
- State handling errors suspected
Architecture Review
Current Flow Analysis
1. State Initialization
useWaveform.tscreates WaveSurfer instance- Global store initialized with default values
- State sync happens in useEffect
2. Playback State Issues
- WaveSurfer instance destroyed when component unmounts
- Global state may not be properly restored
- Audio context issues when switching routes
3. Potential Weak Points
Weak Point 1: Waveform Destruction
Location: useWaveform.ts cleanup function
return () => {
ws.destroy(); // This destroys the audio context
wsRef.current = null;
};
Issue: When navigating away, the WaveSurfer instance is destroyed, stopping playback completely.
Weak Point 2: State Restoration Logic
Location: useWaveform.ts ready event handler
// Only restores if same song AND same band AND was playing
if (options.songId && options.bandId &&
currentSongId === options.songId &&
globalBandId === options.bandId &&
globalIsPlaying) {
ws.play(); // This may not work if audio context is suspended
}
Issue: Audio context may be suspended after route change, requiring user interaction to resume.
Weak Point 3: Global State Sync Timing
Location: State updates in audioprocess event
ws.on("audioprocess", (time) => {
setCurrentTime(time);
setGlobalCurrentTime(time);
options.onTimeUpdate?.(time);
});
Issue: Local state updates may not properly sync with global state during route transitions.
Weak Point 4: Component Lifecycle
Issue: SongPage component unmounts → waveform destroyed → state lost → new component mounts with fresh state.
Root Cause Analysis
Primary Issue: Audio Context Lifecycle
- WaveSurfer creates an AudioContext
- When component unmounts, AudioContext is destroyed
- New component creates new AudioContext
- Browser requires user interaction to resume suspended audio contexts
- Even if we restore state, audio won't play without user interaction
Secondary Issue: State Restoration Timing
- Global state may be updated after component unmounts
- New component may mount before global state is fully updated
- Race condition in state restoration
Solution Architecture
Option 1: Persistent Audio Context (Recommended)
- Move WaveSurfer instance outside React component lifecycle
- Create singleton audio service
- Maintain audio context across route changes
- Use global state only for UI synchronization
Option 2: Audio Context Recovery
- Handle suspended audio context states
- Add user interaction requirement handling
- Implement graceful degradation
Option 3: Hybrid Approach
- Keep minimal global state for navigation
- Create persistent audio manager
- Sync between audio manager and React components
Implementation Plan for Fix
Step 1: Create Audio Service (New File)
// web/src/services/audioService.ts
class AudioService {
private static instance: AudioService;
private wavesurfer: WaveSurfer | null = null;
private audioContext: AudioContext | null = null;
private constructor() {}
public static getInstance() {
if (!this.instance) {
this.instance = new AudioService();
}
return this.instance;
}
public initialize(container: HTMLElement, url: string) {
// Create wavesurfer with persistent audio context
}
public play() {
// Handle suspended audio context
if (this.audioContext?.state === 'suspended') {
this.audioContext.resume();
}
this.wavesurfer?.play();
}
public cleanup() {
// Don't destroy audio context, just disconnect nodes
}
}
Step 2: Modify Waveform Hook
- Use audio service instead of local WaveSurfer instance
- Sync service state with global store
- Handle component mount/unmount gracefully
Step 3: Update Global State Management
- Separate audio state from UI state
- Add audio context status tracking
- Implement proper error handling
Step 4: Add User Interaction Handling
- Detect suspended audio context
- Provide UI feedback
- Handle resume on user interaction
Debugging Steps
1. Verify Current Behavior
# Check browser console for audio context errors
# Look for "play() failed because the user didn't interact with the document first"
2. Add Debug Logging
// Add to useWaveform.ts
console.log('Waveform ready, attempting to restore state:', {
currentSongId,
globalBandId,
globalIsPlaying,
globalCurrentTime
});
// Add audio context state logging
console.log('Audio context state:', ws.backend.getAudioContext().state);
3. Test State Restoration
- Start playback
- Navigate away
- Check global store state in Redux devtools
- Navigate back
- Verify state is restored correctly
Recommended Fix Strategy
Short-term Fix (Quick Implementation)
- Modify
useWaveform.tsto handle suspended audio context - Add user interaction requirement detection
- Implement graceful fallback when audio context is suspended
Long-term Fix (Robust Solution)
- Create persistent audio service
- Separate audio management from React components
- Implement proper audio context lifecycle management
- Add comprehensive error handling
Next Steps
- Add debug logging to identify exact failure point
- Implement suspended audio context handling
- Test state restoration with debug logs
- Implement persistent audio service if needed