@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.
Files changed (92) hide show
  1. package/package.json +13 -8
  2. package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
  3. package/src/stores/index.ts +8 -0
  4. package/src/stores/mediaCache.ts +464 -0
  5. package/src/tools/AudioPlayer/@refactoring/00-PLAN.md +148 -0
  6. package/src/tools/AudioPlayer/@refactoring/01-TYPES.md +301 -0
  7. package/src/tools/AudioPlayer/@refactoring/02-HOOKS.md +281 -0
  8. package/src/tools/AudioPlayer/@refactoring/03-CONTEXT.md +328 -0
  9. package/src/tools/AudioPlayer/@refactoring/04-COMPONENTS.md +251 -0
  10. package/src/tools/AudioPlayer/@refactoring/05-EFFECTS.md +427 -0
  11. package/src/tools/AudioPlayer/@refactoring/06-UTILS-AND-INDEX.md +193 -0
  12. package/src/tools/AudioPlayer/@refactoring/07-EXECUTION-CHECKLIST.md +146 -0
  13. package/src/tools/AudioPlayer/README.md +325 -0
  14. package/src/tools/AudioPlayer/components/AudioEqualizer.tsx +200 -0
  15. package/src/tools/AudioPlayer/components/AudioPlayer.tsx +231 -0
  16. package/src/tools/AudioPlayer/components/AudioShortcutsPopover.tsx +99 -0
  17. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +147 -0
  18. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  19. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  20. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  21. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  22. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  23. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  24. package/src/tools/AudioPlayer/components/SimpleAudioPlayer.tsx +280 -0
  25. package/src/tools/AudioPlayer/components/VisualizationToggle.tsx +64 -0
  26. package/src/tools/AudioPlayer/components/index.ts +21 -0
  27. package/src/tools/AudioPlayer/context/AudioProvider.tsx +292 -0
  28. package/src/tools/AudioPlayer/context/index.ts +11 -0
  29. package/src/tools/AudioPlayer/context/selectors.ts +96 -0
  30. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  31. package/src/tools/AudioPlayer/hooks/index.ts +29 -0
  32. package/src/tools/AudioPlayer/hooks/useAudioAnalysis.ts +110 -0
  33. package/src/tools/AudioPlayer/hooks/useAudioHotkeys.ts +149 -0
  34. package/src/tools/AudioPlayer/hooks/useSharedWebAudio.ts +106 -0
  35. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +201 -0
  36. package/src/tools/AudioPlayer/index.ts +139 -0
  37. package/src/tools/AudioPlayer/types/audio.ts +107 -0
  38. package/src/tools/AudioPlayer/types/components.ts +98 -0
  39. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  40. package/src/tools/AudioPlayer/types/index.ts +35 -0
  41. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  42. package/src/tools/AudioPlayer/utils/index.ts +5 -0
  43. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  44. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  45. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  46. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  47. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  48. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  49. package/src/tools/ImageViewer/README.md +174 -0
  50. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  51. package/src/tools/ImageViewer/components/ImageToolbar.tsx +150 -0
  52. package/src/tools/ImageViewer/components/ImageViewer.tsx +235 -0
  53. package/src/tools/ImageViewer/components/index.ts +7 -0
  54. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  55. package/src/tools/ImageViewer/hooks/useImageLoading.ts +153 -0
  56. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  57. package/src/tools/ImageViewer/index.ts +60 -0
  58. package/src/tools/ImageViewer/types.ts +75 -0
  59. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  60. package/src/tools/ImageViewer/utils/index.ts +16 -0
  61. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  62. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  63. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  64. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  65. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  66. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  67. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  68. package/src/tools/VideoPlayer/README.md +212 -187
  69. package/src/tools/VideoPlayer/{VideoControls.tsx → components/VideoControls.tsx} +8 -9
  70. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +174 -0
  71. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  72. package/src/tools/VideoPlayer/components/index.ts +14 -0
  73. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  74. package/src/tools/VideoPlayer/context/index.ts +8 -0
  75. package/src/tools/VideoPlayer/hooks/index.ts +9 -0
  76. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +109 -0
  77. package/src/tools/VideoPlayer/index.ts +70 -9
  78. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
  79. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +401 -0
  80. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +332 -0
  81. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  82. package/src/tools/VideoPlayer/types/index.ts +38 -0
  83. package/src/tools/VideoPlayer/types/player.ts +116 -0
  84. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  85. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  86. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  87. package/src/tools/VideoPlayer/utils/index.ts +11 -0
  88. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  89. package/src/tools/index.ts +92 -4
  90. package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
  91. package/src/tools/VideoPlayer/VideoPlayer.tsx +0 -231
  92. package/src/tools/VideoPlayer/types.ts +0 -118
