@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.
Files changed (36) hide show
  1. package/package.json +4 -4
  2. package/src/tools/AudioPlayer/README.md +60 -166
  3. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +0 -35
  4. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +0 -11
  5. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +23 -21
  6. package/src/tools/AudioPlayer/components/index.ts +4 -8
  7. package/src/tools/AudioPlayer/context/index.ts +1 -8
  8. package/src/tools/AudioPlayer/hooks/index.ts +6 -13
  9. package/src/tools/AudioPlayer/index.ts +25 -89
  10. package/src/tools/AudioPlayer/types/index.ts +10 -18
  11. package/src/tools/index.ts +51 -56
  12. package/src/tools/AudioPlayer/@refactoring3/00-IMPLEMENTATION-ROADMAP.md +0 -1146
  13. package/src/tools/AudioPlayer/@refactoring3/01-WAVESURFER-STREAMING-ANALYSIS.md +0 -611
  14. package/src/tools/AudioPlayer/@refactoring3/02-MEDIA-VIEWER-ANALYSIS.md +0 -560
  15. package/src/tools/AudioPlayer/@refactoring3/03-HYBRID-ARCHITECTURE-PROPOSAL.md +0 -769
  16. package/src/tools/AudioPlayer/@refactoring3/04-CRACKLING-ISSUE-DIAGNOSIS.md +0 -373
  17. package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +0 -200
  18. package/src/tools/AudioPlayer/components/AudioPlayer.tsx +0 -236
  19. package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +0 -99
  20. package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +0 -278
  21. package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +0 -64
  22. package/src/tools/AudioPlayer/context/AudioProvider.tsx +0 -376
  23. package/src/tools/AudioPlayer/context/selectors.ts +0 -96
  24. package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +0 -110
  25. package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +0 -150
  26. package/src/tools/AudioPlayer/hooks/useAudioSource.ts +0 -155
  27. package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +0 -109
  28. package/src/tools/AudioPlayer/progressive/ProgressiveAudioPlayer.tsx +0 -303
  29. package/src/tools/AudioPlayer/progressive/WaveformCanvas.tsx +0 -381
  30. package/src/tools/AudioPlayer/progressive/index.ts +0 -40
  31. package/src/tools/AudioPlayer/progressive/peaks.ts +0 -234
  32. package/src/tools/AudioPlayer/progressive/types.ts +0 -179
  33. package/src/tools/AudioPlayer/progressive/useAudioElement.ts +0 -340
  34. package/src/tools/AudioPlayer/progressive/useProgressiveWaveform.ts +0 -267
  35. package/src/tools/AudioPlayer/types/audio.ts +0 -121
  36. 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.