@djangocfg/ui-nextjs 2.1.83 → 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 (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 +5 -5
  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,236 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * AudioPlayer - WaveSurfer-based player component (Legacy)
5
- *
6
- * @deprecated Consider using `HybridAudioPlayer` instead for better streaming
7
- * support and guaranteed no audio crackling.
8
- *
9
- * Uses AudioContext for state management.
10
- * Renders waveform (via container ref), controls, timer, and equalizer.
11
- * Supports keyboard shortcuts for playback control.
12
- *
13
- * @see HybridAudioPlayer for the recommended alternative
14
- */
15
-
16
- import { forwardRef } from 'react';
17
- import {
18
- Play,
19
- Pause,
20
- RotateCcw,
21
- SkipBack,
22
- SkipForward,
23
- Volume2,
24
- VolumeX,
25
- Loader2,
26
- Repeat,
27
- } from 'lucide-react';
28
- import { Button, Slider, cn } from '@djangocfg/ui-nextjs';
29
- import { useAudio } from '../context';
30
- import { AudioEqualizer } from './AudioEqualizer';
31
- import { useAudioHotkeys } from '../hooks';
32
- import { AudioShortcutsPopover } from './AudioShortcutsPopover';
33
- import { formatTime } from '../utils';
34
- import type { AudioPlayerProps } from '../types';
35
-
36
- // =============================================================================
37
- // COMPONENT
38
- // =============================================================================
39
-
40
- export const AudioPlayer = forwardRef<HTMLDivElement, AudioPlayerProps>(
41
- function AudioPlayer(
42
- {
43
- showControls = true,
44
- showWaveform = true,
45
- showEqualizer = false,
46
- showTimer = true,
47
- showVolume = true,
48
- showLoop = true,
49
- equalizerOptions = {},
50
- className,
51
- style,
52
- },
53
- ref
54
- ) {
55
- // Get all state and controls from context
56
- const {
57
- isReady,
58
- isPlaying,
59
- currentTime,
60
- duration,
61
- volume,
62
- isMuted,
63
- isLooping,
64
- togglePlay,
65
- restart,
66
- skip,
67
- setVolume,
68
- toggleMute,
69
- toggleLoop,
70
- } = useAudio();
71
-
72
- // Enable keyboard shortcuts
73
- useAudioHotkeys({ enabled: isReady });
74
-
75
- const isLoading = !isReady;
76
-
77
- const handleVolumeChange = (value: number[]) => {
78
- setVolume(value[0] / 100);
79
- };
80
-
81
- return (
82
- <div
83
- className={cn(
84
- 'flex flex-col gap-3 p-4 rounded-lg bg-card border',
85
- className
86
- )}
87
- style={style}
88
- >
89
- {/* Waveform container - rendered by WaveSurfer via context */}
90
- {showWaveform && (
91
- <div className="relative">
92
- <div
93
- ref={ref}
94
- className={cn(
95
- 'w-full rounded transition-opacity',
96
- isLoading && 'opacity-50'
97
- )}
98
- />
99
- {isLoading && (
100
- <div className="absolute inset-0 flex items-center justify-center">
101
- <Loader2 className="h-6 w-6 animate-spin text-primary" />
102
- </div>
103
- )}
104
- </div>
105
- )}
106
-
107
- {/* Equalizer animation */}
108
- {showEqualizer && (
109
- <AudioEqualizer
110
- barCount={equalizerOptions.barCount}
111
- height={equalizerOptions.height}
112
- gap={equalizerOptions.gap}
113
- showPeaks={equalizerOptions.showPeaks}
114
- barColor={equalizerOptions.barColor}
115
- peakColor={equalizerOptions.peakColor}
116
- className="px-1"
117
- />
118
- )}
119
-
120
- {/* Timer */}
121
- {showTimer && (
122
- <div className="flex justify-between text-xs text-muted-foreground tabular-nums px-1">
123
- <span>{formatTime(currentTime)}</span>
124
- <span>{formatTime(duration)}</span>
125
- </div>
126
- )}
127
-
128
- {/* Controls */}
129
- {showControls && (
130
- <div className="flex items-center justify-center gap-1">
131
- {/* Restart */}
132
- <Button
133
- variant="ghost"
134
- size="icon"
135
- className="h-9 w-9"
136
- onClick={restart}
137
- disabled={!isReady}
138
- title="Restart"
139
- >
140
- <RotateCcw className="h-4 w-4" />
141
- </Button>
142
-
143
- {/* Skip back 5s */}
144
- <Button
145
- variant="ghost"
146
- size="icon"
147
- className="h-9 w-9"
148
- onClick={() => skip(-5)}
149
- disabled={!isReady}
150
- title="Back 5 seconds"
151
- >
152
- <SkipBack className="h-4 w-4" />
153
- </Button>
154
-
155
- {/* Play/Pause */}
156
- <Button
157
- variant="default"
158
- size="icon"
159
- className="h-12 w-12 rounded-full"
160
- onClick={togglePlay}
161
- disabled={!isReady && !isLoading}
162
- title={isPlaying ? 'Pause' : 'Play'}
163
- >
164
- {isLoading ? (
165
- <Loader2 className="h-5 w-5 animate-spin" />
166
- ) : isPlaying ? (
167
- <Pause className="h-5 w-5" />
168
- ) : (
169
- <Play className="h-5 w-5 ml-0.5" />
170
- )}
171
- </Button>
172
-
173
- {/* Skip forward 5s */}
174
- <Button
175
- variant="ghost"
176
- size="icon"
177
- className="h-9 w-9"
178
- onClick={() => skip(5)}
179
- disabled={!isReady}
180
- title="Forward 5 seconds"
181
- >
182
- <SkipForward className="h-4 w-4" />
183
- </Button>
184
-
185
- {/* Volume */}
186
- {showVolume && (
187
- <>
188
- <Button
189
- variant="ghost"
190
- size="icon"
191
- className="h-9 w-9"
192
- onClick={toggleMute}
193
- title={isMuted ? 'Unmute' : 'Mute'}
194
- >
195
- {isMuted || volume === 0 ? (
196
- <VolumeX className="h-4 w-4" />
197
- ) : (
198
- <Volume2 className="h-4 w-4" />
199
- )}
200
- </Button>
201
-
202
- <Slider
203
- value={[isMuted ? 0 : volume * 100]}
204
- max={100}
205
- step={1}
206
- onValueChange={handleVolumeChange}
207
- className="w-20"
208
- aria-label="Volume"
209
- />
210
- </>
211
- )}
212
-
213
- {/* Loop/Repeat */}
214
- {showLoop && (
215
- <Button
216
- variant="ghost"
217
- size="icon"
218
- className={cn('h-9 w-9', isLooping && 'text-primary')}
219
- onClick={toggleLoop}
220
- disabled={!isReady}
221
- title={isLooping ? 'Disable loop' : 'Enable loop'}
222
- >
223
- <Repeat className="h-4 w-4" />
224
- </Button>
225
- )}
226
-
227
- {/* Shortcuts help */}
228
- <AudioShortcutsPopover compact />
229
- </div>
230
- )}
231
- </div>
232
- );
233
- }
234
- );
235
-
236
- export default AudioPlayer;
@@ -1,99 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * AudioShortcutsPopover
5
- *
6
- * Displays keyboard shortcuts for audio player controls.
7
- */
8
-
9
- import {
10
- Popover,
11
- PopoverContent,
12
- PopoverTrigger,
13
- Button,
14
- Kbd,
15
- KbdGroup,
16
- Separator,
17
- Tooltip,
18
- TooltipTrigger,
19
- TooltipContent,
20
- } from '@djangocfg/ui-nextjs';
21
- import { Keyboard } from 'lucide-react';
22
- import { AUDIO_SHORTCUTS } from '../hooks';
23
-
24
- // =============================================================================
25
- // TYPES
26
- // =============================================================================
27
-
28
- interface AudioShortcutsPopoverProps {
29
- /** Compact mode for smaller displays */
30
- compact?: boolean;
31
- }
32
-
33
- // =============================================================================
34
- // COMPONENT
35
- // =============================================================================
36
-
37
- export function AudioShortcutsPopover({ compact = false }: AudioShortcutsPopoverProps) {
38
- const trigger = (
39
- <PopoverTrigger asChild>
40
- <Button
41
- variant="ghost"
42
- size="icon"
43
- className={compact ? 'h-6 w-6' : 'size-7 text-muted-foreground hover:text-foreground'}
44
- title={compact ? undefined : 'Keyboard shortcuts'}
45
- >
46
- <Keyboard className={compact ? 'h-3.5 w-3.5' : 'size-4'} />
47
- </Button>
48
- </PopoverTrigger>
49
- );
50
-
51
- return (
52
- <Popover>
53
- {compact ? (
54
- <Tooltip>
55
- <TooltipTrigger asChild>{trigger}</TooltipTrigger>
56
- <TooltipContent side="bottom">Shortcuts</TooltipContent>
57
- </Tooltip>
58
- ) : (
59
- trigger
60
- )}
61
- <PopoverContent className="w-56 p-0" align="end">
62
- <div className="px-3 py-2 border-b">
63
- <h4 className="font-medium text-sm">Player Shortcuts</h4>
64
- </div>
65
- <div className="p-2 space-y-3 max-h-72 overflow-y-auto">
66
- {AUDIO_SHORTCUTS.map((group, groupIndex) => (
67
- <div key={group.title}>
68
- {groupIndex > 0 && <Separator className="my-2" />}
69
- <div className="text-xs font-medium text-muted-foreground mb-1.5 px-1">
70
- {group.title}
71
- </div>
72
- <div className="space-y-1">
73
- {group.shortcuts.map((shortcut) => (
74
- <div
75
- key={shortcut.label}
76
- className="flex items-center justify-between px-1 py-0.5"
77
- >
78
- <span className="text-sm text-foreground">
79
- {shortcut.label}
80
- </span>
81
- <KbdGroup>
82
- {shortcut.keys.map((key, i) => (
83
- <Kbd key={i} size="sm">
84
- {key}
85
- </Kbd>
86
- ))}
87
- </KbdGroup>
88
- </div>
89
- ))}
90
- </div>
91
- </div>
92
- ))}
93
- </div>
94
- </PopoverContent>
95
- </Popover>
96
- );
97
- }
98
-
99
- export default AudioShortcutsPopover;
@@ -1,278 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * SimpleAudioPlayer - WaveSurfer-based audio player (Legacy)
5
- *
6
- * @deprecated Consider using `HybridSimplePlayer` instead for better streaming
7
- * support, lower memory usage, and guaranteed no audio crackling.
8
- *
9
- * Use SimpleAudioPlayer only when you specifically need the static waveform
10
- * visualization that shows the full audio amplitude shape.
11
- *
12
- * Migration:
13
- * ```tsx
14
- * // Before
15
- * <SimpleAudioPlayer src={url} prefetch reactiveCover />
16
- *
17
- * // After
18
- * <HybridSimplePlayer src={url} reactiveCover />
19
- * ```
20
- *
21
- * Key differences:
22
- * - HybridSimplePlayer: Real-time frequency bars, native streaming, no prefetch needed
23
- * - SimpleAudioPlayer: Static waveform shape, requires prefetch for streaming URLs
24
- */
25
-
26
- import { useRef, type ReactNode } from 'react';
27
- import { Music } from 'lucide-react';
28
- import { cn } from '@djangocfg/ui-core';
29
-
30
- import { AudioProvider } from '../context';
31
- import { AudioPlayer } from './AudioPlayer';
32
- import { AudioReactiveCover } from './ReactiveCover';
33
- import { VisualizationProvider, useVisualization } from '../hooks';
34
- import type { WaveformOptions, EqualizerOptions } from '../types';
35
- import type { EffectIntensity, EffectColorScheme } from '../effects';
36
- import type { VisualizationVariant } from '../hooks';
37
-
38
- // =============================================================================
39
- // TYPES
40
- // =============================================================================
41
-
42
- export interface SimpleAudioPlayerProps {
43
- /** Audio source URL */
44
- src: string;
45
-
46
- /**
47
- * Pre-fetch audio as blob before loading into WaveSurfer.
48
- * Required for streaming URLs because WaveSurfer needs complete file for seek to work.
49
- * @default true
50
- */
51
- prefetch?: boolean;
52
-
53
- /** Track title */
54
- title?: string;
55
-
56
- /** Artist name */
57
- artist?: string;
58
-
59
- /** Cover art URL or ReactNode */
60
- coverArt?: string | ReactNode;
61
-
62
- /** Cover art size */
63
- coverSize?: 'sm' | 'md' | 'lg';
64
-
65
- /** Show waveform visualization */
66
- showWaveform?: boolean;
67
-
68
- /** Show equalizer bars */
69
- showEqualizer?: boolean;
70
-
71
- /** Show timer */
72
- showTimer?: boolean;
73
-
74
- /** Show volume control */
75
- showVolume?: boolean;
76
-
77
- /** Show loop/repeat button */
78
- showLoop?: boolean;
79
-
80
- /** Enable audio-reactive cover effects */
81
- reactiveCover?: boolean;
82
-
83
- /** Reactive effect variant */
84
- variant?: VisualizationVariant;
85
-
86
- /** Reactive effect intensity */
87
- intensity?: EffectIntensity;
88
-
89
- /** Reactive effect color scheme */
90
- colorScheme?: EffectColorScheme;
91
-
92
- /** Auto-play on load */
93
- autoPlay?: boolean;
94
-
95
- /** Waveform customization */
96
- waveformOptions?: WaveformOptions;
97
-
98
- /** Equalizer customization */
99
- equalizerOptions?: EqualizerOptions;
100
-
101
- /** Layout direction */
102
- layout?: 'vertical' | 'horizontal';
103
-
104
- /** Additional class name */
105
- className?: string;
106
- }
107
-
108
- // =============================================================================
109
- // CONSTANTS
110
- // =============================================================================
111
-
112
- const COVER_SIZES = {
113
- sm: 'w-24 h-24',
114
- md: 'w-32 h-32',
115
- lg: 'w-48 h-48',
116
- };
117
-
118
- // =============================================================================
119
- // COMPONENT
120
- // =============================================================================
121
-
122
- export function SimpleAudioPlayer(props: SimpleAudioPlayerProps) {
123
- return (
124
- <VisualizationProvider>
125
- <SimpleAudioPlayerContent {...props} />
126
- </VisualizationProvider>
127
- );
128
- }
129
-
130
- function SimpleAudioPlayerContent({
131
- src,
132
- prefetch = true,
133
- title,
134
- artist,
135
- coverArt,
136
- coverSize = 'md',
137
- showWaveform = true,
138
- showEqualizer = false,
139
- showTimer = true,
140
- showVolume = true,
141
- showLoop = true,
142
- reactiveCover = true,
143
- variant,
144
- intensity,
145
- colorScheme,
146
- autoPlay = false,
147
- waveformOptions,
148
- equalizerOptions,
149
- layout = 'vertical',
150
- className,
151
- }: SimpleAudioPlayerProps) {
152
- const containerRef = useRef<HTMLDivElement>(null);
153
- const { settings: vizSettings, nextVariant } = useVisualization();
154
-
155
- // Determine effective variant (from props or localStorage settings)
156
- const effectiveVariant = variant ?? (vizSettings.variant !== 'none' ? vizSettings.variant : 'spotlight');
157
- const effectiveIntensity = intensity ?? vizSettings.intensity;
158
- const effectiveColorScheme = colorScheme ?? vizSettings.colorScheme;
159
-
160
- // Show reactive cover if enabled and variant is not 'none'
161
- const showReactiveCover = reactiveCover && effectiveVariant !== 'none';
162
-
163
- // Render cover art content
164
- const renderCoverContent = () => {
165
- if (typeof coverArt === 'string') {
166
- return (
167
- <img
168
- src={coverArt}
169
- alt={title || 'Album cover'}
170
- className="w-full h-full object-cover"
171
- />
172
- );
173
- }
174
-
175
- if (coverArt) {
176
- return coverArt;
177
- }
178
-
179
- // Default placeholder
180
- return (
181
- <div className="w-full h-full bg-muted/30 flex items-center justify-center">
182
- <Music className="w-1/3 h-1/3 text-muted-foreground/50" />
183
- </div>
184
- );
185
- };
186
-
187
- const isHorizontal = layout === 'horizontal';
188
-
189
- return (
190
- <AudioProvider
191
- source={{ uri: src, prefetch }}
192
- containerRef={containerRef}
193
- autoPlay={autoPlay}
194
- waveformOptions={waveformOptions}
195
- >
196
- <div
197
- className={cn(
198
- 'flex gap-4',
199
- isHorizontal ? 'flex-row items-center' : 'flex-col items-center',
200
- className
201
- )}
202
- >
203
- {/* Cover Art */}
204
- {(coverArt || reactiveCover) && (
205
- <div className="flex flex-col items-center gap-2 shrink-0">
206
- {showReactiveCover ? (
207
- <AudioReactiveCover
208
- size={coverSize}
209
- variant={effectiveVariant as 'glow' | 'orbs' | 'spotlight' | 'mesh'}
210
- intensity={effectiveIntensity}
211
- colorScheme={effectiveColorScheme}
212
- onClick={nextVariant}
213
- >
214
- <div className={cn('rounded-lg overflow-hidden', COVER_SIZES[coverSize])}>
215
- {renderCoverContent()}
216
- </div>
217
- </AudioReactiveCover>
218
- ) : (
219
- <div
220
- className={cn(
221
- 'rounded-lg overflow-hidden shadow-lg cursor-pointer',
222
- COVER_SIZES[coverSize]
223
- )}
224
- onClick={nextVariant}
225
- role="button"
226
- tabIndex={0}
227
- onKeyDown={(e) => e.key === 'Enter' && nextVariant()}
228
- >
229
- {renderCoverContent()}
230
- </div>
231
- )}
232
-
233
- {/* Effect indicator */}
234
- {reactiveCover && (
235
- <span className="text-[10px] uppercase tracking-wider text-muted-foreground/50 select-none">
236
- {vizSettings.variant === 'none' ? 'off' : vizSettings.variant}
237
- </span>
238
- )}
239
- </div>
240
- )}
241
-
242
- {/* Track Info + Player */}
243
- <div className={cn('flex flex-col gap-3', isHorizontal ? 'flex-1 min-w-0' : 'w-full max-w-md')}>
244
- {/* Track Info */}
245
- {(title || artist) && (
246
- <div className={cn('text-center', isHorizontal && 'text-left')}>
247
- {title && (
248
- <h3 className="text-base font-medium text-foreground truncate">
249
- {title}
250
- </h3>
251
- )}
252
- {artist && (
253
- <p className="text-sm text-muted-foreground truncate">
254
- {artist}
255
- </p>
256
- )}
257
- </div>
258
- )}
259
-
260
- {/* Audio Player */}
261
- <AudioPlayer
262
- ref={containerRef}
263
- showControls
264
- showWaveform={showWaveform}
265
- showEqualizer={showEqualizer}
266
- showTimer={showTimer}
267
- showVolume={showVolume}
268
- showLoop={showLoop}
269
- equalizerOptions={equalizerOptions}
270
- className="border-0 bg-transparent"
271
- />
272
- </div>
273
- </div>
274
- </AudioProvider>
275
- );
276
- }
277
-
278
- export default SimpleAudioPlayer;
@@ -1,64 +0,0 @@
1
- 'use client';
2
-
3
- /**
4
- * VisualizationToggle - Simple toggle for audio visualization settings
5
- *
6
- * Click: cycle through variants
7
- * Long press / right click: opens menu (future)
8
- */
9
-
10
- import { Sparkles } from 'lucide-react';
11
- import { Button, cn } from '@djangocfg/ui-nextjs';
12
- import { useVisualization, VARIANT_INFO } from '../hooks';
13
-
14
- // =============================================================================
15
- // TYPES
16
- // =============================================================================
17
-
18
- export interface VisualizationToggleProps {
19
- /** Compact mode (icon only) */
20
- compact?: boolean;
21
- /** Additional class name */
22
- className?: string;
23
- }
24
-
25
- // =============================================================================
26
- // COMPONENT
27
- // =============================================================================
28
-
29
- export function VisualizationToggle({
30
- compact = false,
31
- className,
32
- }: VisualizationToggleProps) {
33
- const { settings, nextVariant } = useVisualization();
34
-
35
- const currentInfo = VARIANT_INFO[settings.variant];
36
- const isEnabled = settings.variant !== 'none';
37
-
38
- return (
39
- <Button
40
- variant={isEnabled ? 'secondary' : 'ghost'}
41
- size={compact ? 'icon' : 'sm'}
42
- className={cn(
43
- 'transition-all',
44
- isEnabled && 'bg-primary/10 text-primary hover:bg-primary/20',
45
- className
46
- )}
47
- onClick={nextVariant}
48
- title={`Visualization: ${currentInfo.label} (click to change)`}
49
- >
50
- <Sparkles
51
- className={cn(
52
- 'h-4 w-4',
53
- isEnabled && 'text-primary',
54
- !compact && 'mr-1.5'
55
- )}
56
- />
57
- {!compact && (
58
- <span className="text-xs">{currentInfo.label}</span>
59
- )}
60
- </Button>
61
- );
62
- }
63
-
64
- export default VisualizationToggle;