@@ -0,0 +1,201 @@
1
+ /**
2
+ * VideoPlayer - Unified Video Player Component
3
+ *
4
+ * Supports multiple modes:
5
+ * - vidstack: Full-featured player (YouTube, Vimeo, HLS, DASH)
6
+ * - native: Lightweight HTML5 player
7
+ * - streaming: HTTP Range streaming with auth / Blob sources
8
+ *
9
+ * @example
10
+ * // YouTube video
11
+ * <VideoPlayer source={{ type: 'youtube', id: 'dQw4w9WgXcQ' }} />
12
+ *
13
+ * @example
14
+ * // HLS stream
15
+ * <VideoPlayer source={{ type: 'hls', url: 'https://example.com/video.m3u8' }} />
16
+ *
17
+ * @example
18
+ * // HTTP Range streaming with auth (full source)
19
+ * <VideoPlayer
20
+ * source={{
21
+ * type: 'stream',
22
+ * sessionId: 'abc123',
23
+ * path: '/videos/movie.mp4',
24
+ * getStreamUrl: (id, path) => `/api/stream/${id}?path=${path}&token=${token}`
25
+ * }}
26
+ * />
27
+ *
28
+ * @example
29
+ * // HTTP Range streaming (simplified, using VideoPlayerProvider context)
30
+ * <VideoPlayerProvider sessionId={sessionId} getStreamUrl={getStreamUrl}>
31
+ * <VideoPlayer source={{ type: 'stream', path: '/videos/movie.mp4' }} />
32
+ * </VideoPlayerProvider>
33
+ *
34
+ * @example
35
+ * // Blob/ArrayBuffer
36
+ * <VideoPlayer source={{ type: 'blob', data: arrayBuffer, mimeType: 'video/mp4' }} />
37
+ */
38
+
39
+ 'use client';
40
+
41
+ import React, { forwardRef, useMemo } from 'react';
42
+
43
+ import { VidstackProvider, NativeProvider, StreamProvider } from '../providers';
44
+ import { useVideoPlayerContext } from '../context';
45
+ import { resolvePlayerMode, isSimpleStreamSource, resolveStreamSource } from '../utils';
46
+
47
+ import type { VideoPlayerProps, VideoPlayerRef, VideoSourceUnion, VidstackProviderProps, NativeProviderProps, StreamProviderProps, SimpleStreamSource } from '../types';
48
+
49
+ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps & { source: VideoSourceUnion | SimpleStreamSource }>(
50
+ (
51
+ {
52
+ source: rawSource,
53
+ mode = 'auto',
54
+ aspectRatio = 16 / 9,
55
+ autoPlay = false,
56
+ muted = false,
57
+ loop = false,
58
+ playsInline = true,
59
+ controls = true,
60
+ preload = 'metadata',
61
+ theme = 'default',
62
+ showInfo = false,
63
+ className,
64
+ videoClassName,
65
+ disableContextMenu = false,
66
+ showPreloader = true,
67
+ preloaderTimeout = 5000,
68
+ errorFallback,
69
+ onPlay,
70
+ onPause,
71
+ onEnded,
72
+ onError,
73
+ onLoadStart,
74
+ onCanPlay,
75
+ onTimeUpdate,
76
+ },
77
+ ref
78
+ ) => {
79
+ // Get context for simplified stream sources
80
+ const context = useVideoPlayerContext();
81
+
82
+ // Resolve simplified stream source to full source using context
83
+ const source = useMemo(() => {
84
+ if (isSimpleStreamSource(rawSource)) {
85
+ const resolved = resolveStreamSource(rawSource, context);
86
+ if (!resolved) {
87
+ // Return a special error source that will trigger error fallback
88
+ return null;
89
+ }
90
+ return resolved;
91
+ }
92
+ return rawSource;
93
+ }, [rawSource, context]);
94
+
95
+ // Handle unresolved source
96
+ if (!source) {
97
+ // Render error state
98
+ const errorMessage = 'Stream source requires VideoPlayerProvider with getStreamUrl and sessionId';
99
+
100
+ if (typeof errorFallback === 'function') {
101
+ return (
102
+ <div className={className} style={{ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio }}>
103
+ {errorFallback({ error: errorMessage })}
104
+ </div>
105
+ );
106
+ }
107
+
108
+ if (errorFallback) {
109
+ return (
110
+ <div className={className} style={{ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio }}>
111
+ {errorFallback}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ // Default error UI
117
+ return (
118
+ <div
119
+ className={className}
120
+ style={{
121
+ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio,
122
+ display: 'flex',
123
+ alignItems: 'center',
124
+ justifyContent: 'center',
125
+ backgroundColor: 'black',
126
+ color: 'white',
127
+ }}
128
+ >
129
+ <p>{errorMessage}</p>
130
+ </div>
131
+ );
132
+ }
133
+
134
+ // Determine which provider to use
135
+ const resolvedMode = resolvePlayerMode(source, mode);
136
+
137
+ // Common props for all providers
138
+ const commonProps = {
139
+ aspectRatio,
140
+ autoPlay,
141
+ muted,
142
+ loop,
143
+ playsInline,
144
+ controls,
145
+ preload,
146
+ className,
147
+ onPlay,
148
+ onPause,
149
+ onEnded,
150
+ onError,
151
+ onLoadStart,
152
+ onCanPlay,
153
+ onTimeUpdate,
154
+ };
155
+
156
+ // Render appropriate provider
157
+ switch (resolvedMode) {
158
+ case 'vidstack':
159
+ return (
160
+ <VidstackProvider
161
+ ref={ref}
162
+ source={source as VidstackProviderProps['source']}
163
+ theme={theme}
164
+ showInfo={showInfo}
165
+ errorFallback={errorFallback}
166
+ {...commonProps}
167
+ />
168
+ );
169
+
170
+ case 'streaming':
171
+ return (
172
+ <StreamProvider
173
+ ref={ref}
174
+ source={source as StreamProviderProps['source']}
175
+ videoClassName={videoClassName}
176
+ disableContextMenu={disableContextMenu}
177
+ showPreloader={showPreloader}
178
+ preloaderTimeout={preloaderTimeout}
179
+ errorFallback={errorFallback}
180
+ {...commonProps}
181
+ />
182
+ );
183
+
184
+ case 'native':
185
+ default:
186
+ return (
187
+ <NativeProvider
188
+ ref={ref}
189
+ source={source as NativeProviderProps['source']}
190
+ videoClassName={videoClassName}
191
+ disableContextMenu={disableContextMenu}
192
+ showPreloader={showPreloader}
193
+ preloaderTimeout={preloaderTimeout}
194
+ {...commonProps}
195
+ />
196
+ );
197
+ }
198
+ }
199
+ );
200
+
201
+ VideoPlayer.displayName = 'VideoPlayer';
@@ -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';
@@ -0,0 +1,52 @@
1
+ /**
2
+ * VideoPlayerContext - Context for streaming configuration
3
+ * Simplifies streaming API by providing getStreamUrl globally
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React, { createContext, useContext, useMemo } from 'react';
9
+
10
+ import type { VideoPlayerContextValue, VideoPlayerProviderProps } from '../types';
11
+
12
+ // =============================================================================
13
+ // Context
14
+ // =============================================================================
15
+
16
+ const VideoPlayerContext = createContext<VideoPlayerContextValue | null>(null);
17
+
18
+ /**
19
+ * Provider for VideoPlayer streaming configuration
20
+ *
21
+ * @example
22
+ * // In your app layout or FileWorkspace
23
+ * <VideoPlayerProvider
24
+ * sessionId={sessionId}
25
+ * getStreamUrl={terminalClient.terminal_media.streamStreamRetrieveUrl}
26
+ * >
27
+ * <VideoPlayer source={{ type: 'stream', path: '/video.mp4' }} />
28
+ * </VideoPlayerProvider>
29
+ */
30
+ export function VideoPlayerProvider({
31
+ children,
32
+ getStreamUrl,
33
+ sessionId,
34
+ }: VideoPlayerProviderProps) {
35
+ const value = useMemo(
36
+ () => ({ getStreamUrl, sessionId }),
37
+ [getStreamUrl, sessionId]
38
+ );
39
+
40
+ return (
41
+ <VideoPlayerContext.Provider value={value}>
42
+ {children}
43
+ </VideoPlayerContext.Provider>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Hook to access VideoPlayer context
49
+ */
50
+ export function useVideoPlayerContext(): VideoPlayerContextValue | null {
51
+ return useContext(VideoPlayerContext);
52
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * VideoPlayer context - Public API
3
+ */
4
+
5
+ export {
6
+ VideoPlayerProvider,
7
+ useVideoPlayerContext,
8
+ } from './VideoPlayerContext';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * VideoPlayer hooks - Public API
3
+ */
4
+
5
+ export { useVideoPositionCache } from './useVideoPositionCache';
6
+ export type {
7
+ UseVideoPositionCacheOptions,
8
+ UseVideoPositionCacheReturn,
9
+ } from './useVideoPositionCache';
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * useVideoPositionCache - Manages video playback position caching
5
+ *
6
+ * Saves position periodically during playback and restores on load.
7
+ */
8
+
9
+ import { useRef, useEffect, useCallback } from 'react';
10
+ import { useVideoCache } from '../../../stores/mediaCache';
11
+
12
+ // =============================================================================
13
+ // TYPES
14
+ // =============================================================================
15
+
16
+ export interface UseVideoPositionCacheOptions {
17
+ /** Unique key for caching (e.g., video URL or stream key) */
18
+ cacheKey: string | null;
19
+ /** Current playback time in seconds */
20
+ currentTime: number;
21
+ /** Video duration in seconds */
22
+ duration: number;
23
+ /** Whether video is currently playing */
24
+ isPlaying: boolean;
25
+ /** Whether video is ready to play */
26
+ isReady: boolean;
27
+ /** Callback to seek to a specific time */
28
+ onSeek: (time: number) => void;
29
+ }
30
+
31
+ export interface UseVideoPositionCacheReturn {
32
+ /** Manually save current position */
33
+ savePosition: () => void;
34
+ /** Clear saved position */
35
+ clearPosition: () => void;
36
+ }
37
+
38
+ // =============================================================================
39
+ // CONSTANTS
40
+ // =============================================================================
41
+
42
+ /** Save interval in seconds */
43
+ const SAVE_INTERVAL = 5;
44
+
45
+ /** Minimum offset from end to restore (avoid restoring at the very end) */
46
+ const END_BUFFER = 1;
47
+
48
+ // =============================================================================
49
+ // HOOK
50
+ // =============================================================================
51
+
52
+ export function useVideoPositionCache(
53
+ options: UseVideoPositionCacheOptions
54
+ ): UseVideoPositionCacheReturn {
55
+ const { cacheKey, currentTime, duration, isPlaying, isReady, onSeek } = options;
56
+
57
+ const { saveVideoPosition, getVideoPosition } = useVideoCache();
58
+ const lastSavedTimeRef = useRef<number>(0);
59
+ const hasRestoredRef = useRef<boolean>(false);
60
+
61
+ // Restore position when ready
62
+ useEffect(() => {
63
+ if (!isReady || !cacheKey || hasRestoredRef.current) return;
64
+
65
+ const savedPosition = getVideoPosition(cacheKey);
66
+ if (savedPosition && savedPosition > 0 && duration > 0) {
67
+ // Only restore if position is valid (not at the end)
68
+ if (savedPosition < duration - END_BUFFER) {
69
+ onSeek(savedPosition);
70
+ }
71
+ }
72
+ hasRestoredRef.current = true;
73
+ }, [isReady, cacheKey, duration, getVideoPosition, onSeek]);
74
+
75
+ // Reset restored flag when cache key changes
76
+ useEffect(() => {
77
+ hasRestoredRef.current = false;
78
+ lastSavedTimeRef.current = 0;
79
+ }, [cacheKey]);
80
+
81
+ // Save position periodically during playback
82
+ useEffect(() => {
83
+ if (!cacheKey || !isPlaying || currentTime <= 0) return;
84
+
85
+ const timeSinceLastSave = currentTime - lastSavedTimeRef.current;
86
+ if (timeSinceLastSave >= SAVE_INTERVAL || timeSinceLastSave < 0) {
87
+ saveVideoPosition(cacheKey, currentTime);
88
+ lastSavedTimeRef.current = currentTime;
89
+ }
90
+ }, [cacheKey, isPlaying, currentTime, saveVideoPosition]);
91
+
92
+ const savePosition = useCallback(() => {
93
+ if (cacheKey && currentTime > 0) {
94
+ saveVideoPosition(cacheKey, currentTime);
95
+ lastSavedTimeRef.current = currentTime;
96
+ }
97
+ }, [cacheKey, currentTime, saveVideoPosition]);
98
+
99
+ const clearPosition = useCallback(() => {
100
+ if (cacheKey) {
101
+ saveVideoPosition(cacheKey, 0);
102
+ }
103
+ }, [cacheKey, saveVideoPosition]);
104
+
105
+ return {
106
+ savePosition,
107
+ clearPosition,
108
+ };
109
+ }
@@ -1,16 +1,77 @@
1
1
  /**
2
- * Professional VideoPlayer - Vidstack Implementation
3
- * Export all components and types
2
+ * VideoPlayer - Unified Video Player
3
+ * Supports Vidstack (YouTube, Vimeo, HLS, DASH), Native HTML5, and HTTP Streaming
4
4
  */
5
5
 
6
- export { VideoPlayer, VideoUrlError } from './VideoPlayer';
7
- export { NativePlayer } from './NativePlayer';
8
- export { VideoControls } from './VideoControls';
6
+ // Main component
7
+ export { VideoPlayer } from './components';
8
+
9
+ // Controls (can be used standalone with Vidstack)
10
+ export { VideoControls } from './components';
11
+
12
+ // Error Fallback
13
+ export {
14
+ VideoErrorFallback,
15
+ createVideoErrorFallback,
16
+ } from './components';
17
+ export type {
18
+ VideoErrorFallbackProps,
19
+ CreateVideoErrorFallbackOptions,
20
+ } from './components';
21
+
22
+ // Providers (for advanced usage)
23
+ export { VidstackProvider, NativeProvider, StreamProvider } from './providers';
24
+
25
+ // Context (for streaming configuration)
26
+ export {
27
+ VideoPlayerProvider,
28
+ useVideoPlayerContext,
29
+ } from './context';
30
+
31
+ // Hooks
32
+ export { useVideoPositionCache } from './hooks';
9
33
  export type {
10
- VideoSource,
34
+ UseVideoPositionCacheOptions,
35
+ UseVideoPositionCacheReturn,
36
+ } from './hooks';
37
+
38
+ // Utils
39
+ export {
40
+ resolvePlayerMode,
41
+ resolveFileSource,
42
+ isSimpleStreamSource,
43
+ resolveStreamSource,
44
+ } from './utils';
45
+
46
+ // Types
47
+ export type {
48
+ // Source types
49
+ VideoSourceUnion,
50
+ UrlSource,
51
+ YouTubeSource,
52
+ VimeoSource,
53
+ HLSSource,
54
+ DASHSource,
55
+ StreamSource,
56
+ BlobSource,
57
+ DataUrlSource,
58
+ // Player types
59
+ PlayerMode,
60
+ AspectRatioValue,
11
61
  VideoPlayerProps,
12
62
  VideoPlayerRef,
13
- NativePlayerProps,
14
- NativePlayerRef,
63
+ ErrorFallbackProps,
64
+ // Provider props (internal)
65
+ VidstackProviderProps,
66
+ NativeProviderProps,
67
+ StreamProviderProps,
68
+ // Common types
69
+ CommonPlayerSettings,
70
+ CommonPlayerEvents,
71
+ // File source helper types
72
+ ResolveFileSourceOptions,
73
+ // Context types
74
+ VideoPlayerContextValue,
75
+ VideoPlayerProviderProps,
76
+ SimpleStreamSource,
15
77
  } from './types';
16
-
@@ -0,0 +1,206 @@
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
+
13
+ import type { NativeProviderProps, VideoPlayerRef } from '../types';
14
+
15
+ /**
16
+ * Get video URL from source
17
+ */
18
+ function getVideoUrl(source: NativeProviderProps['source']): string {
19
+ switch (source.type) {
20
+ case 'url':
21
+ return source.url;
22
+ case 'data-url':
23
+ return source.data;
24
+ default:
25
+ return '';
26
+ }
27
+ }
28
+
29
+ export const NativeProvider = forwardRef<VideoPlayerRef, NativeProviderProps>(
30
+ (
31
+ {
32
+ source,
33
+ aspectRatio = 16 / 9,
34
+ autoPlay = true,
35
+ muted = true,
36
+ loop = true,
37
+ playsInline = true,
38
+ preload = 'auto',
39
+ controls = false,
40
+ disableContextMenu = true,
41
+ showPreloader = true,
42
+ preloaderTimeout = 5000,
43
+ className,
44
+ videoClassName,
45
+ onPlay,
46
+ onPause,
47
+ onEnded,
48
+ onError,
49
+ onLoadStart,
50
+ onCanPlay,
51
+ onTimeUpdate,
52
+ },
53
+ ref
54
+ ) => {
55
+ const [isLoading, setIsLoading] = useState(showPreloader);
56
+ const videoRef = useRef<HTMLVideoElement>(null);
57
+
58
+ const videoUrl = getVideoUrl(source);
59
+
60
+ // Expose video element methods via ref
61
+ useImperativeHandle(
62
+ ref,
63
+ () => ({
64
+ play: () => videoRef.current?.play(),
65
+ pause: () => videoRef.current?.pause(),
66
+ togglePlay: () => {
67
+ const video = videoRef.current;
68
+ if (video) {
69
+ video.paused ? video.play() : video.pause();
70
+ }
71
+ },
72
+ seekTo: (time: number) => {
73
+ if (videoRef.current) videoRef.current.currentTime = time;
74
+ },
75
+ setVolume: (volume: number) => {
76
+ if (videoRef.current) videoRef.current.volume = Math.max(0, Math.min(1, volume));
77
+ },
78
+ toggleMute: () => {
79
+ if (videoRef.current) videoRef.current.muted = !videoRef.current.muted;
80
+ },
81
+ enterFullscreen: () => videoRef.current?.requestFullscreen(),
82
+ exitFullscreen: () => document.exitFullscreen(),
83
+ get currentTime() {
84
+ return videoRef.current?.currentTime ?? 0;
85
+ },
86
+ get duration() {
87
+ return videoRef.current?.duration ?? 0;
88
+ },
89
+ get paused() {
90
+ return videoRef.current?.paused ?? true;
91
+ },
92
+ }),
93
+ []
94
+ );
95
+
96
+ useEffect(() => {
97
+ if (!showPreloader) return;
98
+
99
+ const video = videoRef.current;
100
+ if (!video) return;
101
+
102
+ // Check if video is already loaded
103
+ if (video.readyState >= 3) {
104
+ setIsLoading(false);
105
+ return;
106
+ }
107
+
108
+ const hideLoader = () => setIsLoading(false);
109
+
110
+ video.addEventListener('canplay', hideLoader);
111
+ video.addEventListener('loadeddata', hideLoader);
112
+ video.addEventListener('playing', hideLoader);
113
+
114
+ // Fallback: hide loader after timeout
115
+ const timeout = setTimeout(hideLoader, preloaderTimeout);
116
+
117
+ return () => {
118
+ video.removeEventListener('canplay', hideLoader);
119
+ video.removeEventListener('loadeddata', hideLoader);
120
+ video.removeEventListener('playing', hideLoader);
121
+ clearTimeout(timeout);
122
+ };
123
+ }, [showPreloader, preloaderTimeout]);
124
+
125
+ const handleContextMenu = (e: React.MouseEvent) => {
126
+ if (disableContextMenu) {
127
+ e.preventDefault();
128
+ }
129
+ };
130
+
131
+ const handleError = (e: React.SyntheticEvent<HTMLVideoElement>) => {
132
+ setIsLoading(false);
133
+ onError?.(e.currentTarget.error?.message || 'Video playback error');
134
+ };
135
+
136
+ const handleTimeUpdate = () => {
137
+ const video = videoRef.current;
138
+ if (video && onTimeUpdate) {
139
+ onTimeUpdate(video.currentTime, video.duration);
140
+ }
141
+ };
142
+
143
+ // Determine if we should use AspectRatio wrapper or fill mode
144
+ const isFillMode = aspectRatio === 'fill';
145
+ const computedAspectRatio = aspectRatio === 'auto' || aspectRatio === 'fill' ? undefined : aspectRatio;
146
+
147
+ // Video content
148
+ const videoContent = (
149
+ <>
150
+ {/* Preloader */}
151
+ {showPreloader && isLoading && (
152
+ <div
153
+ className={cn(
154
+ 'absolute inset-0 flex items-center justify-center bg-muted/30 backdrop-blur-sm z-10'
155
+ )}
156
+ >
157
+ <Preloader size="lg" spinnerClassName="text-white" />
158
+ </div>
159
+ )}
160
+
161
+ {/* Video */}
162
+ <video
163
+ ref={videoRef}
164
+ className={cn('w-full h-full object-cover', videoClassName)}
165
+ src={videoUrl}
166
+ autoPlay={autoPlay}
167
+ muted={muted}
168
+ loop={loop}
169
+ playsInline={playsInline}
170
+ preload={preload}
171
+ controls={controls}
172
+ poster={source.poster}
173
+ onContextMenu={handleContextMenu}
174
+ onLoadStart={onLoadStart}
175
+ onCanPlay={onCanPlay}
176
+ onPlay={onPlay}
177
+ onPause={onPause}
178
+ onPlaying={() => setIsLoading(false)}
179
+ onEnded={onEnded}
180
+ onError={handleError}
181
+ onTimeUpdate={handleTimeUpdate}
182
+ />
183
+ </>
184
+ );
185
+
186
+ // Fill mode - no AspectRatio wrapper
187
+ if (isFillMode) {
188
+ return (
189
+ <div className={cn('relative w-full h-full overflow-hidden', className)}>
190
+ {videoContent}
191
+ </div>
192
+ );
193
+ }
194
+
195
+ // Normal mode with AspectRatio
196
+ return (
197
+ <div className={cn('relative overflow-hidden', className)}>
198
+ <AspectRatio ratio={computedAspectRatio}>
199
+ {videoContent}
200
+ </AspectRatio>
201
+ </div>
202
+ );
203
+ }
204
+ );
205
+
206
+ NativeProvider.displayName = 'NativeProvider';