@djangocfg/ui-nextjs 2.1.82 → 2.1.84

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 (43) hide show
  1. package/package.json +4 -4
  2. package/src/tools/AudioPlayer/README.md +108 -242
  3. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  4. package/src/tools/AudioPlayer/components/{SimpleAudioPlayer.tsx → HybridSimplePlayer.tsx} +61 -69
  5. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  6. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +5 -5
  7. package/src/tools/AudioPlayer/components/index.ts +7 -6
  8. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +121 -0
  9. package/src/tools/AudioPlayer/context/index.ts +11 -6
  10. package/src/tools/AudioPlayer/hooks/index.ts +14 -10
  11. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  12. package/src/tools/AudioPlayer/hooks/{useAudioAnalysis.ts → useHybridAudioAnalysis.ts} +23 -38
  13. package/src/tools/AudioPlayer/index.ts +37 -70
  14. package/src/tools/AudioPlayer/types/index.ts +10 -18
  15. package/src/tools/index.ts +60 -43
  16. package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +0 -148
  17. package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +0 -301
  18. package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +0 -281
  19. package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +0 -328
  20. package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +0 -251
  21. package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +0 -427
  22. package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +0 -193
  23. package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +0 -146
  24. package/src/tools/AudioPlayer/@refactoring2/ISSUE_ANALYSIS.md +0 -187
  25. package/src/tools/AudioPlayer/@refactoring2/PLAN.md +0 -372
  26. package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +0 -200
  27. package/src/tools/AudioPlayer/components/AudioPlayer.tsx +0 -231
  28. package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +0 -99
  29. package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +0 -64
  30. package/src/tools/AudioPlayer/context/AudioProvider.tsx +0 -371
  31. package/src/tools/AudioPlayer/context/selectors.ts +0 -96
  32. package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +0 -150
  33. package/src/tools/AudioPlayer/hooks/useAudioSource.ts +0 -155
  34. package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +0 -106
  35. package/src/tools/AudioPlayer/progressive/ProgressiveAudioPlayer.tsx +0 -295
  36. package/src/tools/AudioPlayer/progressive/WaveformCanvas.tsx +0 -381
  37. package/src/tools/AudioPlayer/progressive/index.ts +0 -40
  38. package/src/tools/AudioPlayer/progressive/peaks.ts +0 -234
  39. package/src/tools/AudioPlayer/progressive/types.ts +0 -179
  40. package/src/tools/AudioPlayer/progressive/useAudioElement.ts +0 -340
  41. package/src/tools/AudioPlayer/progressive/useProgressiveWaveform.ts +0 -267
  42. package/src/tools/AudioPlayer/types/audio.ts +0 -121
  43. package/src/tools/AudioPlayer/types/components.ts +0 -98
