@djangocfg/ui-nextjs 2.1.65 → 2.1.67
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 +13 -8
- package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
- package/src/stores/index.ts +8 -0
- package/src/stores/mediaCache.ts +464 -0
- package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +148 -0
- package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +301 -0
- package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +281 -0
- package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +328 -0
- package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +251 -0
- package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +427 -0
- package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +193 -0
- package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +146 -0
- package/src/tools/AudioPlayer/README.md +325 -0
- package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +200 -0
- package/src/tools/AudioPlayer/components/AudioPlayer.tsx +231 -0
- package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +99 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +147 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
- package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
- package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +280 -0
- package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +64 -0
- package/src/tools/AudioPlayer/components/index.ts +21 -0
- package/src/tools/AudioPlayer/context/AudioProvider.tsx +292 -0
- package/src/tools/AudioPlayer/context/index.ts +11 -0
- package/src/tools/AudioPlayer/context/selectors.ts +96 -0
- package/src/tools/AudioPlayer/effects/index.ts +412 -0
- package/src/tools/AudioPlayer/hooks/index.ts +29 -0
- package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +110 -0
- package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +149 -0
- package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +106 -0
- package/src/tools/AudioPlayer/hooks/useVisualization.tsx +201 -0
- package/src/tools/AudioPlayer/index.ts +139 -0
- package/src/tools/AudioPlayer/types/audio.ts +107 -0
- package/src/tools/AudioPlayer/types/components.ts +98 -0
- package/src/tools/AudioPlayer/types/effects.ts +73 -0
- package/src/tools/AudioPlayer/types/index.ts +35 -0
- package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
- package/src/tools/AudioPlayer/utils/index.ts +5 -0
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
- package/src/tools/ImageViewer/README.md +174 -0
- package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +150 -0
- package/src/tools/ImageViewer/components/ImageViewer.tsx +235 -0
- package/src/tools/ImageViewer/components/index.ts +7 -0
- package/src/tools/ImageViewer/hooks/index.ts +9 -0
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +153 -0
- package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
- package/src/tools/ImageViewer/index.ts +60 -0
- package/src/tools/ImageViewer/types.ts +75 -0
- package/src/tools/ImageViewer/utils/constants.ts +59 -0
- package/src/tools/ImageViewer/utils/index.ts +16 -0
- package/src/tools/ImageViewer/utils/lqip.ts +47 -0
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
- package/src/tools/VideoPlayer/README.md +212 -187
- package/src/tools/VideoPlayer/{VideoControls.tsx → components/VideoControls.tsx} +8 -9
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +174 -0
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
- package/src/tools/VideoPlayer/components/index.ts +14 -0
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
- package/src/tools/VideoPlayer/context/index.ts +8 -0
- package/src/tools/VideoPlayer/hooks/index.ts +9 -0
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +109 -0
- package/src/tools/VideoPlayer/index.ts +70 -9
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +401 -0
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +332 -0
- package/src/tools/VideoPlayer/providers/index.ts +8 -0
- package/src/tools/VideoPlayer/types/index.ts +38 -0
- package/src/tools/VideoPlayer/types/player.ts +116 -0
- package/src/tools/VideoPlayer/types/provider.ts +93 -0
- package/src/tools/VideoPlayer/types/sources.ts +97 -0
- package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
- package/src/tools/VideoPlayer/utils/index.ts +11 -0
- package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
- package/src/tools/index.ts +92 -4
- package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
- package/src/tools/VideoPlayer/VideoPlayer.tsx +0 -231
- package/src/tools/VideoPlayer/types.ts +0 -118
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AudioProvider - Shared audio state and controls
|
|
5
|
+
*
|
|
6
|
+
* Provides centralized audio state management using WaveSurfer.js
|
|
7
|
+
* All child components can access playback state and controls
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
useRef,
|
|
13
|
+
useMemo,
|
|
14
|
+
useCallback,
|
|
15
|
+
useState,
|
|
16
|
+
useEffect,
|
|
17
|
+
type ReactNode,
|
|
18
|
+
} from 'react';
|
|
19
|
+
import { useWavesurfer } from '@wavesurfer/react';
|
|
20
|
+
import type { AudioContextState, AudioSource, WaveformOptions } from '../types';
|
|
21
|
+
import { useSharedWebAudio, useAudioAnalysis } from '../hooks';
|
|
22
|
+
import { useAudioCache } from '../../../stores/mediaCache';
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// CONTEXT
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
// Named AudioPlayerContext to avoid conflict with browser's AudioContext
|
|
29
|
+
export const AudioPlayerContext = createContext<AudioContextState | null>(null);
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// PROVIDER PROPS
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
interface AudioProviderProps {
|
|
36
|
+
/** Audio source */
|
|
37
|
+
source: AudioSource;
|
|
38
|
+
/** Auto-play when loaded */
|
|
39
|
+
autoPlay?: boolean;
|
|
40
|
+
/** WaveSurfer options */
|
|
41
|
+
waveformOptions?: WaveformOptions;
|
|
42
|
+
/** Container ref for waveform rendering */
|
|
43
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
44
|
+
/** Children components */
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// PROVIDER
|
|
50
|
+
// =============================================================================
|
|
51
|
+
|
|
52
|
+
export function AudioProvider({
|
|
53
|
+
source,
|
|
54
|
+
autoPlay = false,
|
|
55
|
+
waveformOptions = {},
|
|
56
|
+
containerRef,
|
|
57
|
+
children,
|
|
58
|
+
}: AudioProviderProps) {
|
|
59
|
+
// Cache for playback position persistence
|
|
60
|
+
const { saveAudioPosition, getAudioPosition } = useAudioCache();
|
|
61
|
+
const lastSavedTimeRef = useRef<number>(0);
|
|
62
|
+
|
|
63
|
+
// Memoize WaveSurfer options with theme-aware colors
|
|
64
|
+
const options = useMemo(
|
|
65
|
+
() => ({
|
|
66
|
+
container: containerRef,
|
|
67
|
+
url: source.uri,
|
|
68
|
+
// Theme-aware colors using HSL
|
|
69
|
+
waveColor: waveformOptions.waveColor || 'hsl(217 91% 60% / 0.3)',
|
|
70
|
+
progressColor: waveformOptions.progressColor || 'hsl(217 91% 60%)',
|
|
71
|
+
cursorColor: waveformOptions.cursorColor || 'hsl(217 91% 60%)',
|
|
72
|
+
cursorWidth: waveformOptions.cursorWidth ?? 2,
|
|
73
|
+
height: waveformOptions.height ?? 64,
|
|
74
|
+
barWidth: waveformOptions.barWidth ?? 3,
|
|
75
|
+
barRadius: waveformOptions.barRadius ?? 3,
|
|
76
|
+
barGap: waveformOptions.barGap ?? 2,
|
|
77
|
+
normalize: true,
|
|
78
|
+
interact: true,
|
|
79
|
+
hideScrollbar: true,
|
|
80
|
+
autoplay: autoPlay,
|
|
81
|
+
}),
|
|
82
|
+
[source.uri, autoPlay, waveformOptions, containerRef]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Use official wavesurfer-react hook
|
|
86
|
+
const { wavesurfer, isReady, isPlaying, currentTime } = useWavesurfer(options);
|
|
87
|
+
|
|
88
|
+
// Restore cached playback position when ready
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (isReady && wavesurfer && source.uri) {
|
|
91
|
+
const cachedPosition = getAudioPosition(source.uri);
|
|
92
|
+
if (cachedPosition && cachedPosition > 0) {
|
|
93
|
+
const duration = wavesurfer.getDuration();
|
|
94
|
+
// Only restore if position is valid (not at the end)
|
|
95
|
+
if (cachedPosition < duration - 1) {
|
|
96
|
+
wavesurfer.setTime(cachedPosition);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}, [isReady, wavesurfer, source.uri, getAudioPosition]);
|
|
101
|
+
|
|
102
|
+
// Save playback position periodically and on pause
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!source.uri) return;
|
|
105
|
+
|
|
106
|
+
// Save position every 5 seconds during playback
|
|
107
|
+
if (isPlaying && currentTime > 0) {
|
|
108
|
+
const timeSinceLastSave = currentTime - lastSavedTimeRef.current;
|
|
109
|
+
if (timeSinceLastSave >= 5 || timeSinceLastSave < 0) {
|
|
110
|
+
saveAudioPosition(source.uri, currentTime);
|
|
111
|
+
lastSavedTimeRef.current = currentTime;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Save position immediately when paused
|
|
116
|
+
if (!isPlaying && currentTime > 0) {
|
|
117
|
+
saveAudioPosition(source.uri, currentTime);
|
|
118
|
+
lastSavedTimeRef.current = currentTime;
|
|
119
|
+
}
|
|
120
|
+
}, [isPlaying, currentTime, source.uri, saveAudioPosition]);
|
|
121
|
+
|
|
122
|
+
// Derived state
|
|
123
|
+
const duration = wavesurfer?.getDuration() ?? 0;
|
|
124
|
+
const volume = wavesurfer?.getVolume() ?? 1;
|
|
125
|
+
const isMuted = wavesurfer?.getMuted() ?? false;
|
|
126
|
+
|
|
127
|
+
// Loop state
|
|
128
|
+
const [isLooping, setIsLooping] = useState(false);
|
|
129
|
+
|
|
130
|
+
// Handle loop: restart playback when audio finishes
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (!wavesurfer) return;
|
|
133
|
+
|
|
134
|
+
const handleFinish = () => {
|
|
135
|
+
if (isLooping) {
|
|
136
|
+
wavesurfer.seekTo(0);
|
|
137
|
+
wavesurfer.play();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
wavesurfer.on('finish', handleFinish);
|
|
142
|
+
return () => {
|
|
143
|
+
wavesurfer.un('finish', handleFinish);
|
|
144
|
+
};
|
|
145
|
+
}, [wavesurfer, isLooping]);
|
|
146
|
+
|
|
147
|
+
// Get audio element for equalizer
|
|
148
|
+
const audioElement = useMemo(() => {
|
|
149
|
+
return wavesurfer?.getMediaElement() ?? null;
|
|
150
|
+
}, [wavesurfer]);
|
|
151
|
+
|
|
152
|
+
// Shared Web Audio context for all analyzers (prevents duplicate source nodes)
|
|
153
|
+
const sharedAudio = useSharedWebAudio(audioElement);
|
|
154
|
+
|
|
155
|
+
// Audio analysis for reactive effects (uses shared context)
|
|
156
|
+
const audioLevels = useAudioAnalysis(sharedAudio, isPlaying);
|
|
157
|
+
|
|
158
|
+
// Actions
|
|
159
|
+
const play = useCallback(async () => {
|
|
160
|
+
await wavesurfer?.play();
|
|
161
|
+
}, [wavesurfer]);
|
|
162
|
+
|
|
163
|
+
const pause = useCallback(() => {
|
|
164
|
+
wavesurfer?.pause();
|
|
165
|
+
}, [wavesurfer]);
|
|
166
|
+
|
|
167
|
+
const togglePlay = useCallback(() => {
|
|
168
|
+
wavesurfer?.playPause();
|
|
169
|
+
}, [wavesurfer]);
|
|
170
|
+
|
|
171
|
+
const seek = useCallback(
|
|
172
|
+
(time: number) => {
|
|
173
|
+
if (wavesurfer) {
|
|
174
|
+
const clampedTime = Math.max(0, Math.min(time, duration));
|
|
175
|
+
wavesurfer.setTime(clampedTime);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
[wavesurfer, duration]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const seekTo = useCallback(
|
|
182
|
+
(progress: number) => {
|
|
183
|
+
if (wavesurfer) {
|
|
184
|
+
const clampedProgress = Math.max(0, Math.min(progress, 1));
|
|
185
|
+
wavesurfer.seekTo(clampedProgress);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
[wavesurfer]
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const skip = useCallback(
|
|
192
|
+
(seconds: number) => {
|
|
193
|
+
wavesurfer?.skip(seconds);
|
|
194
|
+
},
|
|
195
|
+
[wavesurfer]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const setVolume = useCallback(
|
|
199
|
+
(vol: number) => {
|
|
200
|
+
if (wavesurfer) {
|
|
201
|
+
wavesurfer.setVolume(Math.max(0, Math.min(vol, 1)));
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
[wavesurfer]
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const toggleMute = useCallback(() => {
|
|
208
|
+
if (wavesurfer) {
|
|
209
|
+
wavesurfer.setMuted(!wavesurfer.getMuted());
|
|
210
|
+
}
|
|
211
|
+
}, [wavesurfer]);
|
|
212
|
+
|
|
213
|
+
const restart = useCallback(() => {
|
|
214
|
+
if (wavesurfer) {
|
|
215
|
+
wavesurfer.seekTo(0);
|
|
216
|
+
wavesurfer.play();
|
|
217
|
+
}
|
|
218
|
+
}, [wavesurfer]);
|
|
219
|
+
|
|
220
|
+
const toggleLoop = useCallback(() => {
|
|
221
|
+
setIsLooping((prev) => !prev);
|
|
222
|
+
}, []);
|
|
223
|
+
|
|
224
|
+
const setLoop = useCallback((enabled: boolean) => {
|
|
225
|
+
setIsLooping(enabled);
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
// Context value
|
|
229
|
+
const contextValue = useMemo<AudioContextState>(
|
|
230
|
+
() => ({
|
|
231
|
+
// Core instances
|
|
232
|
+
wavesurfer,
|
|
233
|
+
audioElement,
|
|
234
|
+
sharedAudio,
|
|
235
|
+
|
|
236
|
+
// Playback state
|
|
237
|
+
isReady,
|
|
238
|
+
isPlaying,
|
|
239
|
+
currentTime,
|
|
240
|
+
duration,
|
|
241
|
+
volume,
|
|
242
|
+
isMuted,
|
|
243
|
+
isLooping,
|
|
244
|
+
|
|
245
|
+
// Audio analysis
|
|
246
|
+
audioLevels,
|
|
247
|
+
|
|
248
|
+
// Actions
|
|
249
|
+
play,
|
|
250
|
+
pause,
|
|
251
|
+
togglePlay,
|
|
252
|
+
seek,
|
|
253
|
+
seekTo,
|
|
254
|
+
skip,
|
|
255
|
+
setVolume,
|
|
256
|
+
toggleMute,
|
|
257
|
+
toggleLoop,
|
|
258
|
+
setLoop,
|
|
259
|
+
restart,
|
|
260
|
+
}),
|
|
261
|
+
[
|
|
262
|
+
wavesurfer,
|
|
263
|
+
audioElement,
|
|
264
|
+
sharedAudio,
|
|
265
|
+
isReady,
|
|
266
|
+
isPlaying,
|
|
267
|
+
currentTime,
|
|
268
|
+
duration,
|
|
269
|
+
volume,
|
|
270
|
+
isMuted,
|
|
271
|
+
isLooping,
|
|
272
|
+
audioLevels,
|
|
273
|
+
play,
|
|
274
|
+
pause,
|
|
275
|
+
togglePlay,
|
|
276
|
+
seek,
|
|
277
|
+
seekTo,
|
|
278
|
+
skip,
|
|
279
|
+
setVolume,
|
|
280
|
+
toggleMute,
|
|
281
|
+
toggleLoop,
|
|
282
|
+
setLoop,
|
|
283
|
+
restart,
|
|
284
|
+
]
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<AudioPlayerContext.Provider value={contextValue}>
|
|
289
|
+
{children}
|
|
290
|
+
</AudioPlayerContext.Provider>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer context - Public API
|
|
3
|
+
*
|
|
4
|
+
* Re-exports provider and selector hooks
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Provider
|
|
8
|
+
export { AudioProvider, AudioPlayerContext } from './AudioProvider';
|
|
9
|
+
|
|
10
|
+
// Selector hooks
|
|
11
|
+
export { useAudio, useAudioControls, useAudioState, useAudioElement } from './selectors';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context selectors - Performance-optimized hooks for accessing audio state
|
|
5
|
+
*
|
|
6
|
+
* These hooks provide selective access to context values,
|
|
7
|
+
* helping to minimize unnecessary re-renders.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useContext } from 'react';
|
|
11
|
+
import { AudioPlayerContext } from './AudioProvider';
|
|
12
|
+
import type { AudioContextState } from '../types';
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// MAIN HOOK
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Access full audio context state
|
|
20
|
+
* @throws Error if used outside AudioProvider
|
|
21
|
+
*/
|
|
22
|
+
export function useAudio(): AudioContextState {
|
|
23
|
+
const context = useContext(AudioPlayerContext);
|
|
24
|
+
if (!context) {
|
|
25
|
+
throw new Error('useAudio must be used within an AudioProvider');
|
|
26
|
+
}
|
|
27
|
+
return context;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// SELECTIVE HOOKS - for performance optimization
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook for playback controls only (no re-render on time updates)
|
|
36
|
+
*/
|
|
37
|
+
export function useAudioControls() {
|
|
38
|
+
const {
|
|
39
|
+
isReady,
|
|
40
|
+
play,
|
|
41
|
+
pause,
|
|
42
|
+
togglePlay,
|
|
43
|
+
skip,
|
|
44
|
+
restart,
|
|
45
|
+
setVolume,
|
|
46
|
+
toggleMute,
|
|
47
|
+
toggleLoop,
|
|
48
|
+
setLoop,
|
|
49
|
+
} = useAudio();
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isReady,
|
|
53
|
+
play,
|
|
54
|
+
pause,
|
|
55
|
+
togglePlay,
|
|
56
|
+
skip,
|
|
57
|
+
restart,
|
|
58
|
+
setVolume,
|
|
59
|
+
toggleMute,
|
|
60
|
+
toggleLoop,
|
|
61
|
+
setLoop,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Hook for playback state (read-only)
|
|
67
|
+
*/
|
|
68
|
+
export function useAudioState() {
|
|
69
|
+
const {
|
|
70
|
+
isReady,
|
|
71
|
+
isPlaying,
|
|
72
|
+
currentTime,
|
|
73
|
+
duration,
|
|
74
|
+
volume,
|
|
75
|
+
isMuted,
|
|
76
|
+
isLooping,
|
|
77
|
+
} = useAudio();
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
isReady,
|
|
81
|
+
isPlaying,
|
|
82
|
+
currentTime,
|
|
83
|
+
duration,
|
|
84
|
+
volume,
|
|
85
|
+
isMuted,
|
|
86
|
+
isLooping,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Hook for audio element access (for equalizer and reactive effects)
|
|
92
|
+
*/
|
|
93
|
+
export function useAudioElement() {
|
|
94
|
+
const { audioElement, sharedAudio, isPlaying, audioLevels } = useAudio();
|
|
95
|
+
return { audioElement, sharedAudio, isPlaying, audioLevels };
|
|
96
|
+
}
|