@djangocfg/ui-nextjs 2.1.66 → 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 +8 -6
- 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 +35 -11
- package/src/tools/AudioPlayer/{AudioEqualizer.tsx → components/AudioEqualizer.tsx} +29 -64
- package/src/tools/AudioPlayer/{AudioPlayer.tsx → components/AudioPlayer.tsx} +22 -14
- package/src/tools/AudioPlayer/{AudioShortcutsPopover.tsx → components/AudioShortcutsPopover.tsx} +6 -2
- 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/{SimpleAudioPlayer.tsx → components/SimpleAudioPlayer.tsx} +12 -7
- package/src/tools/AudioPlayer/{VisualizationToggle.tsx → components/VisualizationToggle.tsx} +2 -6
- 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/hooks/index.ts +29 -0
- package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +110 -0
- package/src/tools/AudioPlayer/{useAudioHotkeys.ts → hooks/useAudioHotkeys.ts} +11 -4
- package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +106 -0
- package/src/tools/AudioPlayer/{useAudioVisualization.tsx → hooks/useVisualization.tsx} +11 -5
- package/src/tools/AudioPlayer/index.ts +104 -49
- package/src/tools/AudioPlayer/types/audio.ts +107 -0
- package/src/tools/AudioPlayer/{types.ts → types/components.ts} +20 -84
- 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 +16 -3
- 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 +47 -3
- 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 +26 -10
- package/src/tools/VideoPlayer/{VideoControls.tsx → components/VideoControls.tsx} +8 -9
- package/src/tools/VideoPlayer/{VideoErrorFallback.tsx → components/VideoErrorFallback.tsx} +2 -2
- package/src/tools/VideoPlayer/{VideoPlayer.tsx → components/VideoPlayer.tsx} +4 -5
- 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 +29 -20
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +118 -28
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +89 -11
- 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 +10 -0
- package/src/tools/AudioPlayer/AudioReactiveCover.tsx +0 -389
- package/src/tools/AudioPlayer/context.tsx +0 -426
- package/src/tools/ImageViewer/ImageViewer.tsx +0 -416
- package/src/tools/VideoPlayer/VideoPlayerContext.tsx +0 -125
- package/src/tools/VideoPlayer/types.ts +0 -367
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Phase 2: Utils Extraction
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Extract helper functions from types.ts into dedicated utils files.
|
|
6
|
+
|
|
7
|
+
## Files to Create
|
|
8
|
+
|
|
9
|
+
### utils/resolvers.ts
|
|
10
|
+
|
|
11
|
+
Player mode and source resolution:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
/**
|
|
15
|
+
* Video source and player mode resolvers
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { VideoSourceUnion, PlayerMode, SimpleStreamSource, StreamSource } from '../types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Determine the appropriate player mode for a given source
|
|
22
|
+
*/
|
|
23
|
+
export function resolvePlayerMode(source: VideoSourceUnion): PlayerMode {
|
|
24
|
+
switch (source.type) {
|
|
25
|
+
case 'youtube':
|
|
26
|
+
case 'vimeo':
|
|
27
|
+
case 'hls':
|
|
28
|
+
case 'dash':
|
|
29
|
+
return 'vidstack';
|
|
30
|
+
case 'stream':
|
|
31
|
+
case 'blob':
|
|
32
|
+
case 'data-url':
|
|
33
|
+
return 'streaming';
|
|
34
|
+
case 'url':
|
|
35
|
+
default:
|
|
36
|
+
return 'native';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if source is a simple stream (needs context resolution)
|
|
42
|
+
*/
|
|
43
|
+
export function isSimpleStreamSource(
|
|
44
|
+
source: VideoSourceUnion
|
|
45
|
+
): source is SimpleStreamSource {
|
|
46
|
+
return (
|
|
47
|
+
source.type === 'stream' &&
|
|
48
|
+
!('getStreamUrl' in source)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve simple stream source with context getStreamUrl
|
|
54
|
+
*/
|
|
55
|
+
export function resolveStreamSource(
|
|
56
|
+
source: SimpleStreamSource,
|
|
57
|
+
getStreamUrl: (streamKey: string) => Promise<string>
|
|
58
|
+
): StreamSource {
|
|
59
|
+
return {
|
|
60
|
+
...source,
|
|
61
|
+
getStreamUrl,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### utils/fileSource.ts
|
|
67
|
+
|
|
68
|
+
File content to source conversion:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
/**
|
|
72
|
+
* File source resolution utilities
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
import type { VideoSourceUnion, ResolveFileSourceOptions } from '../types';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Convert file content to appropriate VideoSource
|
|
79
|
+
*/
|
|
80
|
+
export function resolveFileSource(options: ResolveFileSourceOptions): VideoSourceUnion {
|
|
81
|
+
const { fileName, content, mimeType } = options;
|
|
82
|
+
|
|
83
|
+
// Data URL string
|
|
84
|
+
if (typeof content === 'string' && content.startsWith('data:')) {
|
|
85
|
+
return {
|
|
86
|
+
type: 'data-url',
|
|
87
|
+
dataUrl: content,
|
|
88
|
+
mimeType,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Regular URL string
|
|
93
|
+
if (typeof content === 'string') {
|
|
94
|
+
return {
|
|
95
|
+
type: 'url',
|
|
96
|
+
url: content,
|
|
97
|
+
mimeType,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ArrayBuffer -> Blob source
|
|
102
|
+
return {
|
|
103
|
+
type: 'blob',
|
|
104
|
+
data: content,
|
|
105
|
+
mimeType: mimeType || guessMimeType(fileName),
|
|
106
|
+
fileName,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Guess MIME type from file extension
|
|
112
|
+
*/
|
|
113
|
+
function guessMimeType(fileName: string): string {
|
|
114
|
+
const ext = fileName.split('.').pop()?.toLowerCase();
|
|
115
|
+
const mimeTypes: Record<string, string> = {
|
|
116
|
+
mp4: 'video/mp4',
|
|
117
|
+
webm: 'video/webm',
|
|
118
|
+
ogg: 'video/ogg',
|
|
119
|
+
mov: 'video/quicktime',
|
|
120
|
+
avi: 'video/x-msvideo',
|
|
121
|
+
mkv: 'video/x-matroska',
|
|
122
|
+
};
|
|
123
|
+
return mimeTypes[ext || ''] || 'video/mp4';
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### utils/index.ts
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
/**
|
|
131
|
+
* VideoPlayer utilities - Public API
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
resolvePlayerMode,
|
|
136
|
+
isSimpleStreamSource,
|
|
137
|
+
resolveStreamSource,
|
|
138
|
+
} from './resolvers';
|
|
139
|
+
|
|
140
|
+
export { resolveFileSource } from './fileSource';
|
|
141
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Phase 3: Hooks Extraction
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Extract duplicated video position caching logic into a reusable hook.
|
|
6
|
+
|
|
7
|
+
## Current Duplication
|
|
8
|
+
|
|
9
|
+
The position caching pattern is duplicated in:
|
|
10
|
+
1. `VidstackProvider.tsx` (lines ~180-220)
|
|
11
|
+
2. `StreamProvider.tsx` (lines ~200-240)
|
|
12
|
+
|
|
13
|
+
Both implement:
|
|
14
|
+
- Save position every 5 seconds during playback
|
|
15
|
+
- Restore position when video loads (canPlay)
|
|
16
|
+
- Clear position when video ends
|
|
17
|
+
|
|
18
|
+
## File to Create
|
|
19
|
+
|
|
20
|
+
### hooks/useVideoPositionCache.ts
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
'use client';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* useVideoPositionCache - Manages video playback position caching
|
|
27
|
+
*
|
|
28
|
+
* Saves position periodically during playback and restores on load.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
32
|
+
import { useVideoCache } from '../../../stores/mediaCache';
|
|
33
|
+
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// TYPES
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
export interface UseVideoPositionCacheOptions {
|
|
39
|
+
/** Unique key for caching (e.g., video URL or stream key) */
|
|
40
|
+
cacheKey: string;
|
|
41
|
+
/** Current playback time in seconds */
|
|
42
|
+
currentTime: number;
|
|
43
|
+
/** Video duration in seconds */
|
|
44
|
+
duration: number;
|
|
45
|
+
/** Whether video is currently playing */
|
|
46
|
+
isPlaying: boolean;
|
|
47
|
+
/** Whether video is ready to play */
|
|
48
|
+
isReady: boolean;
|
|
49
|
+
/** Callback to seek to a specific time */
|
|
50
|
+
onSeek: (time: number) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface UseVideoPositionCacheReturn {
|
|
54
|
+
/** Manually save current position */
|
|
55
|
+
savePosition: () => void;
|
|
56
|
+
/** Clear saved position */
|
|
57
|
+
clearPosition: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// CONSTANTS
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
/** Save interval in seconds */
|
|
65
|
+
const SAVE_INTERVAL = 5;
|
|
66
|
+
|
|
67
|
+
/** Minimum position to restore (avoid 0 or near-end) */
|
|
68
|
+
const MIN_RESTORE_POSITION = 1;
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// HOOK
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
export function useVideoPositionCache(
|
|
75
|
+
options: UseVideoPositionCacheOptions
|
|
76
|
+
): UseVideoPositionCacheReturn {
|
|
77
|
+
const { cacheKey, currentTime, duration, isPlaying, isReady, onSeek } = options;
|
|
78
|
+
|
|
79
|
+
const { saveVideoPosition, getVideoPosition } = useVideoCache();
|
|
80
|
+
const lastSavedTimeRef = useRef<number>(0);
|
|
81
|
+
const hasRestoredRef = useRef<boolean>(false);
|
|
82
|
+
|
|
83
|
+
// Restore position when ready
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!isReady || !cacheKey || hasRestoredRef.current) return;
|
|
86
|
+
|
|
87
|
+
const savedPosition = getVideoPosition(cacheKey);
|
|
88
|
+
if (savedPosition && savedPosition > MIN_RESTORE_POSITION && savedPosition < duration - 1) {
|
|
89
|
+
onSeek(savedPosition);
|
|
90
|
+
}
|
|
91
|
+
hasRestoredRef.current = true;
|
|
92
|
+
}, [isReady, cacheKey, duration, getVideoPosition, onSeek]);
|
|
93
|
+
|
|
94
|
+
// Reset restored flag when cache key changes
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
hasRestoredRef.current = false;
|
|
97
|
+
lastSavedTimeRef.current = 0;
|
|
98
|
+
}, [cacheKey]);
|
|
99
|
+
|
|
100
|
+
// Save position periodically during playback
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!cacheKey || !isPlaying || currentTime <= 0) return;
|
|
103
|
+
|
|
104
|
+
const timeSinceLastSave = currentTime - lastSavedTimeRef.current;
|
|
105
|
+
if (timeSinceLastSave >= SAVE_INTERVAL || timeSinceLastSave < 0) {
|
|
106
|
+
saveVideoPosition(cacheKey, currentTime);
|
|
107
|
+
lastSavedTimeRef.current = currentTime;
|
|
108
|
+
}
|
|
109
|
+
}, [cacheKey, isPlaying, currentTime, saveVideoPosition]);
|
|
110
|
+
|
|
111
|
+
// Save position on pause
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!cacheKey || isPlaying || currentTime <= 0) return;
|
|
114
|
+
|
|
115
|
+
saveVideoPosition(cacheKey, currentTime);
|
|
116
|
+
lastSavedTimeRef.current = currentTime;
|
|
117
|
+
}, [cacheKey, isPlaying, currentTime, saveVideoPosition]);
|
|
118
|
+
|
|
119
|
+
const savePosition = useCallback(() => {
|
|
120
|
+
if (cacheKey && currentTime > 0) {
|
|
121
|
+
saveVideoPosition(cacheKey, currentTime);
|
|
122
|
+
}
|
|
123
|
+
}, [cacheKey, currentTime, saveVideoPosition]);
|
|
124
|
+
|
|
125
|
+
const clearPosition = useCallback(() => {
|
|
126
|
+
if (cacheKey) {
|
|
127
|
+
saveVideoPosition(cacheKey, 0);
|
|
128
|
+
}
|
|
129
|
+
}, [cacheKey, saveVideoPosition]);
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
savePosition,
|
|
133
|
+
clearPosition,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### hooks/index.ts
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
/**
|
|
142
|
+
* VideoPlayer hooks - Public API
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
export { useVideoPositionCache } from './useVideoPositionCache';
|
|
146
|
+
export type {
|
|
147
|
+
UseVideoPositionCacheOptions,
|
|
148
|
+
UseVideoPositionCacheReturn,
|
|
149
|
+
} from './useVideoPositionCache';
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Usage in Providers
|
|
153
|
+
|
|
154
|
+
After extraction, providers can use:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// In VidstackProvider.tsx
|
|
158
|
+
const { clearPosition } = useVideoPositionCache({
|
|
159
|
+
cacheKey: sourceKey,
|
|
160
|
+
currentTime: player?.currentTime ?? 0,
|
|
161
|
+
duration: player?.duration ?? 0,
|
|
162
|
+
isPlaying: !paused,
|
|
163
|
+
isReady: canPlay,
|
|
164
|
+
onSeek: (time) => player?.currentTime = time,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Clear on video end
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (ended) clearPosition();
|
|
170
|
+
}, [ended, clearPosition]);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Benefits
|
|
174
|
+
|
|
175
|
+
1. **DRY** - Single source of truth for position caching logic
|
|
176
|
+
2. **Testable** - Hook can be unit tested independently
|
|
177
|
+
3. **Reusable** - Can be used in future video components
|
|
178
|
+
4. **Maintainable** - Changes only need to be made in one place
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Phase 4: Components Organization
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Move standalone components to a dedicated `components/` folder for consistency.
|
|
6
|
+
|
|
7
|
+
## Current Location
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
VideoPlayer/
|
|
11
|
+
├── VideoPlayer.tsx # Main orchestrator
|
|
12
|
+
├── VideoControls.tsx # Vidstack controls
|
|
13
|
+
├── VideoErrorFallback.tsx # Error UI
|
|
14
|
+
└── providers/ # Provider implementations
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Target Location
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
VideoPlayer/
|
|
21
|
+
├── components/
|
|
22
|
+
│ ├── index.ts
|
|
23
|
+
│ ├── VideoPlayer.tsx
|
|
24
|
+
│ ├── VideoControls.tsx
|
|
25
|
+
│ └── VideoErrorFallback.tsx
|
|
26
|
+
└── providers/ # Unchanged
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Files to Create
|
|
30
|
+
|
|
31
|
+
### components/index.ts
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
/**
|
|
35
|
+
* VideoPlayer components - Public API
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export { VideoPlayer } from './VideoPlayer';
|
|
39
|
+
export { VideoControls } from './VideoControls';
|
|
40
|
+
export {
|
|
41
|
+
VideoErrorFallback,
|
|
42
|
+
createVideoErrorFallback,
|
|
43
|
+
} from './VideoErrorFallback';
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Component Updates
|
|
47
|
+
|
|
48
|
+
### VideoPlayer.tsx
|
|
49
|
+
|
|
50
|
+
Update imports to use new paths:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Before
|
|
54
|
+
import { VidstackProvider } from './providers';
|
|
55
|
+
import { NativeProvider } from './providers';
|
|
56
|
+
import { StreamProvider } from './providers';
|
|
57
|
+
import { resolvePlayerMode, isSimpleStreamSource, resolveStreamSource } from './types';
|
|
58
|
+
import { useVideoPlayerContext } from './VideoPlayerContext';
|
|
59
|
+
|
|
60
|
+
// After
|
|
61
|
+
import { VidstackProvider, NativeProvider, StreamProvider } from '../providers';
|
|
62
|
+
import { resolvePlayerMode, isSimpleStreamSource, resolveStreamSource } from '../utils';
|
|
63
|
+
import { useVideoPlayerContext } from '../context';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### VideoControls.tsx
|
|
67
|
+
|
|
68
|
+
Update imports:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Before
|
|
72
|
+
import type { ... } from './types';
|
|
73
|
+
|
|
74
|
+
// After
|
|
75
|
+
import type { ... } from '../types';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### VideoErrorFallback.tsx
|
|
79
|
+
|
|
80
|
+
Update imports:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Before
|
|
84
|
+
import type { ErrorFallbackProps } from './types';
|
|
85
|
+
|
|
86
|
+
// After
|
|
87
|
+
import type { ErrorFallbackProps } from '../types';
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Notes
|
|
91
|
+
|
|
92
|
+
- VideoPlayer.tsx is the main entry point, moved to components/
|
|
93
|
+
- Providers stay in their own folder (already well-organized)
|
|
94
|
+
- Context moves to context/ folder
|
|
95
|
+
- All imports updated to reflect new structure
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Execution Checklist
|
|
2
|
+
|
|
3
|
+
## Pre-flight
|
|
4
|
+
|
|
5
|
+
- [ ] Read all current files
|
|
6
|
+
- [ ] Backup or rename original files
|
|
7
|
+
- [ ] Create folder structure
|
|
8
|
+
|
|
9
|
+
## Phase 1: Folder Structure
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd src/tools/VideoPlayer
|
|
13
|
+
mkdir -p types hooks utils components context
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- [ ] Create `types/` directory
|
|
17
|
+
- [ ] Create `hooks/` directory
|
|
18
|
+
- [ ] Create `utils/` directory
|
|
19
|
+
- [ ] Create `components/` directory
|
|
20
|
+
- [ ] Create `context/` directory
|
|
21
|
+
|
|
22
|
+
## Phase 2: Types
|
|
23
|
+
|
|
24
|
+
- [ ] Create `types/sources.ts` - Source type definitions
|
|
25
|
+
- [ ] Create `types/player.ts` - Player config and main types
|
|
26
|
+
- [ ] Create `types/provider.ts` - Provider-specific types
|
|
27
|
+
- [ ] Create `types/index.ts` - Re-exports
|
|
28
|
+
- [ ] Run `pnpm check`
|
|
29
|
+
|
|
30
|
+
## Phase 3: Utils
|
|
31
|
+
|
|
32
|
+
- [ ] Create `utils/resolvers.ts` - resolvePlayerMode, isSimpleStreamSource
|
|
33
|
+
- [ ] Create `utils/fileSource.ts` - resolveFileSource
|
|
34
|
+
- [ ] Create `utils/index.ts` - Re-exports
|
|
35
|
+
- [ ] Run `pnpm check`
|
|
36
|
+
|
|
37
|
+
## Phase 4: Hooks
|
|
38
|
+
|
|
39
|
+
- [ ] Create `hooks/useVideoPositionCache.ts` - Position caching logic
|
|
40
|
+
- [ ] Create `hooks/index.ts` - Re-exports
|
|
41
|
+
- [ ] Run `pnpm check`
|
|
42
|
+
|
|
43
|
+
## Phase 5: Components
|
|
44
|
+
|
|
45
|
+
- [ ] Move `VideoPlayer.tsx` to `components/`
|
|
46
|
+
- [ ] Move `VideoControls.tsx` to `components/`
|
|
47
|
+
- [ ] Move `VideoErrorFallback.tsx` to `components/`
|
|
48
|
+
- [ ] Create `components/index.ts`
|
|
49
|
+
- [ ] Update imports in all moved files
|
|
50
|
+
- [ ] Run `pnpm check`
|
|
51
|
+
|
|
52
|
+
## Phase 6: Context
|
|
53
|
+
|
|
54
|
+
- [ ] Move `VideoPlayerContext.tsx` to `context/`
|
|
55
|
+
- [ ] Create `context/index.ts`
|
|
56
|
+
- [ ] Update imports
|
|
57
|
+
- [ ] Run `pnpm check`
|
|
58
|
+
|
|
59
|
+
## Phase 7: Update Providers
|
|
60
|
+
|
|
61
|
+
- [ ] Update imports in `VidstackProvider.tsx`
|
|
62
|
+
- [ ] Update imports in `NativeProvider.tsx`
|
|
63
|
+
- [ ] Update imports in `StreamProvider.tsx`
|
|
64
|
+
- [ ] Optionally integrate `useVideoPositionCache` hook
|
|
65
|
+
- [ ] Run `pnpm check`
|
|
66
|
+
|
|
67
|
+
## Phase 8: Main Index
|
|
68
|
+
|
|
69
|
+
- [ ] Update `index.ts` with new imports
|
|
70
|
+
- [ ] Verify all exports work
|
|
71
|
+
- [ ] Run `pnpm check`
|
|
72
|
+
|
|
73
|
+
## Phase 9: Cleanup
|
|
74
|
+
|
|
75
|
+
- [ ] Delete old `types.ts`
|
|
76
|
+
- [ ] Delete old `VideoPlayer.tsx`
|
|
77
|
+
- [ ] Delete old `VideoControls.tsx`
|
|
78
|
+
- [ ] Delete old `VideoErrorFallback.tsx`
|
|
79
|
+
- [ ] Delete old `VideoPlayerContext.tsx`
|
|
80
|
+
- [ ] Run `pnpm check`
|
|
81
|
+
- [ ] Run `pnpm build` (optional)
|
|
82
|
+
|
|
83
|
+
## Post-flight
|
|
84
|
+
|
|
85
|
+
- [ ] Test video playback (URL source)
|
|
86
|
+
- [ ] Test YouTube embed
|
|
87
|
+
- [ ] Test stream playback
|
|
88
|
+
- [ ] Test error fallback
|
|
89
|
+
- [ ] Verify position caching works
|
|
90
|
+
|
|
91
|
+
## Final Structure
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
VideoPlayer/
|
|
95
|
+
├── index.ts
|
|
96
|
+
├── README.md
|
|
97
|
+
├── types/
|
|
98
|
+
│ ├── index.ts
|
|
99
|
+
│ ├── sources.ts
|
|
100
|
+
│ ├── player.ts
|
|
101
|
+
│ └── provider.ts
|
|
102
|
+
├── hooks/
|
|
103
|
+
│ ├── index.ts
|
|
104
|
+
│ └── useVideoPositionCache.ts
|
|
105
|
+
├── utils/
|
|
106
|
+
│ ├── index.ts
|
|
107
|
+
│ ├── resolvers.ts
|
|
108
|
+
│ └── fileSource.ts
|
|
109
|
+
├── components/
|
|
110
|
+
│ ├── index.ts
|
|
111
|
+
│ ├── VideoPlayer.tsx
|
|
112
|
+
│ ├── VideoControls.tsx
|
|
113
|
+
│ └── VideoErrorFallback.tsx
|
|
114
|
+
├── context/
|
|
115
|
+
│ ├── index.ts
|
|
116
|
+
│ └── VideoPlayerContext.tsx
|
|
117
|
+
└── providers/
|
|
118
|
+
├── index.ts
|
|
119
|
+
├── VidstackProvider.tsx
|
|
120
|
+
├── NativeProvider.tsx
|
|
121
|
+
└── StreamProvider.tsx
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Metrics
|
|
125
|
+
|
|
126
|
+
| Metric | Before | After |
|
|
127
|
+
|--------|--------|-------|
|
|
128
|
+
| types.ts | 369 lines | Split into 3 files (~100 each) |
|
|
129
|
+
| Total files | 10 | 18 |
|
|
130
|
+
| Hooks | 0 | 1 (useVideoPositionCache) |
|
|
131
|
+
| Utils | 0 (in types.ts) | 2 |
|
|
132
|
+
| Duplicated code | Position caching x2 | Extracted to hook |
|
|
133
|
+
|
|
134
|
+
## Optional Improvements
|
|
135
|
+
|
|
136
|
+
After main refactoring, consider:
|
|
137
|
+
1. Integrate `useVideoPositionCache` into providers
|
|
138
|
+
2. Extract more shared logic from StreamProvider
|
|
139
|
+
3. Add unit tests for utils and hooks
|
|
@@ -218,16 +218,32 @@ type VideoSourceUnion =
|
|
|
218
218
|
|
|
219
219
|
```
|
|
220
220
|
VideoPlayer/
|
|
221
|
-
├──
|
|
222
|
-
├──
|
|
223
|
-
├──
|
|
224
|
-
├──
|
|
225
|
-
├──
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
│ ├──
|
|
229
|
-
│ └──
|
|
230
|
-
|
|
221
|
+
├── index.ts # Public API exports
|
|
222
|
+
├── types/
|
|
223
|
+
│ ├── index.ts # Type re-exports
|
|
224
|
+
│ ├── sources.ts # Source types (Url, YouTube, HLS, etc.)
|
|
225
|
+
│ ├── player.ts # Player props, ref, events
|
|
226
|
+
│ └── provider.ts # Provider & context types
|
|
227
|
+
├── hooks/
|
|
228
|
+
│ ├── index.ts
|
|
229
|
+
│ └── useVideoPositionCache.ts # Playback position caching
|
|
230
|
+
├── utils/
|
|
231
|
+
│ ├── index.ts
|
|
232
|
+
│ ├── resolvers.ts # resolvePlayerMode, isSimpleStreamSource
|
|
233
|
+
│ └── fileSource.ts # resolveFileSource helper
|
|
234
|
+
├── components/
|
|
235
|
+
│ ├── index.ts
|
|
236
|
+
│ ├── VideoPlayer.tsx # Main orchestrator
|
|
237
|
+
│ ├── VideoControls.tsx # Standalone Vidstack controls
|
|
238
|
+
│ └── VideoErrorFallback.tsx # Pre-built error UI
|
|
239
|
+
├── context/
|
|
240
|
+
│ ├── index.ts
|
|
241
|
+
│ └── VideoPlayerContext.tsx # Streaming config provider
|
|
242
|
+
└── providers/
|
|
243
|
+
├── index.ts
|
|
244
|
+
├── VidstackProvider.tsx # YouTube, Vimeo, HLS, DASH
|
|
245
|
+
├── NativeProvider.tsx # HTML5 <video>
|
|
246
|
+
└── StreamProvider.tsx # HTTP Range, Blob
|
|
231
247
|
```
|
|
232
248
|
|
|
233
249
|
## Accessibility
|
|
@@ -19,7 +19,7 @@ interface VideoControlsProps {
|
|
|
19
19
|
export function VideoControls({ player, className }: VideoControlsProps) {
|
|
20
20
|
const store = useMediaStore(player);
|
|
21
21
|
const remote = useMediaRemote();
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
const isPaused = store.paused;
|
|
24
24
|
const isMuted = store.muted;
|
|
25
25
|
const isFullscreen = store.fullscreen;
|
|
@@ -56,7 +56,7 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
56
56
|
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
|
|
57
57
|
|
|
58
58
|
return (
|
|
59
|
-
<div
|
|
59
|
+
<div
|
|
60
60
|
className={cn(
|
|
61
61
|
"absolute inset-0 flex flex-col justify-end transition-opacity duration-300",
|
|
62
62
|
"bg-gradient-to-t from-black/80 via-black/20 to-transparent",
|
|
@@ -67,11 +67,11 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
67
67
|
>
|
|
68
68
|
{/* Progress Bar */}
|
|
69
69
|
<div className="px-4 pb-2 pointer-events-auto">
|
|
70
|
-
<div
|
|
70
|
+
<div
|
|
71
71
|
className="h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all group"
|
|
72
72
|
onClick={handleProgressClick}
|
|
73
73
|
>
|
|
74
|
-
<div
|
|
74
|
+
<div
|
|
75
75
|
className="h-full bg-primary rounded-full transition-all relative group-hover:bg-primary/90"
|
|
76
76
|
style={{ width: `${progress}%` }}
|
|
77
77
|
>
|
|
@@ -109,15 +109,15 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
109
109
|
<Volume2 className="h-5 w-5" />
|
|
110
110
|
)}
|
|
111
111
|
</button>
|
|
112
|
-
|
|
113
|
-
<div
|
|
112
|
+
|
|
113
|
+
<div
|
|
114
114
|
className="w-0 group-hover/volume:w-20 transition-all overflow-hidden"
|
|
115
115
|
>
|
|
116
|
-
<div
|
|
116
|
+
<div
|
|
117
117
|
className="h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all"
|
|
118
118
|
onClick={handleVolumeChange}
|
|
119
119
|
>
|
|
120
|
-
<div
|
|
120
|
+
<div
|
|
121
121
|
className="h-full bg-white rounded-full transition-all"
|
|
122
122
|
style={{ width: `${volume * 100}%` }}
|
|
123
123
|
/>
|
|
@@ -136,4 +136,3 @@ export function VideoControls({ player, className }: VideoControlsProps) {
|
|
|
136
136
|
</div>
|
|
137
137
|
);
|
|
138
138
|
}
|
|
139
|
-
|
|
@@ -10,9 +10,9 @@ import { FileVideo, RefreshCw } from 'lucide-react';
|
|
|
10
10
|
|
|
11
11
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
12
12
|
import { Button } from '@djangocfg/ui-core/components';
|
|
13
|
-
import { DownloadButton } from '
|
|
13
|
+
import { DownloadButton } from '../../../components/button-download';
|
|
14
14
|
|
|
15
|
-
import type { ErrorFallbackProps } from '
|
|
15
|
+
import type { ErrorFallbackProps } from '../types';
|
|
16
16
|
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// Types
|
|
@@ -40,12 +40,11 @@
|
|
|
40
40
|
|
|
41
41
|
import React, { forwardRef, useMemo } from 'react';
|
|
42
42
|
|
|
43
|
-
import { VidstackProvider, NativeProvider, StreamProvider } from '
|
|
44
|
-
import { useVideoPlayerContext
|
|
45
|
-
import { resolvePlayerMode } from '
|
|
43
|
+
import { VidstackProvider, NativeProvider, StreamProvider } from '../providers';
|
|
44
|
+
import { useVideoPlayerContext } from '../context';
|
|
45
|
+
import { resolvePlayerMode, isSimpleStreamSource, resolveStreamSource } from '../utils';
|
|
46
46
|
|
|
47
|
-
import type { VideoPlayerProps, VideoPlayerRef, VideoSourceUnion, VidstackProviderProps, NativeProviderProps, StreamProviderProps } from '
|
|
48
|
-
import type { SimpleStreamSource } from './VideoPlayerContext';
|
|
47
|
+
import type { VideoPlayerProps, VideoPlayerRef, VideoSourceUnion, VidstackProviderProps, NativeProviderProps, StreamProviderProps, SimpleStreamSource } from '../types';
|
|
49
48
|
|
|
50
49
|
export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps & { source: VideoSourceUnion | SimpleStreamSource }>(
|
|
51
50
|
(
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VideoPlayer components - Public API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { VideoPlayer } from './VideoPlayer';
|
|
6
|
+
export { VideoControls } from './VideoControls';
|
|
7
|
+
export {
|
|
8
|
+
VideoErrorFallback,
|
|
9
|
+
createVideoErrorFallback,
|
|
10
|
+
} from './VideoErrorFallback';
|
|
11
|
+
export type {
|
|
12
|
+
VideoErrorFallbackProps,
|
|
13
|
+
CreateVideoErrorFallbackOptions,
|
|
14
|
+
} from './VideoErrorFallback';
|