@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-nextjs",
3
- "version": "2.1.82",
3
+ "version": "2.1.84",
4
4
  "description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -58,8 +58,8 @@
58
58
  "check": "tsc --noEmit"
59
59
  },
60
60
  "peerDependencies": {
61
- "@djangocfg/api": "^2.1.82",
62
- "@djangocfg/ui-core": "^2.1.82",
61
+ "@djangocfg/api": "^2.1.84",
62
+ "@djangocfg/ui-core": "^2.1.84",
63
63
  "@types/react": "^19.1.0",
64
64
  "@types/react-dom": "^19.1.0",
65
65
  "consola": "^3.4.2",
@@ -110,7 +110,7 @@
110
110
  "wavesurfer.js": "^7.12.1"
111
111
  },
112
112
  "devDependencies": {
113
- "@djangocfg/typescript-config": "^2.1.82",
113
+ "@djangocfg/typescript-config": "^2.1.84",
114
114
  "@types/node": "^24.7.2",
115
115
  "eslint": "^9.37.0",
116
116
  "tailwindcss-animate": "1.0.7",
@@ -1,289 +1,172 @@
1
1
  # AudioPlayer
2
2
 
3
- Audio player with waveform visualization, built on WaveSurfer.js.
3
+ Audio player with native HTML5 streaming and audio-reactive visualizations.
4
4
 
5
5
  ## Features
6
6
 
7
- - Waveform visualization with interactive seeking
7
+ - Native HTML5 audio playback (no crackling, native streaming support)
8
+ - Web Audio API for real-time frequency analysis
8
9
  - Audio-reactive cover effects (glow, orbs, spotlight, mesh)
9
- - Real-time frequency equalizer
10
- - Keyboard shortcuts
11
- - Volume control with mute
12
- - Skip forward/backward
13
- - Playback speed control
10
+ - Frequency visualization waveform
11
+ - Full playback controls
14
12
 
15
- ## Quick Start (Recommended)
13
+ ## Quick Start
16
14
 
17
15
  ```tsx
18
- import { SimpleAudioPlayer } from '@djangocfg/ui-nextjs';
16
+ import { HybridSimplePlayer } from '@djangocfg/ui-nextjs';
19
17
 
20
- // Minimal
21
- <SimpleAudioPlayer src="https://example.com/audio.mp3" />
18
+ // Simple usage
19
+ <HybridSimplePlayer src="https://example.com/audio.mp3" />
22
20
 
23
- // With metadata
24
- <SimpleAudioPlayer
21
+ // With metadata and reactive cover
22
+ <HybridSimplePlayer
25
23
  src={audioUrl}
26
24
  title="Track Title"
27
25
  artist="Artist Name"
28
26
  coverArt="/path/to/cover.jpg"
27
+ reactiveCover
28
+ variant="spotlight"
29
29
  />
30
30
 
31
31
  // Full customization
32
- <SimpleAudioPlayer
32
+ <HybridSimplePlayer
33
33
  src={audioUrl}
34
34
  title="Track Title"
35
35
  coverArt={coverUrl}
36
36
  showWaveform
37
- showEqualizer={false}
37
+ waveformMode="frequency" // 'frequency' | 'static'
38
+ showLoop
38
39
  reactiveCover
39
- variant="spotlight"
40
- layout="horizontal"
40
+ variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh' | 'none'
41
+ intensity="medium" // 'subtle' | 'medium' | 'strong'
42
+ colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
43
+ layout="horizontal" // 'vertical' | 'horizontal'
41
44
  />
42
45
  ```
43
46
 
44
- ### SimpleAudioPlayer Props
47
+ ## Props
45
48
 
46
49
  | Prop | Type | Default | Description |
47
50
  |------|------|---------|-------------|
48
51
  | `src` | `string` | required | Audio URL |
49
- | `prefetch` | `boolean` | `true` | Pre-fetch audio as blob (required for streaming URLs to enable seek) |
50
52
  | `title` | `string` | - | Track title |
51
53
  | `artist` | `string` | - | Artist name |
52
54
  | `coverArt` | `string \| ReactNode` | - | Cover image URL or custom element |
53
55
  | `coverSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | Cover art size |
54
- | `showWaveform` | `boolean` | `true` | Show waveform |
55
- | `showEqualizer` | `boolean` | `false` | Show equalizer bars |
56
+ | `showWaveform` | `boolean` | `true` | Show frequency visualization |
57
+ | `waveformMode` | `'frequency' \| 'static'` | `'frequency'` | Visualization mode |
58
+ | `waveformHeight` | `number` | `64` | Waveform height in pixels |
56
59
  | `showTimer` | `boolean` | `true` | Show time display |
57
60
  | `showVolume` | `boolean` | `true` | Show volume control |
58
- | `showLoop` | `boolean` | `true` | Show loop/repeat button |
61
+ | `showLoop` | `boolean` | `true` | Show loop button |
59
62
  | `reactiveCover` | `boolean` | `true` | Enable reactive effects |
60
- | `variant` | `VisualizationVariant` | - | Effect variant |
61
- | `intensity` | `EffectIntensity` | - | Effect intensity |
62
- | `colorScheme` | `EffectColorScheme` | - | Effect colors |
63
+ | `variant` | `VisualizationVariant` | `'spotlight'` | Effect variant |
64
+ | `intensity` | `EffectIntensity` | `'medium'` | Effect intensity |
65
+ | `colorScheme` | `EffectColorScheme` | `'primary'` | Effect colors |
63
66
  | `autoPlay` | `boolean` | `false` | Auto-play on load |
67
+ | `loop` | `boolean` | `false` | Loop playback |
64
68
  | `layout` | `'vertical' \| 'horizontal'` | `'vertical'` | Layout direction |
65
69
 
66
- > **Note:** The `prefetch` option is enabled by default. This fetches the entire audio file as a blob before loading into WaveSurfer, which is required for seeking to work correctly with streaming URLs. For very large files (> 50MB), consider using `prefetch={false}` and the Progressive player mode instead.
67
-
68
- ---
69
-
70
70
  ## Advanced Usage
71
71
 
72
- For full control, use `AudioProvider` + `AudioPlayer` directly:
72
+ ### HybridAudioProvider + HybridAudioPlayer
73
+
74
+ For custom layouts:
73
75
 
74
76
  ```tsx
75
- import { useRef } from 'react';
76
- import { AudioProvider, AudioPlayer } from '@djangocfg/ui-nextjs';
77
+ import {
78
+ HybridAudioProvider,
79
+ HybridAudioPlayer,
80
+ AudioReactiveCover,
81
+ useHybridAudioContext
82
+ } from '@djangocfg/ui-nextjs';
83
+
84
+ function MyPlayer({ audioUrl }: { audioUrl: string }) {
85
+ return (
86
+ <HybridAudioProvider src={audioUrl}>
87
+ <AudioReactiveCover variant="spotlight" onClick={handleClick}>
88
+ <img src={coverUrl} alt="Cover" />
89
+ </AudioReactiveCover>
90
+ <HybridAudioPlayer showWaveform showControls />
91
+ <CustomControls />
92
+ </HybridAudioProvider>
93
+ );
94
+ }
77
95
 
78
- function MyAudioPlayer({ audioUrl }: { audioUrl: string }) {
79
- const containerRef = useRef<HTMLDivElement>(null);
96
+ function CustomControls() {
97
+ const { state, controls, audioLevels } = useHybridAudioContext();
80
98
 
81
99
  return (
82
- <AudioProvider
83
- source={{ uri: audioUrl }}
84
- containerRef={containerRef}
85
- >
86
- <AudioPlayer
87
- ref={containerRef}
88
- showControls
89
- showWaveform
90
- showTimer
91
- showVolume
92
- />
93
- </AudioProvider>
100
+ <div>
101
+ <p>Bass level: {(audioLevels.bass * 100).toFixed(0)}%</p>
102
+ <button onClick={controls.togglePlay}>
103
+ {state.isPlaying ? 'Pause' : 'Play'}
104
+ </button>
105
+ </div>
94
106
  );
95
107
  }
96
108
  ```
97
109
 
98
- ## Components
110
+ ## Hooks
99
111
 
100
- ### AudioProvider
112
+ ### useHybridAudioContext
101
113
 
102
- Context provider for audio state. Wraps all audio components.
114
+ Full context access:
103
115
 
104
116
  ```tsx
105
- <AudioProvider
106
- source={{
107
- uri: 'https://example.com/audio.mp3',
108
- prefetch: true // Fetch as blob for seek support (default: false)
109
- }}
110
- containerRef={containerRef}
111
- autoPlay={false}
112
- waveformOptions={{
113
- waveColor: 'hsl(217 91% 60% / 0.3)',
114
- progressColor: 'hsl(217 91% 60%)',
115
- height: 64,
116
- barWidth: 3,
117
- barRadius: 3,
118
- barGap: 2,
119
- }}
120
- >
121
- {children}
122
- </AudioProvider>
117
+ const {
118
+ state, // { isReady, isPlaying, currentTime, duration, volume, isMuted, isLooping }
119
+ controls, // { play, pause, togglePlay, seek, skip, setVolume, toggleMute, toggleLoop }
120
+ audioLevels, // { bass, mid, high, overall }
121
+ webAudio, // { context, analyser, sourceNode }
122
+ audioRef, // React ref to HTMLAudioElement
123
+ } = useHybridAudioContext();
123
124
  ```
124
125
 
125
- #### AudioSource Options
126
-
127
- | Prop | Type | Default | Description |
128
- |------|------|---------|-------------|
129
- | `uri` | `string` | required | Audio URL |
130
- | `prefetch` | `boolean` | `false` | Pre-fetch as blob (enables seek for streaming URLs) |
131
-
132
- ### AudioPlayer
126
+ ### Specialized Hooks
133
127
 
134
- Main player component with waveform and controls.
135
-
136
- | Prop | Type | Default | Description |
137
- |------|------|---------|-------------|
138
- | `showControls` | `boolean` | `true` | Show playback controls |
139
- | `showWaveform` | `boolean` | `true` | Show waveform visualization |
140
- | `showEqualizer` | `boolean` | `false` | Show equalizer animation |
141
- | `showTimer` | `boolean` | `true` | Show time display |
142
- | `showVolume` | `boolean` | `true` | Show volume slider |
143
- | `className` | `string` | - | Additional CSS class |
128
+ ```tsx
129
+ // State only
130
+ const { isPlaying, currentTime, duration } = useHybridAudioState();
144
131
 
145
- ### AudioEqualizer
132
+ // Controls only (no re-render on time updates)
133
+ const { play, pause, togglePlay, seek } = useHybridAudioControls();
146
134
 
147
- Real-time frequency visualizer with animated bars.
135
+ // Audio levels for reactive effects
136
+ const { bass, mid, high, overall } = useHybridAudioLevels();
148
137
 
149
- ```tsx
150
- <AudioEqualizer
151
- barCount={24}
152
- height={48}
153
- gap={2}
154
- showPeaks
155
- barColor="hsl(217 91% 60%)"
156
- peakColor="hsl(217 91% 70%)"
157
- />
138
+ // Web Audio API access
139
+ const { context, analyser, sourceNode } = useHybridWebAudio();
158
140
  ```
159
141
 
142
+ ## Components
143
+
160
144
  ### AudioReactiveCover
161
145
 
162
- Album art wrapper with audio-reactive effects.
146
+ Album art wrapper with audio-reactive effects:
163
147
 
164
148
  ```tsx
165
149
  <AudioReactiveCover
166
- variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh'
167
- intensity="medium" // 'subtle' | 'medium' | 'strong'
168
- colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
150
+ variant="spotlight" // 'glow' | 'orbs' | 'spotlight' | 'mesh'
151
+ intensity="medium" // 'subtle' | 'medium' | 'strong'
152
+ colorScheme="primary" // 'primary' | 'vibrant' | 'cool' | 'warm'
169
153
  onClick={() => nextVariant()}
170
154
  >
171
155
  <img src={coverArt} alt="Album cover" />
172
156
  </AudioReactiveCover>
173
157
  ```
174
158
 
175
- ### VisualizationToggle
176
-
177
- Button to cycle through visualization variants.
178
-
179
- ```tsx
180
- <VisualizationToggle compact />
181
- ```
182
-
183
- ## Hooks
184
-
185
- ### useAudio
159
+ ### HybridWaveform
186
160
 
187
- Access full audio state and controls.
161
+ Real-time frequency visualization:
188
162
 
189
163
  ```tsx
190
- const {
191
- isReady,
192
- isPlaying,
193
- currentTime,
194
- duration,
195
- volume,
196
- isMuted,
197
- audioLevels, // { bass, mid, high, overall }
198
- play,
199
- pause,
200
- togglePlay,
201
- seek,
202
- seekTo,
203
- skip,
204
- setVolume,
205
- toggleMute,
206
- restart,
207
- } = useAudio();
208
- ```
209
-
210
- ### useAudioControls
211
-
212
- Controls only (no re-render on time updates).
213
-
214
- ```tsx
215
- const { play, pause, togglePlay, skip, restart } = useAudioControls();
216
- ```
217
-
218
- ### useAudioState
219
-
220
- Read-only playback state.
221
-
222
- ```tsx
223
- const { isPlaying, currentTime, duration, volume } = useAudioState();
224
- ```
225
-
226
- ### useAudioElement
227
-
228
- Access audio element for custom visualizations.
229
-
230
- ```tsx
231
- const { audioElement, isPlaying, audioLevels } = useAudioElement();
232
- ```
233
-
234
- ### useAudioVisualization
235
-
236
- Manage visualization settings (persisted in localStorage).
237
-
238
- ```tsx
239
- const {
240
- settings, // { enabled, variant, intensity, colorScheme }
241
- toggle,
242
- setSetting,
243
- nextVariant,
244
- nextIntensity,
245
- nextColorScheme,
246
- reset,
247
- } = useAudioVisualization();
248
- ```
249
-
250
- ### useAudioHotkeys
251
-
252
- Enable keyboard shortcuts.
253
-
254
- ```tsx
255
- useAudioHotkeys({
256
- enabled: true,
257
- skipDuration: 10,
258
- volumeStep: 0.1,
259
- });
260
- ```
261
-
262
- ## Keyboard Shortcuts
263
-
264
- | Key | Action |
265
- |-----|--------|
266
- | `Space` | Play/Pause |
267
- | `←` / `J` | Skip 10s backward |
268
- | `→` / `L` | Skip 10s forward |
269
- | `↑` | Volume up |
270
- | `↓` | Volume down |
271
- | `M` | Mute/Unmute |
272
- | `0-9` | Jump to 0-90% |
273
-
274
- ## Waveform Options
275
-
276
- ```typescript
277
- interface WaveformOptions {
278
- waveColor?: string; // Unplayed wave color
279
- progressColor?: string; // Played wave color
280
- cursorColor?: string; // Playhead cursor color
281
- cursorWidth?: number; // Cursor width in px
282
- height?: number; // Waveform height in px
283
- barWidth?: number; // Bar width in px
284
- barRadius?: number; // Bar corner radius
285
- barGap?: number; // Gap between bars
286
- }
164
+ <HybridWaveform
165
+ mode="frequency" // 'frequency' | 'static'
166
+ height={64}
167
+ barWidth={3}
168
+ barGap={2}
169
+ />
287
170
  ```
288
171
 
289
172
  ## Effect Variants
@@ -301,40 +184,23 @@ interface WaveformOptions {
301
184
  ```
302
185
  AudioPlayer/
303
186
  ├── index.ts # Public API exports
304
- ├── types/
305
- │ ├── index.ts # Type re-exports
306
- │ ├── audio.ts # Audio state & source types
307
- │ ├── components.ts # Component prop types
308
- │ └── effects.ts # Visualization effect types
187
+ ├── types/ # TypeScript types
309
188
  ├── hooks/
310
- │ ├── index.ts
311
- │ ├── useAudioSource.ts # Audio source loading with prefetch
312
- ├── useAudioHotkeys.ts # Keyboard shortcuts
313
- │ ├── useVisualization.tsx # Visualization settings
314
- │ ├── useAudioAnalysis.ts # Web Audio frequency analysis
315
- │ └── useSharedWebAudio.ts # Shared AudioContext
316
- ├── utils/
317
- │ ├── index.ts
318
- │ └── formatTime.ts # Time formatting
189
+ │ ├── useHybridAudio.ts # HTML5 audio + Web Audio hook
190
+ │ ├── useHybridAudioAnalysis.ts # Frequency analysis
191
+ └── useVisualization.tsx # Visualization settings
319
192
  ├── context/
320
- ├── index.ts
321
- │ ├── AudioProvider.tsx # Audio state provider
322
- │ └── selectors.ts # useAudio, useAudioControls hooks
193
+ └── HybridAudioProvider.tsx # Audio context provider
323
194
  ├── components/
324
- │ ├── index.ts
325
- │ ├── AudioPlayer.tsx # Main player component
326
- │ ├── SimpleAudioPlayer.tsx # All-in-one wrapper
327
- ├── AudioEqualizer.tsx # Frequency bars
328
- ├── AudioShortcutsPopover.tsx # Shortcuts help
329
- │ ├── VisualizationToggle.tsx # Effect toggle button
330
- │ └── ReactiveCover/
331
- │ ├── index.ts
332
- │ ├── AudioReactiveCover.tsx # Reactive album art
333
- │ └── effects/ # Effect components
334
- │ ├── GlowEffect.tsx
335
- │ ├── OrbsEffect.tsx
336
- │ ├── SpotlightEffect.tsx
337
- │ └── MeshEffect.tsx
338
- └── effects/
339
- └── index.ts # Effect calculations
195
+ │ ├── HybridAudioPlayer.tsx # Main player component
196
+ │ ├── HybridSimplePlayer.tsx # All-in-one wrapper
197
+ │ ├── HybridWaveform.tsx # Frequency visualization
198
+ └── ReactiveCover/ # Reactive effects
199
+ ├── effects/ # Effect calculations
200
+ └── utils/ # Utilities
340
201
  ```
202
+
203
+ Key design:
204
+ - HTML5 `<audio>` for playback (native streaming, no crackling)
205
+ - Web Audio API AnalyserNode for visualization only (not connected to output)
206
+ - Audio graph: `source → destination` + `source → analyser` (passive)
@@ -0,0 +1,216 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * HybridAudioPlayer - Audio playback controls with frequency visualization
5
+ *
6
+ * Uses HybridAudioContext for state management.
7
+ * Native HTML5 audio for playback, Web Audio API for visualization only.
8
+ *
9
+ * Features:
10
+ * - Real-time frequency visualization
11
+ * - Playback controls (play, pause, skip, restart)
12
+ * - Volume control with mute
13
+ * - Loop toggle
14
+ * - Keyboard shortcuts
15
+ * - Timer display
16
+ */
17
+
18
+ import { memo } from 'react';
19
+ import {
20
+ Play,
21
+ Pause,
22
+ RotateCcw,
23
+ SkipBack,
24
+ SkipForward,
25
+ Volume2,
26
+ VolumeX,
27
+ Loader2,
28
+ Repeat,
29
+ } from 'lucide-react';
30
+ import { Button, Slider, cn } from '@djangocfg/ui-nextjs';
31
+ import { useHybridAudioContext } from '../context/HybridAudioProvider';
32
+ import { HybridWaveform } from './HybridWaveform';
33
+ import { formatTime } from '../utils';
34
+
35
+ // =============================================================================
36
+ // TYPES
37
+ // =============================================================================
38
+
39
+ export interface HybridAudioPlayerProps {
40
+ /** Show playback controls */
41
+ showControls?: boolean;
42
+ /** Show frequency waveform */
43
+ showWaveform?: boolean;
44
+ /** Waveform visualization mode */
45
+ waveformMode?: 'frequency' | 'static';
46
+ /** Waveform height in pixels */
47
+ waveformHeight?: number;
48
+ /** Show time display */
49
+ showTimer?: boolean;
50
+ /** Show volume control */
51
+ showVolume?: boolean;
52
+ /** Show loop button */
53
+ showLoop?: boolean;
54
+ /** Additional CSS class */
55
+ className?: string;
56
+ /** Inline styles */
57
+ style?: React.CSSProperties;
58
+ }
59
+
60
+ // =============================================================================
61
+ // COMPONENT
62
+ // =============================================================================
63
+
64
+ export const HybridAudioPlayer = memo(function HybridAudioPlayer({
65
+ showControls = true,
66
+ showWaveform = true,
67
+ waveformMode = 'frequency',
68
+ waveformHeight = 64,
69
+ showTimer = true,
70
+ showVolume = true,
71
+ showLoop = true,
72
+ className,
73
+ style,
74
+ }: HybridAudioPlayerProps) {
75
+ const { state, controls } = useHybridAudioContext();
76
+
77
+ const isLoading = !state.isReady;
78
+
79
+ const handleVolumeChange = (value: number[]) => {
80
+ controls.setVolume(value[0] / 100);
81
+ };
82
+
83
+ return (
84
+ <div
85
+ className={cn('flex flex-col gap-3 p-4 rounded-lg bg-card border', className)}
86
+ style={style}
87
+ >
88
+ {/* Frequency Waveform */}
89
+ {showWaveform && (
90
+ <div className="relative">
91
+ <HybridWaveform
92
+ mode={waveformMode}
93
+ height={waveformHeight}
94
+ className={cn(isLoading && 'opacity-50')}
95
+ />
96
+ {isLoading && (
97
+ <div className="absolute inset-0 flex items-center justify-center">
98
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
99
+ </div>
100
+ )}
101
+ </div>
102
+ )}
103
+
104
+ {/* Timer */}
105
+ {showTimer && (
106
+ <div className="flex justify-between text-xs text-muted-foreground tabular-nums px-1">
107
+ <span>{formatTime(state.currentTime)}</span>
108
+ <span>{formatTime(state.duration)}</span>
109
+ </div>
110
+ )}
111
+
112
+ {/* Controls */}
113
+ {showControls && (
114
+ <div className="flex items-center justify-center gap-1">
115
+ {/* Restart */}
116
+ <Button
117
+ variant="ghost"
118
+ size="icon"
119
+ className="h-9 w-9"
120
+ onClick={controls.restart}
121
+ disabled={!state.isReady}
122
+ title="Restart"
123
+ >
124
+ <RotateCcw className="h-4 w-4" />
125
+ </Button>
126
+
127
+ {/* Skip back 5s */}
128
+ <Button
129
+ variant="ghost"
130
+ size="icon"
131
+ className="h-9 w-9"
132
+ onClick={() => controls.skip(-5)}
133
+ disabled={!state.isReady}
134
+ title="Back 5 seconds"
135
+ >
136
+ <SkipBack className="h-4 w-4" />
137
+ </Button>
138
+
139
+ {/* Play/Pause */}
140
+ <Button
141
+ variant="default"
142
+ size="icon"
143
+ className="h-12 w-12 rounded-full"
144
+ onClick={controls.togglePlay}
145
+ disabled={!state.isReady && !isLoading}
146
+ title={state.isPlaying ? 'Pause' : 'Play'}
147
+ >
148
+ {isLoading ? (
149
+ <Loader2 className="h-5 w-5 animate-spin" />
150
+ ) : state.isPlaying ? (
151
+ <Pause className="h-5 w-5" />
152
+ ) : (
153
+ <Play className="h-5 w-5 ml-0.5" />
154
+ )}
155
+ </Button>
156
+
157
+ {/* Skip forward 5s */}
158
+ <Button
159
+ variant="ghost"
160
+ size="icon"
161
+ className="h-9 w-9"
162
+ onClick={() => controls.skip(5)}
163
+ disabled={!state.isReady}
164
+ title="Forward 5 seconds"
165
+ >
166
+ <SkipForward className="h-4 w-4" />
167
+ </Button>
168
+
169
+ {/* Volume */}
170
+ {showVolume && (
171
+ <>
172
+ <Button
173
+ variant="ghost"
174
+ size="icon"
175
+ className="h-9 w-9"
176
+ onClick={controls.toggleMute}
177
+ title={state.isMuted ? 'Unmute' : 'Mute'}
178
+ >
179
+ {state.isMuted || state.volume === 0 ? (
180
+ <VolumeX className="h-4 w-4" />
181
+ ) : (
182
+ <Volume2 className="h-4 w-4" />
183
+ )}
184
+ </Button>
185
+
186
+ <Slider
187
+ value={[state.isMuted ? 0 : state.volume * 100]}
188
+ max={100}
189
+ step={1}
190
+ onValueChange={handleVolumeChange}
191
+ className="w-20"
192
+ aria-label="Volume"
193
+ />
194
+ </>
195
+ )}
196
+
197
+ {/* Loop/Repeat */}
198
+ {showLoop && (
199
+ <Button
200
+ variant="ghost"
201
+ size="icon"
202
+ className={cn('h-9 w-9', state.isLooping && 'text-primary')}
203
+ onClick={controls.toggleLoop}
204
+ disabled={!state.isReady}
205
+ title={state.isLooping ? 'Disable loop' : 'Enable loop'}
206
+ >
207
+ <Repeat className="h-4 w-4" />
208
+ </Button>
209
+ )}
210
+ </div>
211
+ )}
212
+ </div>
213
+ );
214
+ });
215
+
216
+ export default HybridAudioPlayer;