191 lines
5.5 KiB
Markdown
191 lines
5.5 KiB
Markdown
# 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.ts` creates 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
|
|
```typescript
|
|
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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
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
|
|
1. WaveSurfer creates an AudioContext
|
|
2. When component unmounts, AudioContext is destroyed
|
|
3. New component creates new AudioContext
|
|
4. Browser requires user interaction to resume suspended audio contexts
|
|
5. Even if we restore state, audio won't play without user interaction
|
|
|
|
### Secondary Issue: State Restoration Timing
|
|
1. Global state may be updated after component unmounts
|
|
2. New component may mount before global state is fully updated
|
|
3. 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)
|
|
```typescript
|
|
// 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
|
|
```bash
|
|
# 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
|
|
```typescript
|
|
// 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)
|
|
1. Modify `useWaveform.ts` to handle suspended audio context
|
|
2. Add user interaction requirement detection
|
|
3. Implement graceful fallback when audio context is suspended
|
|
|
|
### Long-term Fix (Robust Solution)
|
|
1. Create persistent audio service
|
|
2. Separate audio management from React components
|
|
3. Implement proper audio context lifecycle management
|
|
4. Add comprehensive error handling
|
|
|
|
## Next Steps
|
|
1. Add debug logging to identify exact failure point
|
|
2. Implement suspended audio context handling
|
|
3. Test state restoration with debug logs
|
|
4. Implement persistent audio service if needed |