@djangocfg/ui-tools 2.1.404 → 2.1.407
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/README.md +2 -1
- package/package.json +11 -9
- package/src/tools/AudioPlayer/lazy.tsx +13 -27
- package/src/tools/ImageViewer/components/ImageViewer.tsx +10 -2
- package/src/tools/VideoPlayer/README.md +87 -230
- package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
- package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
- package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +38 -0
- package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
- package/src/tools/VideoPlayer/canvas/index.ts +12 -0
- package/src/tools/VideoPlayer/canvas/jsx.d.ts +54 -0
- package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
- package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +39 -0
- package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +77 -0
- package/src/tools/VideoPlayer/index.ts +51 -65
- package/src/tools/VideoPlayer/lazy.tsx +11 -54
- package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
- package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
- package/src/tools/VideoPlayer/parts/index.ts +15 -0
- package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
- package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
- package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
- package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
- package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
- package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
- package/src/tools/VideoPlayer/styles/video-player.css +141 -0
- package/src/tools/VideoPlayer/types.ts +82 -0
- package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
- package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
- package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
- package/src/tools/index.ts +35 -28
- package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
- package/src/tools/VideoPlayer/components/index.ts +0 -14
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
- package/src/tools/VideoPlayer/context/index.ts +0 -8
- package/src/tools/VideoPlayer/hooks/index.ts +0 -12
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
- package/src/tools/VideoPlayer/providers/index.ts +0 -8
- package/src/tools/VideoPlayer/types/index.ts +0 -38
- package/src/tools/VideoPlayer/types/player.ts +0 -116
- package/src/tools/VideoPlayer/types/provider.ts +0 -93
- package/src/tools/VideoPlayer/types/sources.ts +0 -97
- package/src/tools/VideoPlayer/utils/debug.ts +0 -14
- package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
- package/src/tools/VideoPlayer/utils/index.ts +0 -12
- package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NativeProvider - Lightweight native HTML5 video player
|
|
3
|
-
* For demo videos, background videos, autoplay loop muted scenarios
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
9
|
-
|
|
10
|
-
import { cn } from '@djangocfg/ui-core/lib';
|
|
11
|
-
import { Preloader, AspectRatio } from '@djangocfg/ui-core';
|
|
12
|
-
import { useVideoPlayerSettings } from '../hooks/useVideoPlayerSettings';
|
|
13
|
-
|
|
14
|
-
import type { NativeProviderProps, VideoPlayerRef } from '../types';
|
|
15
|
-
import { videoDebug } from '../utils/debug';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get video URL from source
|
|
19
|
-
*/
|
|
20
|
-
function getVideoUrl(source: NativeProviderProps['source']): string {
|
|
21
|
-
switch (source.type) {
|
|
22
|
-
case 'url':
|
|
23
|
-
return source.url;
|
|
24
|
-
case 'data-url':
|
|
25
|
-
return source.data;
|
|
26
|
-
default:
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const NativeProvider = forwardRef<VideoPlayerRef, NativeProviderProps>(
|
|
32
|
-
(
|
|
33
|
-
{
|
|
34
|
-
source,
|
|
35
|
-
aspectRatio = 16 / 9,
|
|
36
|
-
autoPlay = true,
|
|
37
|
-
muted = true,
|
|
38
|
-
loop = true,
|
|
39
|
-
playsInline = true,
|
|
40
|
-
preload = 'auto',
|
|
41
|
-
controls = false,
|
|
42
|
-
disableContextMenu = true,
|
|
43
|
-
showPreloader = true,
|
|
44
|
-
preloaderTimeout = 5000,
|
|
45
|
-
className,
|
|
46
|
-
videoClassName,
|
|
47
|
-
onPlay,
|
|
48
|
-
onPause,
|
|
49
|
-
onEnded,
|
|
50
|
-
onError,
|
|
51
|
-
onLoadStart,
|
|
52
|
-
onCanPlay,
|
|
53
|
-
onTimeUpdate,
|
|
54
|
-
},
|
|
55
|
-
ref
|
|
56
|
-
) => {
|
|
57
|
-
const [isLoading, setIsLoading] = useState(showPreloader);
|
|
58
|
-
const videoRef = useRef<HTMLVideoElement>(null);
|
|
59
|
-
|
|
60
|
-
// Persisted player settings
|
|
61
|
-
const { settings: savedSettings, updateVolume } = useVideoPlayerSettings();
|
|
62
|
-
|
|
63
|
-
const videoUrl = getVideoUrl(source);
|
|
64
|
-
|
|
65
|
-
// Debug: Log video source
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
videoDebug.load(videoUrl, source.type);
|
|
68
|
-
}, [videoUrl, source.type]);
|
|
69
|
-
|
|
70
|
-
// Expose video element methods via ref
|
|
71
|
-
useImperativeHandle(
|
|
72
|
-
ref,
|
|
73
|
-
() => ({
|
|
74
|
-
play: () => videoRef.current?.play(),
|
|
75
|
-
pause: () => videoRef.current?.pause(),
|
|
76
|
-
togglePlay: () => {
|
|
77
|
-
const video = videoRef.current;
|
|
78
|
-
if (video) {
|
|
79
|
-
video.paused ? video.play() : video.pause();
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
seekTo: (time: number) => {
|
|
83
|
-
if (videoRef.current) videoRef.current.currentTime = time;
|
|
84
|
-
},
|
|
85
|
-
setVolume: (volume: number) => {
|
|
86
|
-
if (videoRef.current) videoRef.current.volume = Math.max(0, Math.min(1, volume));
|
|
87
|
-
},
|
|
88
|
-
toggleMute: () => {
|
|
89
|
-
if (videoRef.current) videoRef.current.muted = !videoRef.current.muted;
|
|
90
|
-
},
|
|
91
|
-
enterFullscreen: () => videoRef.current?.requestFullscreen(),
|
|
92
|
-
exitFullscreen: () => document.exitFullscreen(),
|
|
93
|
-
get currentTime() {
|
|
94
|
-
return videoRef.current?.currentTime ?? 0;
|
|
95
|
-
},
|
|
96
|
-
get duration() {
|
|
97
|
-
return videoRef.current?.duration ?? 0;
|
|
98
|
-
},
|
|
99
|
-
get paused() {
|
|
100
|
-
return videoRef.current?.paused ?? true;
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
[]
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (!showPreloader) return;
|
|
108
|
-
|
|
109
|
-
const video = videoRef.current;
|
|
110
|
-
if (!video) return;
|
|
111
|
-
|
|
112
|
-
// Check if video is already loaded
|
|
113
|
-
if (video.readyState >= 3) {
|
|
114
|
-
setIsLoading(false);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const hideLoader = () => setIsLoading(false);
|
|
119
|
-
|
|
120
|
-
video.addEventListener('canplay', hideLoader);
|
|
121
|
-
video.addEventListener('loadeddata', hideLoader);
|
|
122
|
-
video.addEventListener('playing', hideLoader);
|
|
123
|
-
|
|
124
|
-
// Fallback: hide loader after timeout
|
|
125
|
-
const timeout = setTimeout(hideLoader, preloaderTimeout);
|
|
126
|
-
|
|
127
|
-
return () => {
|
|
128
|
-
video.removeEventListener('canplay', hideLoader);
|
|
129
|
-
video.removeEventListener('loadeddata', hideLoader);
|
|
130
|
-
video.removeEventListener('playing', hideLoader);
|
|
131
|
-
clearTimeout(timeout);
|
|
132
|
-
};
|
|
133
|
-
}, [showPreloader, preloaderTimeout]);
|
|
134
|
-
|
|
135
|
-
// Debug: Log video events
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
const video = videoRef.current;
|
|
138
|
-
if (!video) return;
|
|
139
|
-
|
|
140
|
-
const handleLoadedMetadata = () => {
|
|
141
|
-
videoDebug.state('loadedmetadata', { duration: video.duration });
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const handleCanPlayDebug = () => {
|
|
145
|
-
videoDebug.state('canplay', { duration: video.duration, buffered: video.buffered.length });
|
|
146
|
-
videoDebug.buffer(video.buffered, video.duration);
|
|
147
|
-
// Apply saved volume
|
|
148
|
-
video.volume = savedSettings.volume;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const handleSeeking = () => {
|
|
152
|
-
videoDebug.event('seeking', { currentTime: video.currentTime });
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const handleSeeked = () => {
|
|
156
|
-
videoDebug.event('seeked', { currentTime: video.currentTime });
|
|
157
|
-
videoDebug.buffer(video.buffered, video.duration);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const handleWaiting = () => {
|
|
161
|
-
videoDebug.warn('WAITING - buffering...');
|
|
162
|
-
videoDebug.buffer(video.buffered, video.duration);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const handleStalled = () => {
|
|
166
|
-
videoDebug.warn('STALLED - network issue');
|
|
167
|
-
videoDebug.buffer(video.buffered, video.duration);
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
video.addEventListener('loadedmetadata', handleLoadedMetadata);
|
|
171
|
-
video.addEventListener('canplay', handleCanPlayDebug);
|
|
172
|
-
video.addEventListener('seeking', handleSeeking);
|
|
173
|
-
video.addEventListener('seeked', handleSeeked);
|
|
174
|
-
video.addEventListener('waiting', handleWaiting);
|
|
175
|
-
video.addEventListener('stalled', handleStalled);
|
|
176
|
-
|
|
177
|
-
return () => {
|
|
178
|
-
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
|
179
|
-
video.removeEventListener('canplay', handleCanPlayDebug);
|
|
180
|
-
video.removeEventListener('seeking', handleSeeking);
|
|
181
|
-
video.removeEventListener('seeked', handleSeeked);
|
|
182
|
-
video.removeEventListener('waiting', handleWaiting);
|
|
183
|
-
video.removeEventListener('stalled', handleStalled);
|
|
184
|
-
};
|
|
185
|
-
}, [savedSettings.volume]);
|
|
186
|
-
|
|
187
|
-
// Persist volume when user changes it via native controls
|
|
188
|
-
useEffect(() => {
|
|
189
|
-
const video = videoRef.current;
|
|
190
|
-
if (!video) return;
|
|
191
|
-
|
|
192
|
-
const handleVolumeChange = () => {
|
|
193
|
-
updateVolume(video.volume);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
video.addEventListener('volumechange', handleVolumeChange);
|
|
197
|
-
return () => video.removeEventListener('volumechange', handleVolumeChange);
|
|
198
|
-
}, [updateVolume]);
|
|
199
|
-
|
|
200
|
-
const handleContextMenu = (e: React.MouseEvent) => {
|
|
201
|
-
if (disableContextMenu) {
|
|
202
|
-
e.preventDefault();
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const handleError = (e: React.SyntheticEvent<HTMLVideoElement>) => {
|
|
207
|
-
const video = e.currentTarget;
|
|
208
|
-
const errorMsg = video.error?.message || 'Video playback error';
|
|
209
|
-
videoDebug.error('Video error', { code: video.error?.code, message: errorMsg });
|
|
210
|
-
setIsLoading(false);
|
|
211
|
-
onError?.(errorMsg);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const handleTimeUpdate = () => {
|
|
215
|
-
const video = videoRef.current;
|
|
216
|
-
if (video && onTimeUpdate) {
|
|
217
|
-
onTimeUpdate(video.currentTime, video.duration);
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
// Determine if we should use AspectRatio wrapper or fill mode
|
|
222
|
-
const isFillMode = aspectRatio === 'fill';
|
|
223
|
-
const computedAspectRatio = aspectRatio === 'auto' || aspectRatio === 'fill' ? undefined : aspectRatio;
|
|
224
|
-
|
|
225
|
-
// Video content
|
|
226
|
-
const videoContent = (
|
|
227
|
-
<>
|
|
228
|
-
{/* Preloader */}
|
|
229
|
-
{showPreloader && isLoading && (
|
|
230
|
-
<div
|
|
231
|
-
className={cn(
|
|
232
|
-
'absolute inset-0 flex items-center justify-center bg-muted/30 backdrop-blur-sm z-10'
|
|
233
|
-
)}
|
|
234
|
-
>
|
|
235
|
-
<Preloader size="lg" spinnerClassName="text-white" />
|
|
236
|
-
</div>
|
|
237
|
-
)}
|
|
238
|
-
|
|
239
|
-
{/* Video */}
|
|
240
|
-
<video
|
|
241
|
-
ref={videoRef}
|
|
242
|
-
className={cn('w-full h-full object-cover', videoClassName)}
|
|
243
|
-
src={videoUrl}
|
|
244
|
-
autoPlay={autoPlay}
|
|
245
|
-
muted={muted}
|
|
246
|
-
loop={loop}
|
|
247
|
-
playsInline={playsInline}
|
|
248
|
-
preload={preload}
|
|
249
|
-
controls={controls}
|
|
250
|
-
poster={source.poster}
|
|
251
|
-
onContextMenu={handleContextMenu}
|
|
252
|
-
onLoadStart={onLoadStart}
|
|
253
|
-
onCanPlay={onCanPlay}
|
|
254
|
-
onPlay={onPlay}
|
|
255
|
-
onPause={onPause}
|
|
256
|
-
onPlaying={() => setIsLoading(false)}
|
|
257
|
-
onEnded={onEnded}
|
|
258
|
-
onError={handleError}
|
|
259
|
-
onTimeUpdate={handleTimeUpdate}
|
|
260
|
-
/>
|
|
261
|
-
</>
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
// Fill mode - no AspectRatio wrapper
|
|
265
|
-
if (isFillMode) {
|
|
266
|
-
return (
|
|
267
|
-
<div className={cn('relative w-full h-full overflow-hidden', className)}>
|
|
268
|
-
{videoContent}
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Normal mode with AspectRatio
|
|
274
|
-
return (
|
|
275
|
-
<div className={cn('relative overflow-hidden', className)}>
|
|
276
|
-
<AspectRatio ratio={computedAspectRatio}>
|
|
277
|
-
{videoContent}
|
|
278
|
-
</AspectRatio>
|
|
279
|
-
</div>
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
NativeProvider.displayName = 'NativeProvider';
|