@djangocfg/ui-nextjs 2.1.83 → 2.1.85
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/tools/AudioPlayer/README.md +60 -166
- package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +0 -35
- package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +0 -11
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +23 -21
- package/src/tools/AudioPlayer/components/index.ts +4 -8
- package/src/tools/AudioPlayer/context/index.ts +1 -8
- package/src/tools/AudioPlayer/hooks/index.ts +6 -13
- package/src/tools/AudioPlayer/index.ts +25 -89
- package/src/tools/AudioPlayer/types/index.ts +10 -18
- package/src/tools/index.ts +51 -56
- package/src/tools/AudioPlayer/@refactoring3/00-IMPLEMENTATION-ROADMAP.md +0 -1146
- package/src/tools/AudioPlayer/@refactoring3/01-WAVESURFER-STREAMING-ANALYSIS.md +0 -611
- package/src/tools/AudioPlayer/@refactoring3/02-MEDIA-VIEWER-ANALYSIS.md +0 -560
- package/src/tools/AudioPlayer/@refactoring3/03-HYBRID-ARCHITECTURE-PROPOSAL.md +0 -769
- package/src/tools/AudioPlayer/@refactoring3/04-CRACKLING-ISSUE-DIAGNOSIS.md +0 -373
- package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +0 -200
- package/src/tools/AudioPlayer/components/AudioPlayer.tsx +0 -236
- package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +0 -99
- package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +0 -278
- package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +0 -64
- package/src/tools/AudioPlayer/context/AudioProvider.tsx +0 -376
- package/src/tools/AudioPlayer/context/selectors.ts +0 -96
- package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +0 -110
- package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +0 -150
- package/src/tools/AudioPlayer/hooks/useAudioSource.ts +0 -155
- package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +0 -109
- package/src/tools/AudioPlayer/progressive/ProgressiveAudioPlayer.tsx +0 -303
- package/src/tools/AudioPlayer/progressive/WaveformCanvas.tsx +0 -381
- package/src/tools/AudioPlayer/progressive/index.ts +0 -40
- package/src/tools/AudioPlayer/progressive/peaks.ts +0 -234
- package/src/tools/AudioPlayer/progressive/types.ts +0 -179
- package/src/tools/AudioPlayer/progressive/useAudioElement.ts +0 -340
- package/src/tools/AudioPlayer/progressive/useProgressiveWaveform.ts +0 -267
- package/src/tools/AudioPlayer/types/audio.ts +0 -121
- package/src/tools/AudioPlayer/types/components.ts +0 -98
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
# Media Viewer Audio Analysis - AudioPlayer Usage in FileWorkspace
|
|
2
|
-
|
|
3
|
-
**Date:** 2025-12-30
|
|
4
|
-
**Analyzed Directory:** `cmdop/projects/solution/frontend/apps/web/app/_layouts/FileWorkspace/viewers/media`
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Table of Contents
|
|
9
|
-
|
|
10
|
-
1. [Overview](#overview)
|
|
11
|
-
2. [Component Architecture](#component-architecture)
|
|
12
|
-
3. [SimpleAudioPlayer vs ProgressiveAudioPlayer](#simpleaudioplayer-vs-progressiveaudioplayer)
|
|
13
|
-
4. [Root Cause of Crackling Issue](#root-cause-of-crackling-issue)
|
|
14
|
-
5. [Chunks/Streaming Handling](#chunksstreaming-handling)
|
|
15
|
-
6. [Audio Element Usage Differences](#audio-element-usage-differences)
|
|
16
|
-
7. [Code References](#code-references)
|
|
17
|
-
8. [Recommendations](#recommendations)
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Overview
|
|
22
|
-
|
|
23
|
-
The `AudioViewer.tsx` in FileWorkspace provides two distinct player modes:
|
|
24
|
-
|
|
25
|
-
1. **WaveSurfer Mode** (default): Uses `SimpleAudioPlayer` with WaveSurfer.js for waveform visualization
|
|
26
|
-
2. **Progressive Mode**: Uses `ProgressiveAudioPlayer` with native HTML5 audio
|
|
27
|
-
|
|
28
|
-
Users can toggle between modes via a switch in the UI, with preference persisted in localStorage.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Component Architecture
|
|
33
|
-
|
|
34
|
-
### AudioViewer Component Structure
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
AudioViewer (AudioViewer.tsx)
|
|
38
|
-
|
|
|
39
|
-
+-- Mode Toggle (localStorage: 'audio-viewer-progressive-mode')
|
|
40
|
-
|
|
|
41
|
-
+-- Progressive Mode?
|
|
42
|
-
| |
|
|
43
|
-
| YES --> ProgressiveAudioPlayer
|
|
44
|
-
| | |
|
|
45
|
-
| | +-- useAudioElement (native <audio>)
|
|
46
|
-
| | +-- useProgressiveWaveform (chunks + canvas)
|
|
47
|
-
| | +-- WaveformCanvas (custom rendering)
|
|
48
|
-
| |
|
|
49
|
-
| NO --> Has Metadata?
|
|
50
|
-
| |
|
|
51
|
-
| YES --> VisualizationProvider
|
|
52
|
-
| | +-- AudioViewerWithMetadata
|
|
53
|
-
| | +-- AudioProvider (WaveSurfer context)
|
|
54
|
-
| | +-- AudioReactiveCover
|
|
55
|
-
| | +-- AudioPlayer (WaveSurfer controls)
|
|
56
|
-
| |
|
|
57
|
-
| NO --> SimpleAudioPlayer
|
|
58
|
-
| +-- VisualizationProvider
|
|
59
|
-
| +-- AudioProvider (WaveSurfer)
|
|
60
|
-
| +-- AudioPlayer
|
|
61
|
-
| +-- AudioReactiveCover (optional)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Key File Locations
|
|
65
|
-
|
|
66
|
-
| File | Path |
|
|
67
|
-
|------|------|
|
|
68
|
-
| AudioViewer | `cmdop/.../viewers/media/AudioViewer.tsx` |
|
|
69
|
-
| SimpleAudioPlayer | `djangocfg/.../AudioPlayer/components/SimpleAudioPlayer.tsx` |
|
|
70
|
-
| AudioPlayer | `djangocfg/.../AudioPlayer/components/AudioPlayer.tsx` |
|
|
71
|
-
| AudioProvider | `djangocfg/.../AudioPlayer/context/AudioProvider.tsx` |
|
|
72
|
-
| ProgressiveAudioPlayer | `djangocfg/.../AudioPlayer/progressive/ProgressiveAudioPlayer.tsx` |
|
|
73
|
-
| useAudioElement | `djangocfg/.../AudioPlayer/progressive/useAudioElement.ts` |
|
|
74
|
-
| useProgressiveWaveform | `djangocfg/.../AudioPlayer/progressive/useProgressiveWaveform.ts` |
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## SimpleAudioPlayer vs ProgressiveAudioPlayer
|
|
79
|
-
|
|
80
|
-
### SimpleAudioPlayer (WaveSurfer Mode)
|
|
81
|
-
|
|
82
|
-
**Architecture:**
|
|
83
|
-
```typescript
|
|
84
|
-
// SimpleAudioPlayer.tsx (lines 132-286)
|
|
85
|
-
function SimpleAudioPlayer(props) {
|
|
86
|
-
return (
|
|
87
|
-
<VisualizationProvider>
|
|
88
|
-
<SimpleAudioPlayerContent {...props} />
|
|
89
|
-
</VisualizationProvider>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function SimpleAudioPlayerContent({ src, prefetch = true, ... }) {
|
|
94
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<AudioProvider
|
|
98
|
-
source={{ uri: src, prefetch }} // <-- Key: prefetch=true by default
|
|
99
|
-
containerRef={containerRef}
|
|
100
|
-
>
|
|
101
|
-
<AudioPlayer ref={containerRef} ... /> // WaveSurfer renders here
|
|
102
|
-
</AudioProvider>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
**Key Characteristics:**
|
|
108
|
-
1. Uses `@wavesurfer/react` hook internally via `AudioProvider`
|
|
109
|
-
2. **Requires full file load** for waveform generation
|
|
110
|
-
3. Default `prefetch=true` fetches entire file as blob before loading
|
|
111
|
-
4. WaveSurfer creates its own `<audio>` element internally
|
|
112
|
-
5. Supports reactive audio effects (AudioReactiveCover)
|
|
113
|
-
|
|
114
|
-
**AudioProvider internals (lines 53-371):**
|
|
115
|
-
```typescript
|
|
116
|
-
export function AudioProvider({ source, ... }) {
|
|
117
|
-
// Uses useAudioSource hook for prefetching
|
|
118
|
-
const { url: resolvedUrl, isLoading: isPrefetching } = useAudioSource(source);
|
|
119
|
-
|
|
120
|
-
// WaveSurfer hook
|
|
121
|
-
const { wavesurfer, isReady, isPlaying, currentTime } = useWavesurfer(options);
|
|
122
|
-
|
|
123
|
-
// Shared Web Audio for reactive effects
|
|
124
|
-
const audioElement = wavesurfer?.getMediaElement() ?? null;
|
|
125
|
-
const sharedAudio = useSharedWebAudio(audioElement);
|
|
126
|
-
const audioLevels = useAudioAnalysis(sharedAudio, isPlaying);
|
|
127
|
-
// ...
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### ProgressiveAudioPlayer (Progressive Mode)
|
|
132
|
-
|
|
133
|
-
**Architecture:**
|
|
134
|
-
```typescript
|
|
135
|
-
// ProgressiveAudioPlayer.tsx (lines 43-293)
|
|
136
|
-
export function ProgressiveAudioPlayer({ src, ... }) {
|
|
137
|
-
const audioRef = useRef<HTMLAudioElement>(null);
|
|
138
|
-
|
|
139
|
-
// Native HTML5 audio control
|
|
140
|
-
const audio = useAudioElement({ src, autoPlay, ... });
|
|
141
|
-
|
|
142
|
-
// Progressive waveform (separate from playback)
|
|
143
|
-
const waveform = useProgressiveWaveform({ url: src, duration: audio.duration });
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<div>
|
|
147
|
-
{/* Hidden native audio element */}
|
|
148
|
-
<audio
|
|
149
|
-
ref={(el) => { audioRef.current = el; audio.audioRef.current = el; }}
|
|
150
|
-
preload="metadata"
|
|
151
|
-
crossOrigin="anonymous"
|
|
152
|
-
/>
|
|
153
|
-
|
|
154
|
-
{/* Custom canvas waveform */}
|
|
155
|
-
<WaveformCanvas
|
|
156
|
-
peaks={waveform.peaks}
|
|
157
|
-
currentTime={audio.currentTime}
|
|
158
|
-
duration={audio.duration}
|
|
159
|
-
buffered={audio.buffered}
|
|
160
|
-
onSeek={handleSeek}
|
|
161
|
-
/>
|
|
162
|
-
|
|
163
|
-
{/* Controls */}
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
**Key Characteristics:**
|
|
170
|
-
1. Uses **native `<audio>` element** for playback
|
|
171
|
-
2. Browser handles Range requests automatically
|
|
172
|
-
3. Waveform loads **progressively** in chunks (512KB default)
|
|
173
|
-
4. Seek works on any position (browser fetches needed data)
|
|
174
|
-
5. **No reactive effects** (no Web Audio context by default)
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Root Cause of Crackling Issue
|
|
179
|
-
|
|
180
|
-
### The Problem
|
|
181
|
-
|
|
182
|
-
The "crackling" issue with WaveSurfer mode (`AudioPlayer`/`SimpleAudioPlayer`) has **multiple potential causes**:
|
|
183
|
-
|
|
184
|
-
### Cause 1: Web Audio Context Routing (Most Likely)
|
|
185
|
-
|
|
186
|
-
In `useSharedWebAudio.ts` (lines 39-47):
|
|
187
|
-
```typescript
|
|
188
|
-
if (connectedElementRef.current !== audioElement) {
|
|
189
|
-
// Disconnect old source
|
|
190
|
-
if (sourceRef.current) {
|
|
191
|
-
try { sourceRef.current.disconnect(); } catch { /* ignore */ }
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Create new source - this routes audio through Web Audio API
|
|
195
|
-
sourceRef.current = audioContext.createMediaElementSource(audioElement);
|
|
196
|
-
sourceRef.current.connect(audioContext.destination);
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
**Issue:** When `createMediaElementSource()` is called:
|
|
201
|
-
1. Audio is routed through Web Audio API instead of direct playback
|
|
202
|
-
2. This adds latency and can cause timing issues
|
|
203
|
-
3. If AudioContext is not in "running" state, audio may glitch
|
|
204
|
-
4. Multiple connections (analyser nodes) can cause audio artifacts
|
|
205
|
-
|
|
206
|
-
In `useAudioAnalysis.ts` (lines 31-46):
|
|
207
|
-
```typescript
|
|
208
|
-
useEffect(() => {
|
|
209
|
-
const analyser = sharedAudio.createAnalyser({ fftSize: 256, smoothing: 0.85 });
|
|
210
|
-
if (analyser) {
|
|
211
|
-
analyserRef.current = analyser;
|
|
212
|
-
// This creates: source -> analyser -> destination chain
|
|
213
|
-
}
|
|
214
|
-
}, [sharedAudio.sourceNode, ...]);
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Cause 2: WaveSurfer Internal Buffering
|
|
218
|
-
|
|
219
|
-
WaveSurfer.js uses Web Audio API internally for decoding and playback. When combined with:
|
|
220
|
-
- HTTP Range streaming that doesn't provide complete file
|
|
221
|
-
- Additional Web Audio routing for visualizations
|
|
222
|
-
- Multiple render cycles
|
|
223
|
-
|
|
224
|
-
This can cause:
|
|
225
|
-
- Buffer underruns (crackling/popping)
|
|
226
|
-
- Timing inconsistencies
|
|
227
|
-
- Decode errors that manifest as audio artifacts
|
|
228
|
-
|
|
229
|
-
### Cause 3: React Strict Mode Double-Mounting
|
|
230
|
-
|
|
231
|
-
In development, React Strict Mode can cause:
|
|
232
|
-
1. Double initialization of AudioContext
|
|
233
|
-
2. Race conditions in audio element setup
|
|
234
|
-
3. Multiple event listener attachments
|
|
235
|
-
|
|
236
|
-
### Why ProgressiveAudioPlayer is Smooth
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
// useAudioElement.ts - Direct native audio
|
|
240
|
-
<audio
|
|
241
|
-
ref={audioRef}
|
|
242
|
-
preload="metadata"
|
|
243
|
-
crossOrigin="anonymous"
|
|
244
|
-
/>
|
|
245
|
-
// No Web Audio routing, no extra processing
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
The `ProgressiveAudioPlayer`:
|
|
249
|
-
1. **No Web Audio context** for playback (just for waveform generation)
|
|
250
|
-
2. **No `MediaElementSource`** routing
|
|
251
|
-
3. Browser handles all audio decoding natively
|
|
252
|
-
4. Direct hardware audio output
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## Chunks/Streaming Handling
|
|
257
|
-
|
|
258
|
-
### WaveSurfer Mode (SimpleAudioPlayer)
|
|
259
|
-
|
|
260
|
-
**useAudioSource.ts** handles prefetching (lines 55-141):
|
|
261
|
-
```typescript
|
|
262
|
-
const fetchAsBlob = async () => {
|
|
263
|
-
const response = await fetch(source.uri, {
|
|
264
|
-
headers: { 'Range': 'bytes=0-' }, // Request full file
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Stream chunks for progress tracking
|
|
268
|
-
const reader = response.body.getReader();
|
|
269
|
-
const chunks: ArrayBuffer[] = [];
|
|
270
|
-
|
|
271
|
-
while (true) {
|
|
272
|
-
const { done, value } = await reader.read();
|
|
273
|
-
if (done) break;
|
|
274
|
-
chunks.push(value.buffer);
|
|
275
|
-
// Update progress...
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Combine all chunks into single blob
|
|
279
|
-
const blob = new Blob(chunks, { type: 'audio/mpeg' });
|
|
280
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
281
|
-
setUrl(blobUrl); // WaveSurfer uses this blob URL
|
|
282
|
-
};
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
**Problem:** WaveSurfer needs the **complete file** before it can:
|
|
286
|
-
- Generate accurate waveform
|
|
287
|
-
- Enable seeking to any position
|
|
288
|
-
- Calculate correct duration
|
|
289
|
-
|
|
290
|
-
If prefetch fails or is disabled, WaveSurfer only has partial data.
|
|
291
|
-
|
|
292
|
-
### Progressive Mode (ProgressiveAudioPlayer)
|
|
293
|
-
|
|
294
|
-
**useProgressiveWaveform.ts** (lines 138-227):
|
|
295
|
-
```typescript
|
|
296
|
-
const loadWaveform = useCallback(async () => {
|
|
297
|
-
// HEAD request for file size
|
|
298
|
-
const headResponse = await fetch(url, { method: 'HEAD' });
|
|
299
|
-
const totalSize = parseInt(contentLength, 10);
|
|
300
|
-
|
|
301
|
-
// Fetch in chunks (512KB default)
|
|
302
|
-
let offset = 0;
|
|
303
|
-
while (offset < totalSize) {
|
|
304
|
-
const response = await fetch(url, {
|
|
305
|
-
headers: { Range: `bytes=${offset}-${end}` },
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const chunk = await response.arrayBuffer();
|
|
309
|
-
accumulatedDataRef.current.push(chunk);
|
|
310
|
-
|
|
311
|
-
// Try to decode and extract peaks periodically
|
|
312
|
-
await decodeAccumulated();
|
|
313
|
-
|
|
314
|
-
offset += chunkSize;
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
**For playback**, native `<audio>` handles streaming independently:
|
|
320
|
-
```typescript
|
|
321
|
-
// useAudioElement.ts (lines 303-314)
|
|
322
|
-
useEffect(() => {
|
|
323
|
-
audio.src = src; // Browser handles Range requests automatically
|
|
324
|
-
audio.load();
|
|
325
|
-
}, [src]);
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
---
|
|
329
|
-
|
|
330
|
-
## Audio Element Usage Differences
|
|
331
|
-
|
|
332
|
-
### WaveSurfer Mode
|
|
333
|
-
|
|
334
|
-
| Aspect | Implementation |
|
|
335
|
-
|--------|----------------|
|
|
336
|
-
| Audio Element | Created by WaveSurfer internally |
|
|
337
|
-
| Access | `wavesurfer.getMediaElement()` |
|
|
338
|
-
| Routing | Source -> Web Audio -> Destination |
|
|
339
|
-
| Control | Via WaveSurfer API (`wavesurfer.play()`, etc.) |
|
|
340
|
-
| Buffering | WaveSurfer manages internally |
|
|
341
|
-
|
|
342
|
-
```typescript
|
|
343
|
-
// AudioProvider.tsx (lines 202-205)
|
|
344
|
-
const audioElement = useMemo(() => {
|
|
345
|
-
return wavesurfer?.getMediaElement() ?? null;
|
|
346
|
-
}, [wavesurfer]);
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Progressive Mode
|
|
350
|
-
|
|
351
|
-
| Aspect | Implementation |
|
|
352
|
-
|--------|----------------|
|
|
353
|
-
| Audio Element | Explicit `<audio>` in JSX |
|
|
354
|
-
| Access | Direct ref |
|
|
355
|
-
| Routing | Direct to hardware (no Web Audio for playback) |
|
|
356
|
-
| Control | Native DOM API (`audio.play()`, etc.) |
|
|
357
|
-
| Buffering | Browser handles via `buffered` property |
|
|
358
|
-
|
|
359
|
-
```typescript
|
|
360
|
-
// ProgressiveAudioPlayer.tsx (lines 111-118)
|
|
361
|
-
<audio
|
|
362
|
-
ref={(el) => {
|
|
363
|
-
(audioRef as any).current = el;
|
|
364
|
-
(audio.audioRef as any).current = el;
|
|
365
|
-
}}
|
|
366
|
-
preload="metadata"
|
|
367
|
-
crossOrigin="anonymous"
|
|
368
|
-
/>
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
### Event Handling Comparison
|
|
372
|
-
|
|
373
|
-
**WaveSurfer Mode (via AudioProvider):**
|
|
374
|
-
```typescript
|
|
375
|
-
// Uses WaveSurfer events
|
|
376
|
-
wavesurfer.on('seeking', handleSeeking);
|
|
377
|
-
wavesurfer.on('error', handleError);
|
|
378
|
-
wavesurfer.on('loading', handleLoading);
|
|
379
|
-
wavesurfer.on('finish', handleFinish);
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
**Progressive Mode (via useAudioElement):**
|
|
383
|
-
```typescript
|
|
384
|
-
// Uses native DOM events (lines 259-272)
|
|
385
|
-
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
|
|
386
|
-
audio.addEventListener('canplay', handleCanPlay);
|
|
387
|
-
audio.addEventListener('play', handlePlay);
|
|
388
|
-
audio.addEventListener('pause', handlePause);
|
|
389
|
-
audio.addEventListener('timeupdate', handleTimeUpdate);
|
|
390
|
-
audio.addEventListener('progress', handleProgress);
|
|
391
|
-
audio.addEventListener('seeking', handleSeeking);
|
|
392
|
-
audio.addEventListener('seeked', handleSeeked);
|
|
393
|
-
audio.addEventListener('waiting', handleWaiting);
|
|
394
|
-
audio.addEventListener('stalled', handleStalled);
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Code References
|
|
400
|
-
|
|
401
|
-
### AudioViewer Mode Selection (AudioViewer.tsx)
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
// Lines 69-86
|
|
405
|
-
const [useProgressivePlayer, setUseProgressivePlayer] = useLocalStorage(
|
|
406
|
-
'audio-viewer-progressive-mode',
|
|
407
|
-
false
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
const handleModeChange = (checked: boolean) => {
|
|
411
|
-
// Stop all audio before switching
|
|
412
|
-
document.querySelectorAll('audio').forEach(audio => {
|
|
413
|
-
audio.pause();
|
|
414
|
-
audio.currentTime = 0;
|
|
415
|
-
});
|
|
416
|
-
setPlayerKey(prev => prev + 1); // Force remount
|
|
417
|
-
setUseProgressivePlayer(checked);
|
|
418
|
-
};
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### Progressive Player Rendering (AudioViewer.tsx)
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
// Lines 183-206
|
|
425
|
-
if (useProgressivePlayer) {
|
|
426
|
-
return (
|
|
427
|
-
<div key={`progressive-${playerKey}`}>
|
|
428
|
-
{PlayerModeToggle}
|
|
429
|
-
<ProgressiveAudioPlayer
|
|
430
|
-
src={src}
|
|
431
|
-
title={metadata?.title || file.name}
|
|
432
|
-
artist={metadata?.artist}
|
|
433
|
-
showWaveform
|
|
434
|
-
showControls
|
|
435
|
-
showTimer
|
|
436
|
-
showVolume
|
|
437
|
-
showLoop
|
|
438
|
-
/>
|
|
439
|
-
</div>
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### WaveSurfer Player Rendering (AudioViewer.tsx)
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
// Lines 208-237 (simplified)
|
|
448
|
-
if (!metadata) {
|
|
449
|
-
return (
|
|
450
|
-
<SimpleAudioPlayer
|
|
451
|
-
src={src}
|
|
452
|
-
title={file.name}
|
|
453
|
-
showWaveform
|
|
454
|
-
reactiveCover
|
|
455
|
-
/>
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// With metadata
|
|
460
|
-
return (
|
|
461
|
-
<VisualizationProvider>
|
|
462
|
-
<AudioViewerWithMetadata src={src} file={file} metadata={metadata} />
|
|
463
|
-
</VisualizationProvider>
|
|
464
|
-
);
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### WaveSurfer Web Audio Connection (useSharedWebAudio.ts)
|
|
468
|
-
|
|
469
|
-
```typescript
|
|
470
|
-
// Lines 44-47 - The problematic routing
|
|
471
|
-
sourceRef.current = audioContext.createMediaElementSource(audioElement);
|
|
472
|
-
sourceRef.current.connect(audioContext.destination);
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### Native Audio Element Setup (useAudioElement.ts)
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
// Lines 302-314
|
|
479
|
-
useEffect(() => {
|
|
480
|
-
const audio = audioRef.current;
|
|
481
|
-
if (!audio) return;
|
|
482
|
-
|
|
483
|
-
audioDebug.load(src);
|
|
484
|
-
setIsReady(false);
|
|
485
|
-
setError(null);
|
|
486
|
-
setCurrentTime(0);
|
|
487
|
-
setDuration(0);
|
|
488
|
-
audio.src = src;
|
|
489
|
-
audio.load();
|
|
490
|
-
}, [src]);
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
---
|
|
494
|
-
|
|
495
|
-
## Recommendations
|
|
496
|
-
|
|
497
|
-
### To Fix Crackling in WaveSurfer Mode
|
|
498
|
-
|
|
499
|
-
1. **Lazy Web Audio Initialization**
|
|
500
|
-
- Only create `MediaElementSource` when reactive effects are actually needed
|
|
501
|
-
- Add a prop to disable Web Audio routing entirely
|
|
502
|
-
|
|
503
|
-
2. **AudioContext State Management**
|
|
504
|
-
```typescript
|
|
505
|
-
// Ensure AudioContext is running before creating source
|
|
506
|
-
if (audioContext.state === 'suspended') {
|
|
507
|
-
await audioContext.resume();
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
3. **Single Analyser Node**
|
|
512
|
-
- Currently multiple components might create separate analysers
|
|
513
|
-
- Consolidate to a single shared analyser
|
|
514
|
-
|
|
515
|
-
4. **Disable Reactive Effects by Default**
|
|
516
|
-
- Make `reactiveCover={false}` the default
|
|
517
|
-
- Only enable when user explicitly requests
|
|
518
|
-
|
|
519
|
-
### To Improve Progressive Mode
|
|
520
|
-
|
|
521
|
-
1. **Add Reactive Effects Support (Optional)**
|
|
522
|
-
- Create separate Web Audio routing only for visualization
|
|
523
|
-
- Keep playback through native audio
|
|
524
|
-
|
|
525
|
-
2. **Pre-generate Waveform Peaks on Backend**
|
|
526
|
-
- Avoid client-side decoding overhead
|
|
527
|
-
- Cache peaks alongside audio files
|
|
528
|
-
|
|
529
|
-
### Configuration Options
|
|
530
|
-
|
|
531
|
-
Consider adding these options to `AudioViewer`:
|
|
532
|
-
|
|
533
|
-
```typescript
|
|
534
|
-
interface AudioViewerOptions {
|
|
535
|
-
// Disable all Web Audio processing
|
|
536
|
-
disableWebAudio?: boolean;
|
|
537
|
-
|
|
538
|
-
// Disable reactive effects specifically
|
|
539
|
-
disableReactiveEffects?: boolean;
|
|
540
|
-
|
|
541
|
-
// Force progressive mode for streaming sources
|
|
542
|
-
forceProgressiveForStreaming?: boolean;
|
|
543
|
-
}
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
## Summary
|
|
549
|
-
|
|
550
|
-
| Feature | SimpleAudioPlayer (WaveSurfer) | ProgressiveAudioPlayer |
|
|
551
|
-
|---------|------------------------------|----------------------|
|
|
552
|
-
| Audio Routing | Web Audio API | Native Browser |
|
|
553
|
-
| Waveform | Pre-computed (needs full file) | Progressive (chunks) |
|
|
554
|
-
| Seek | Limited by buffered data | Any position |
|
|
555
|
-
| Reactive Effects | Yes | No |
|
|
556
|
-
| Crackling Risk | High (Web Audio routing) | Low (direct playback) |
|
|
557
|
-
| Streaming | Requires prefetch | Native support |
|
|
558
|
-
| Memory Usage | Higher (full file in blob) | Lower (browser cache) |
|
|
559
|
-
|
|
560
|
-
**Conclusion:** The crackling issue in WaveSurfer mode stems primarily from the Web Audio API routing required for reactive visualizations. The `ProgressiveAudioPlayer` avoids this by using native HTML5 audio playback, resulting in smoother audio output at the cost of reactive visual effects.
|