238 lines
7.2 KiB
Markdown
238 lines
7.2 KiB
Markdown
# Audio Visualization Implementation Plan
|
|
|
|
## Overview
|
|
Implement real-time audio feedback visualization using the native Web Audio API to show user voice activity through a volume bar indicator.
|
|
|
|
## Architecture
|
|
|
|
### Components Hierarchy
|
|
```
|
|
Page (app/page.tsx)
|
|
└── VoiceClient (client component)
|
|
├── Audio Controls (mute/unmute button)
|
|
├── VolumeBar (visualization component)
|
|
└── WebRTC Connection Logic
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### 1. Create Audio Analysis Hook (`use-audio-level.ts`)
|
|
|
|
**Purpose:** Extract and calculate audio levels from MediaStream
|
|
|
|
**Implementation Details:**
|
|
- Accept `MediaStream | null` as input
|
|
- Return normalized volume level (0-100)
|
|
- Use Web Audio API components:
|
|
- `AudioContext` - main audio processing context
|
|
- `AnalyserNode` - provides real-time frequency/time-domain analysis
|
|
- `MediaStreamAudioSourceNode` - connects MediaStream to analyser
|
|
|
|
**Key Logic:**
|
|
```typescript
|
|
1. Create AudioContext when stream is available
|
|
2. Create AnalyserNode with fftSize = 2048 (provides 1024 frequency bins)
|
|
3. Create MediaStreamSource from the input stream
|
|
4. Connect: source → analyser (do NOT connect to destination to avoid feedback)
|
|
5. Start animation loop using requestAnimationFrame
|
|
6. In each frame:
|
|
- Call analyser.getByteTimeDomainData() to get audio samples
|
|
- Calculate RMS (Root Mean Square) for volume level
|
|
- Normalize to 0-100 range
|
|
- Update state
|
|
7. Cleanup: disconnect nodes and close context on unmount
|
|
```
|
|
|
|
**Performance Considerations:**
|
|
- Use `requestAnimationFrame` for optimal update timing (~60fps)
|
|
- Reuse `Uint8Array` buffer instead of creating new ones each frame
|
|
- Close AudioContext on cleanup to prevent memory leaks
|
|
|
|
**Edge Cases:**
|
|
- Handle null/undefined stream gracefully
|
|
- Manage AudioContext state changes
|
|
- Handle browser autoplay policies (may require user gesture)
|
|
|
|
### 2. Create Volume Bar Component (`volume-bar.tsx`)
|
|
|
|
**Purpose:** Visual representation of audio level
|
|
|
|
**Props:**
|
|
- `volume: number` (0-100 range)
|
|
- `isMuted?: boolean` (optional, for visual state)
|
|
|
|
**Visual Design:**
|
|
```
|
|
Container: Fixed height bar (e.g., 200px tall, 40px wide)
|
|
└── Fill: Height changes based on volume level
|
|
├── Color zones:
|
|
│ ├── 0-60%: Green (#10b981)
|
|
│ ├── 60-80%: Yellow (#fbbf24)
|
|
│ └── 80-100%: Red (#ef4444)
|
|
└── Animation: Smooth transitions (transition: all 0.1s ease)
|
|
```
|
|
|
|
**States:**
|
|
- Active: Shows current volume with color-coded levels
|
|
- Muted: Grayed out or show muted icon overlay
|
|
- No stream: Empty/disabled state
|
|
|
|
**Implementation:**
|
|
- Use CSS flexbox for vertical orientation
|
|
- Apply `transform: scaleY()` or dynamic height for bar fill
|
|
- Add subtle animations for smoothness
|
|
- Responsive design considerations
|
|
|
|
### 3. Integrate into Voice Client Component
|
|
|
|
**File:** `app/voice-client.tsx` (or similar)
|
|
|
|
**Integration Flow:**
|
|
```
|
|
1. Manage MediaStream state
|
|
2. Pass stream to useAudioLevel hook
|
|
3. Get volume value from hook
|
|
4. Pass volume to VolumeBar component
|
|
5. Update VolumeBar based on mute state
|
|
```
|
|
|
|
**State Management:**
|
|
```typescript
|
|
const [stream, setStream] = useState<MediaStream | null>(null);
|
|
const [isMuted, setIsMuted] = useState(false);
|
|
const volume = useAudioLevel(stream);
|
|
|
|
// When muting: keep stream active but disable audio track
|
|
stream?.getAudioTracks().forEach(track => {
|
|
track.enabled = !isMuted;
|
|
});
|
|
```
|
|
|
|
**Mute/Unmute Logic:**
|
|
- Don't stop the stream when muting (keeps analysis running)
|
|
- Disable audio track: `track.enabled = false`
|
|
- Visual feedback: gray out volume bar or add overlay
|
|
- Optional: Stop showing volume updates when muted
|
|
|
|
## Technical Specifications
|
|
|
|
### Audio Analysis Parameters
|
|
|
|
**AnalyserNode Configuration:**
|
|
- `fftSize: 2048` - Higher values = more frequency detail but slower
|
|
- `smoothingTimeConstant: 0.8` (default) - Smooths out rapid changes
|
|
- `frequencyBinCount: analyser.fftSize / 2` - Number of data points (1024)
|
|
|
|
**Volume Calculation:**
|
|
```typescript
|
|
// Get time-domain data (waveform)
|
|
const dataArray = new Uint8Array(analyser.frequencyBinCount);
|
|
analyser.getByteTimeDomainData(dataArray);
|
|
|
|
// Calculate RMS (Root Mean Square)
|
|
const rms = Math.sqrt(
|
|
dataArray.reduce((sum, value) => sum + value * value, 0) / dataArray.length
|
|
);
|
|
|
|
// Normalize: Byte values are 0-255, normalize to 0-100
|
|
const normalizedVolume = (rms / 255) * 100;
|
|
|
|
// Optional: Apply scaling for better visual response
|
|
const scaledVolume = Math.min(100, normalizedVolume * 1.5);
|
|
```
|
|
|
|
### Alternative: Using Frequency Data
|
|
```typescript
|
|
// For more responsive visualization
|
|
analyser.getByteFrequencyData(dataArray);
|
|
const average = dataArray.reduce((sum, value) => sum + value, 0) / dataArray.length;
|
|
const volume = (average / 255) * 100;
|
|
```
|
|
|
|
## File Structure
|
|
|
|
```
|
|
app/
|
|
├── page.tsx # Main page (server component)
|
|
├── voice-client.tsx # Client component with WebRTC
|
|
├── components/
|
|
│ └── volume-bar.tsx # Volume visualization component
|
|
└── hooks/
|
|
└── use-audio-level.ts # Audio analysis hook
|
|
```
|
|
|
|
## TypeScript Types
|
|
|
|
```typescript
|
|
// hooks/use-audio-level.ts
|
|
interface UseAudioLevelOptions {
|
|
enabled?: boolean;
|
|
smoothing?: number;
|
|
}
|
|
|
|
function useAudioLevel(
|
|
stream: MediaStream | null,
|
|
options?: UseAudioLevelOptions
|
|
): number;
|
|
|
|
// components/volume-bar.tsx
|
|
interface VolumeBarProps {
|
|
volume: number; // 0-100
|
|
isMuted?: boolean;
|
|
className?: string;
|
|
}
|
|
```
|
|
|
|
## Testing Considerations
|
|
|
|
1. **Manual Testing:**
|
|
- Test with different microphone input levels
|
|
- Verify mute/unmute visual feedback
|
|
- Check performance (CPU usage should be minimal)
|
|
- Test on different browsers (Chrome, Firefox, Safari)
|
|
|
|
2. **Edge Cases:**
|
|
- No microphone access granted
|
|
- Microphone disconnected mid-session
|
|
- Multiple rapid mute/unmute toggles
|
|
- Browser tab backgrounded (check if AudioContext suspends)
|
|
|
|
3. **Visual Verification:**
|
|
- Bar should respond quickly to voice (< 100ms latency feel)
|
|
- Color transitions should be smooth
|
|
- Muted state should be clearly visible
|
|
|
|
## Browser Compatibility
|
|
|
|
**Web Audio API Support:**
|
|
- Chrome/Edge: Full support
|
|
- Firefox: Full support
|
|
- Safari: Full support (requires user gesture for AudioContext)
|
|
|
|
**Important:** Safari requires a user interaction (e.g., button click) to start AudioContext due to autoplay policies.
|
|
|
|
## Performance Optimization
|
|
|
|
1. **Throttle/Debounce:** Already handled by `requestAnimationFrame`
|
|
2. **Memory:** Reuse typed arrays instead of creating new ones
|
|
3. **Cleanup:** Always disconnect nodes and close AudioContext
|
|
4. **Conditional Rendering:** Only run analysis when stream is active
|
|
|
|
## Future Enhancements (Optional)
|
|
|
|
- Add peak level indicator (shows max volume reached)
|
|
- Add threshold indicators for voice activity detection
|
|
- Implement waveform visualization as alternative view
|
|
- Add accessibility features (ARIA labels, screen reader support)
|
|
- Smoothing controls for users to adjust responsiveness
|
|
- Save user preferences (mute state, visualization style)
|
|
|
|
## Implementation Order
|
|
|
|
1. ✅ Create `use-audio-level.ts` hook (core functionality)
|
|
2. ✅ Create `volume-bar.tsx` component (visual display)
|
|
3. ✅ Integrate into voice client component
|
|
4. ✅ Test with microphone input
|
|
5. ✅ Refine visual styling and animations
|
|
6. ✅ Handle edge cases and error states
|