@@ -1,267 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * useProgressiveWaveform - Hook for progressive audio waveform loading
5
- *
6
- * Fetches audio in chunks, decodes progressively, and extracts peaks.
7
- * Provides loading state and partial waveform data as it loads.
8
- */
9
-
10
- import { useState, useEffect, useRef, useCallback } from 'react';
11
- import { extractPeaks, mergePeaks } from './peaks';
12
- import type { LoadedRange, DecoderOptions } from './types';
13
-
14
- // =============================================================================
15
- // CONSTANTS
16
- // =============================================================================
17
-
18
- const DEFAULT_CHUNK_SIZE = 512 * 1024; // 512KB
19
- const DEFAULT_PEAKS_PER_SECOND = 50;
20
- const MAX_PARALLEL_FETCHES = 2;
21
-
22
- // =============================================================================
23
- // TYPES
24
- // =============================================================================
25
-
26
- interface UseProgressiveWaveformOptions extends DecoderOptions {
27
- /** Audio source URL */
28
- url: string;
29
- /** Known duration (from metadata) - improves initial render */
30
- duration?: number;
31
- /** Enabled */
32
- enabled?: boolean;
33
- }
34
-
35
- interface UseProgressiveWaveformReturn {
36
- /** Accumulated peaks (0-1 normalized) */
37
- peaks: number[];
38
- /** Loading progress (0-100) */
39
- loadedPercent: number;
40
- /** Is currently loading */
41
- isLoading: boolean;
42
- /** Is complete */
43
- isComplete: boolean;
44
- /** Error if any */
45
- error: Error | null;
46
- /** Loaded ranges (for visual indication) */
47
- loadedRanges: LoadedRange[];
48
- /** Detected duration */
49
- detectedDuration: number;
50
- /** Retry loading */
51
- retry: () => void;
52
- }
53
-
54
- // =============================================================================
55
- // AUDIO CONTEXT SINGLETON
56
- // =============================================================================
57
-
58
- let audioContextInstance: AudioContext | null = null;
59
-
60
- function getAudioContext(): AudioContext {
61
- if (!audioContextInstance) {
62
- audioContextInstance = new (window.AudioContext || (window as any).webkitAudioContext)();
63
- }
64
- return audioContextInstance;
65
- }
66
-
67
- // =============================================================================
68
- // HOOK
69
- // =============================================================================
70
-
71
- export function useProgressiveWaveform(
72
- options: UseProgressiveWaveformOptions
73
- ): UseProgressiveWaveformReturn {
74
- const {
75
- url,
76
- duration: knownDuration,
77
- enabled = true,
78
- chunkSize = DEFAULT_CHUNK_SIZE,
79
- peaksPerSecond = DEFAULT_PEAKS_PER_SECOND,
80
- parallelFetches = MAX_PARALLEL_FETCHES,
81
- } = options;
82
-
83
- // State
84
- const [peaks, setPeaks] = useState<number[]>([]);
85
- const [loadedPercent, setLoadedPercent] = useState(0);
86
- const [isLoading, setIsLoading] = useState(false);
87
- const [isComplete, setIsComplete] = useState(false);
88
- const [error, setError] = useState<Error | null>(null);
89
- const [loadedRanges, setLoadedRanges] = useState<LoadedRange[]>([]);
90
- const [detectedDuration, setDetectedDuration] = useState(knownDuration || 0);
91
-
92
- // Refs
93
- const abortControllerRef = useRef<AbortController | null>(null);
94
- const accumulatedDataRef = useRef<ArrayBuffer[]>([]);
95
- const totalSizeRef = useRef(0);
96
- const loadedSizeRef = useRef(0);
97
- const retryCountRef = useRef(0);
98
-
99
- // ==========================================================================
100
- // DECODE ACCUMULATED DATA
101
- // ==========================================================================
102
-
103
- const decodeAccumulated = useCallback(async () => {
104
- const chunks = accumulatedDataRef.current;
105
- if (chunks.length === 0) return;
106
-
107
- try {
108
- // Combine all chunks
109
- const totalLength = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
110
- const combined = new Uint8Array(totalLength);
111
- let offset = 0;
112
- for (const chunk of chunks) {
113
- combined.set(new Uint8Array(chunk), offset);
114
- offset += chunk.byteLength;
115
- }
116
-
117
- // Decode
118
- const audioContext = getAudioContext();
119
- const audioBuffer = await audioContext.decodeAudioData(combined.buffer.slice(0));
120
-
121
- // Extract peaks
122
- const targetPeaks = Math.ceil(audioBuffer.duration * peaksPerSecond);
123
- const newPeaks = extractPeaks(audioBuffer, { length: targetPeaks });
124
-
125
- setPeaks(newPeaks);
126
- setDetectedDuration(audioBuffer.duration);
127
- } catch (err) {
128
- // Decoding failed - might need more data
129
- // This is expected for partial MP3 data
130
- console.debug('Decode attempt failed, waiting for more data');
131
- }
132
- }, [peaksPerSecond]);
133
-
134
- // ==========================================================================
135
- // FETCH LOGIC
136
- // ==========================================================================
137
-
138
- const loadWaveform = useCallback(async () => {
139
- if (!url || !enabled) return;
140
-
141
- // Abort previous
142
- abortControllerRef.current?.abort();
143
- abortControllerRef.current = new AbortController();
144
- const { signal } = abortControllerRef.current;
145
-
146
- // Reset state
147
- setIsLoading(true);
148
- setIsComplete(false);
149
- setError(null);
150
- setPeaks([]);
151
- setLoadedRanges([]);
152
- setLoadedPercent(0);
153
- accumulatedDataRef.current = [];
154
- loadedSizeRef.current = 0;
155
-
156
- try {
157
- // Get file size with HEAD request
158
- const headResponse = await fetch(url, { method: 'HEAD', signal });
159
- const contentLength = headResponse.headers.get('content-length');
160
- const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
161
- totalSizeRef.current = totalSize;
162
-
163
- if (totalSize === 0) {
164
- // Fallback: fetch entire file
165
- const response = await fetch(url, { signal });
166
- const data = await response.arrayBuffer();
167
- accumulatedDataRef.current = [data];
168
- await decodeAccumulated();
169
- setIsComplete(true);
170
- setLoadedPercent(100);
171
- setIsLoading(false);
172
- return;
173
- }
174
-
175
- // Fetch in chunks
176
- let offset = 0;
177
- const ranges: LoadedRange[] = [];
178
-
179
- while (offset < totalSize) {
180
- if (signal.aborted) return;
181
-
182
- const end = Math.min(offset + chunkSize - 1, totalSize - 1);
183
-
184
- const response = await fetch(url, {
185
- headers: { Range: `bytes=${offset}-${end}` },
186
- signal,
187
- });
188
-
189
- if (!response.ok && response.status !== 206) {
190
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
191
- }
192
-
193
- const chunk = await response.arrayBuffer();
194
- accumulatedDataRef.current.push(chunk);
195
- loadedSizeRef.current += chunk.byteLength;
196
-
197
- // Update progress
198
- const progress = (loadedSizeRef.current / totalSize) * 100;
199
- setLoadedPercent(progress);
200
-
201
- // Update ranges
202
- const rangeStart = offset / totalSize;
203
- const rangeEnd = (offset + chunk.byteLength) / totalSize;
204
- ranges.push({ start: rangeStart, end: rangeEnd });
205
- setLoadedRanges([...ranges]);
206
-
207
- // Try to decode periodically
208
- if (accumulatedDataRef.current.length % 2 === 0 || offset + chunkSize >= totalSize) {
209
- await decodeAccumulated();
210
- }
211
-
212
- offset += chunkSize;
213
- }
214
-
215
- // Final decode
216
- await decodeAccumulated();
217
- setIsComplete(true);
218
- } catch (err) {
219
- if ((err as Error).name === 'AbortError') return;
220
-
221
- const error = err instanceof Error ? err : new Error('Loading failed');
222
- setError(error);
223
- console.error('Waveform loading error:', error);
224
- } finally {
225
- setIsLoading(false);
226
- }
227
- }, [url, enabled, chunkSize, decodeAccumulated]);
228
-
229
- // ==========================================================================
230
- // RETRY
231
- // ==========================================================================
232
-
233
- const retry = useCallback(() => {
234
- retryCountRef.current += 1;
235
- loadWaveform();
236
- }, [loadWaveform]);
237
-
238
- // ==========================================================================
239
- // EFFECTS
240
- // ==========================================================================
241
-
242
- useEffect(() => {
243
- loadWaveform();
244
-
245
- return () => {
246
- abortControllerRef.current?.abort();
247
- };
248
- }, [loadWaveform]);
249
-
250
- // Update duration from prop
251
- useEffect(() => {
252
- if (knownDuration && knownDuration > 0 && detectedDuration === 0) {
253
- setDetectedDuration(knownDuration);
254
- }
255
- }, [knownDuration, detectedDuration]);
256
-
257
- return {
258
- peaks,
259
- loadedPercent,
260
- isLoading,
261
- isComplete,
262
- error,
263
- loadedRanges,
264
- detectedDuration,
265
- retry,
266
- };
267
- }
@@ -1,121 +0,0 @@
1
- /**
2
- * Core audio-related types
3
- */
4
-
5
- import type WaveSurfer from 'wavesurfer.js';
6
- import type { AudioLevels } from './effects';
7
-
8
- // =============================================================================
9
- // AUDIO SOURCE
10
- // =============================================================================
11
-
12
- export interface AudioSource {
13
- /** Audio URL (streaming or direct) */
14
- uri: string;
15
- /**
16
- * Pre-fetch the URL as blob before passing to WaveSurfer.
17
- * Required for streaming URLs because WaveSurfer needs complete file for seek to work.
18
- * When true, the URL will be fetched and converted to blob URL.
19
- * @default false
20
- */
21
- prefetch?: boolean;
22
- }
23
-
24
- // =============================================================================
25
- // PLAYBACK STATE
26
- // =============================================================================
27
-
28
- export interface PlaybackStatus {
29
- isLoaded: boolean;
30
- isPlaying: boolean;
31
- duration: number;
32
- currentTime: number;
33
- volume: number;
34
- isMuted: boolean;
35
- }
36
-
37
- // =============================================================================
38
- // WAVEFORM OPTIONS
39
- // =============================================================================
40
-
41
- export interface WaveformOptions {
42
- waveColor?: string;
43
- progressColor?: string;
44
- height?: number;
45
- barWidth?: number;
46
- barRadius?: number;
47
- barGap?: number;
48
- cursorWidth?: number;
49
- cursorColor?: string;
50
- }
51
-
52
- // =============================================================================
53
- // EQUALIZER OPTIONS
54
- // =============================================================================
55
-
56
- export interface EqualizerOptions {
57
- barCount?: number;
58
- height?: number;
59
- gap?: number;
60
- showPeaks?: boolean;
61
- barColor?: string;
62
- peakColor?: string;
63
- }
64
-
65
- // =============================================================================
66
- // SHARED WEB AUDIO CONTEXT
67
- // =============================================================================
68
-
69
- export interface SharedWebAudioContext {
70
- /** The Web Audio AudioContext instance */
71
- audioContext: AudioContext | null;
72
- /** The MediaElementSourceNode connected to the audio element */
73
- sourceNode: MediaElementAudioSourceNode | null;
74
- /** Create an AnalyserNode connected to the shared source */
75
- createAnalyser: (options?: { fftSize?: number; smoothing?: number }) => AnalyserNode | null;
76
- /** Disconnect and cleanup an AnalyserNode */
77
- disconnectAnalyser: (analyser: AnalyserNode) => void;
78
- }
79
-
80
- // =============================================================================
81
- // AUDIO CONTEXT STATE
82
- // =============================================================================
83
-
84
- export interface AudioContextState {
85
- // Core instances
86
- wavesurfer: WaveSurfer | null;
87
- audioElement: HTMLMediaElement | null;
88
- /** Shared Web Audio context for analyzers */
89
- sharedAudio: SharedWebAudioContext;
90
-
91
- // Playback state
92
- isReady: boolean;
93
- isPlaying: boolean;
94
- currentTime: number;
95
- duration: number;
96
- volume: number;
97
- isMuted: boolean;
98
- isLooping: boolean;
99
-
100
- // Prefetch state (for streaming URLs)
101
- /** Whether audio is being prefetched as blob */
102
- isPrefetching: boolean;
103
- /** Prefetch progress (0-100) */
104
- prefetchProgress: number;
105
-
106
- // Audio analysis (for reactive effects)
107
- audioLevels: AudioLevels;
108
-
109
- // Actions
110
- play: () => Promise<void>;
111
- pause: () => void;
112
- togglePlay: () => void;
113
- seek: (time: number) => void;
114
- seekTo: (progress: number) => void;
115
- skip: (seconds: number) => void;
116
- setVolume: (volume: number) => void;
117
- toggleMute: () => void;
118
- toggleLoop: () => void;
119
- setLoop: (enabled: boolean) => void;
120
- restart: () => void;
121
- }
@@ -1,98 +0,0 @@
1
- /**
2
- * Component props types
3
- */
4
-
5
- import type { CSSProperties } from 'react';
6
- import type { WaveformOptions, EqualizerOptions, AudioSource, PlaybackStatus } from './audio';
7
-
8
- // =============================================================================
9
- // AUDIO PLAYER PROPS
10
- // =============================================================================
11
-
12
- export interface AudioPlayerProps {
13
- /** Show playback controls */
14
- showControls?: boolean;
15
- /** Show waveform visualization */
16
- showWaveform?: boolean;
17
- /** Show equalizer animation */
18
- showEqualizer?: boolean;
19
- /** Show timer (position/duration) */
20
- showTimer?: boolean;
21
- /** Show volume control */
22
- showVolume?: boolean;
23
- /** Show loop/repeat button */
24
- showLoop?: boolean;
25
- /** WaveSurfer options override */
26
- waveformOptions?: WaveformOptions;
27
- /** Equalizer options */
28
- equalizerOptions?: EqualizerOptions;
29
- /** Additional class name */
30
- className?: string;
31
- /** Additional styles */
32
- style?: CSSProperties;
33
- }
34
-
35
- // =============================================================================
36
- // AUDIO EQUALIZER PROPS
37
- // =============================================================================
38
-
39
- export interface AudioEqualizerProps {
40
- /** Number of frequency bars */
41
- barCount?: number;
42
- /** Height of the equalizer in pixels */
43
- height?: number;
44
- /** Gap between bars in pixels */
45
- gap?: number;
46
- /** Show peak indicators */
47
- showPeaks?: boolean;
48
- /** Bar color (CSS color) */
49
- barColor?: string;
50
- /** Peak indicator color */
51
- peakColor?: string;
52
- /** Additional class name */
53
- className?: string;
54
- }
55
-
56
- // =============================================================================
57
- // AUDIO REACTIVE COVER PROPS
58
- // =============================================================================
59
-
60
- export interface AudioReactiveCoverProps {
61
- /** Visual variant */
62
- variant?: 'glow' | 'orbs' | 'spotlight' | 'mesh';
63
- /** Intensity of effects */
64
- intensity?: 'subtle' | 'medium' | 'strong';
65
- /** Color scheme */
66
- colorScheme?: 'primary' | 'vibrant' | 'cool' | 'warm';
67
- }
68
-
69
- // =============================================================================
70
- // AUDIO VIEWER PROPS (legacy)
71
- // =============================================================================
72
-
73
- export interface AudioViewerProps {
74
- /** Audio source URL */
75
- source: AudioSource;
76
- /** Auto-play when loaded */
77
- autoPlay?: boolean;
78
- /** Show playback controls */
79
- showControls?: boolean;
80
- /** Show waveform visualization */
81
- showWaveform?: boolean;
82
- /** Show equalizer animation */
83
- showEqualizer?: boolean;
84
- /** Show timer */
85
- showTimer?: boolean;
86
- /** Show volume control */
87
- showVolume?: boolean;
88
- /** Waveform options */
89
- waveformOptions?: WaveformOptions;
90
- /** Equalizer options */
91
- equalizerOptions?: EqualizerOptions;
92
- /** Callback on playback status change */
93
- onPlaybackStatusUpdate?: (status: PlaybackStatus) => void;
94
- /** Additional class name */
95
- className?: string;
96
- /** Additional styles */
97
- style?: CSSProperties;
98
- }