@djangocfg/ui-nextjs 2.1.65 → 2.1.66

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 (30) hide show
  1. package/package.json +9 -6
  2. package/src/blocks/SplitHero/SplitHeroMedia.tsx +2 -1
  3. package/src/tools/AudioPlayer/AudioEqualizer.tsx +235 -0
  4. package/src/tools/AudioPlayer/AudioPlayer.tsx +223 -0
  5. package/src/tools/AudioPlayer/AudioReactiveCover.tsx +389 -0
  6. package/src/tools/AudioPlayer/AudioShortcutsPopover.tsx +95 -0
  7. package/src/tools/AudioPlayer/README.md +301 -0
  8. package/src/tools/AudioPlayer/SimpleAudioPlayer.tsx +275 -0
  9. package/src/tools/AudioPlayer/VisualizationToggle.tsx +68 -0
  10. package/src/tools/AudioPlayer/context.tsx +426 -0
  11. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  12. package/src/tools/AudioPlayer/index.ts +84 -0
  13. package/src/tools/AudioPlayer/types.ts +162 -0
  14. package/src/tools/AudioPlayer/useAudioHotkeys.ts +142 -0
  15. package/src/tools/AudioPlayer/useAudioVisualization.tsx +195 -0
  16. package/src/tools/ImageViewer/ImageViewer.tsx +416 -0
  17. package/src/tools/ImageViewer/README.md +161 -0
  18. package/src/tools/ImageViewer/index.ts +16 -0
  19. package/src/tools/VideoPlayer/README.md +196 -187
  20. package/src/tools/VideoPlayer/VideoErrorFallback.tsx +174 -0
  21. package/src/tools/VideoPlayer/VideoPlayer.tsx +189 -218
  22. package/src/tools/VideoPlayer/VideoPlayerContext.tsx +125 -0
  23. package/src/tools/VideoPlayer/index.ts +59 -7
  24. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +206 -0
  25. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +311 -0
  26. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +254 -0
  27. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  28. package/src/tools/VideoPlayer/types.ts +320 -71
  29. package/src/tools/index.ts +82 -4
  30. package/src/tools/VideoPlayer/NativePlayer.tsx +0 -141
@@ -1,231 +1,202 @@
1
- // @ts-nocheck
2
1
  /**
3
- * Professional VideoPlayer - Vidstack Implementation
4
- * Supports YouTube, Vimeo, MP4, HLS and more with custom controls
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' }} />
5
37
  */
6
38
 
7
39
  'use client';
8
40
 
9
- // Import Vidstack base styles
10
- import '@vidstack/react/player/styles/base.css';
11
- // Import default theme
12
- import '@vidstack/react/player/styles/default/theme.css';
13
- import '@vidstack/react/player/styles/default/layouts/video.css';
14
-
15
- import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
16
-
17
- import { cn, generateOgImageUrl } from '@djangocfg/ui-core/lib';
18
- import { MediaPlayer, MediaProvider, Poster } from '@vidstack/react';
19
- import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
20
-
21
- import { type, VideoPlayerProps, VideoPlayerRef } from './types';
22
-
23
- import type { MediaPlayerInstance } from '@vidstack/react';
24
- /**
25
- * Custom error class for invalid video URLs
26
- */
27
- export class VideoUrlError extends Error {
28
- constructor(
29
- message: string,
30
- public readonly url: string,
31
- public readonly suggestion?: string
32
- ) {
33
- super(message);
34
- this.name = 'VideoUrlError';
35
- }
36
- }
37
-
38
- /**
39
- * Validates and normalizes video URL for Vidstack
40
- * @throws {VideoUrlError} If URL format is invalid for the detected provider
41
- */
42
- function normalizeVideoUrl(url: string): string {
43
- if (!url || typeof url !== 'string') {
44
- throw new VideoUrlError('Video URL is required', url || '');
45
- }
46
-
47
- const trimmedUrl = url.trim();
41
+ import React, { forwardRef, useMemo } from 'react';
42
+
43
+ import { VidstackProvider, NativeProvider, StreamProvider } from './providers';
44
+ import { useVideoPlayerContext, isSimpleStreamSource, resolveStreamSource } from './VideoPlayerContext';
45
+ import { resolvePlayerMode } from './types';
46
+
47
+ import type { VideoPlayerProps, VideoPlayerRef, VideoSourceUnion, VidstackProviderProps, NativeProviderProps, StreamProviderProps } from './types';
48
+ import type { SimpleStreamSource } from './VideoPlayerContext';
49
+
50
+ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps & { source: VideoSourceUnion | SimpleStreamSource }>(
51
+ (
52
+ {
53
+ source: rawSource,
54
+ mode = 'auto',
55
+ aspectRatio = 16 / 9,
56
+ autoPlay = false,
57
+ muted = false,
58
+ loop = false,
59
+ playsInline = true,
60
+ controls = true,
61
+ preload = 'metadata',
62
+ theme = 'default',
63
+ showInfo = false,
64
+ className,
65
+ videoClassName,
66
+ disableContextMenu = false,
67
+ showPreloader = true,
68
+ preloaderTimeout = 5000,
69
+ errorFallback,
70
+ onPlay,
71
+ onPause,
72
+ onEnded,
73
+ onError,
74
+ onLoadStart,
75
+ onCanPlay,
76
+ onTimeUpdate,
77
+ },
78
+ ref
79
+ ) => {
80
+ // Get context for simplified stream sources
81
+ const context = useVideoPlayerContext();
82
+
83
+ // Resolve simplified stream source to full source using context
84
+ const source = useMemo(() => {
85
+ if (isSimpleStreamSource(rawSource)) {
86
+ const resolved = resolveStreamSource(rawSource, context);
87
+ if (!resolved) {
88
+ // Return a special error source that will trigger error fallback
89
+ return null;
90
+ }
91
+ return resolved;
92
+ }
93
+ return rawSource;
94
+ }, [rawSource, context]);
95
+
96
+ // Handle unresolved source
97
+ if (!source) {
98
+ // Render error state
99
+ const errorMessage = 'Stream source requires VideoPlayerProvider with getStreamUrl and sessionId';
100
+
101
+ if (typeof errorFallback === 'function') {
102
+ return (
103
+ <div className={className} style={{ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio }}>
104
+ {errorFallback({ error: errorMessage })}
105
+ </div>
106
+ );
107
+ }
48
108
 
49
- // Already in correct format (youtube/ID, vimeo/ID, or direct URL)
50
- if (trimmedUrl.startsWith('youtube/') || trimmedUrl.startsWith('vimeo/')) {
51
- return trimmedUrl;
52
- }
109
+ if (errorFallback) {
110
+ return (
111
+ <div className={className} style={{ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio }}>
112
+ {errorFallback}
113
+ </div>
114
+ );
115
+ }
53
116
 
54
- // YouTube URL patterns - auto-convert to youtube/ID format
55
- const youtubePatterns = [
56
- /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/,
57
- /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/,
58
- ];
59
-
60
- for (const pattern of youtubePatterns) {
61
- const match = trimmedUrl.match(pattern);
62
- if (match) {
63
- // Auto-convert YouTube URL to youtube/ID format
64
- const videoId = match[1];
65
- return `youtube/${videoId}`;
117
+ // Default error UI
118
+ return (
119
+ <div
120
+ className={className}
121
+ style={{
122
+ aspectRatio: aspectRatio === 'fill' ? undefined : aspectRatio,
123
+ display: 'flex',
124
+ alignItems: 'center',
125
+ justifyContent: 'center',
126
+ backgroundColor: 'black',
127
+ color: 'white',
128
+ }}
129
+ >
130
+ <p>{errorMessage}</p>
131
+ </div>
132
+ );
66
133
  }
67
- }
68
-
69
- // Vimeo URL patterns - auto-convert to vimeo/ID format
70
- const vimeoPattern = /vimeo\.com\/(\d+)/;
71
- const vimeoMatch = trimmedUrl.match(vimeoPattern);
72
- if (vimeoMatch) {
73
- const videoId = vimeoMatch[1];
74
- return `vimeo/${videoId}`;
75
- }
76
-
77
- // Direct video URLs (mp4, webm, etc.) - allow as-is
78
- if (/\.(mp4|webm|ogg|m3u8|mpd)(\?.*)?$/i.test(trimmedUrl)) {
79
- return trimmedUrl;
80
- }
81
134
 
82
- // HLS/DASH streams
83
- if (trimmedUrl.includes('.m3u8') || trimmedUrl.includes('.mpd')) {
84
- return trimmedUrl;
85
- }
135
+ // Determine which provider to use
136
+ const resolvedMode = resolvePlayerMode(source, mode);
137
+
138
+ // Common props for all providers
139
+ const commonProps = {
140
+ aspectRatio,
141
+ autoPlay,
142
+ muted,
143
+ loop,
144
+ playsInline,
145
+ controls,
146
+ preload,
147
+ className,
148
+ onPlay,
149
+ onPause,
150
+ onEnded,
151
+ onError,
152
+ onLoadStart,
153
+ onCanPlay,
154
+ onTimeUpdate,
155
+ };
86
156
 
87
- // Unknown format - return as-is
88
- return trimmedUrl;
89
- }
90
-
91
- export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
92
- source,
93
- aspectRatio = 16 / 9,
94
- autoplay = false,
95
- muted = false,
96
- playsInline = true,
97
- controls = true,
98
- className,
99
- showInfo = false,
100
- theme = 'default',
101
- onPlay,
102
- onPause,
103
- onEnded,
104
- onError,
105
- }, ref) => {
106
- const playerRef = useRef<MediaPlayerInstance | null>(null);
107
-
108
- // Auto-generate poster if not provided (uses OG Image API)
109
- const posterUrl = useMemo(() => {
110
- if (source.poster) return source.poster;
111
- if (!source.title) return undefined;
112
- return generateOgImageUrl({ title: source.title, description: source.description });
113
- }, [source.poster, source.title, source.description]);
114
-
115
- // Validate and normalize URL - throws VideoUrlError if invalid
116
- const normalizedUrl = useMemo(() => {
117
- try {
118
- return normalizeVideoUrl(source.url);
119
- } catch (error) {
120
- if (error instanceof VideoUrlError) {
121
- // Call onError callback and re-throw
122
- onError?.(error.message + (error.suggestion ? ` Use: "${error.suggestion}"` : ''));
123
- throw error;
124
- }
125
- throw error;
157
+ // Render appropriate provider
158
+ switch (resolvedMode) {
159
+ case 'vidstack':
160
+ return (
161
+ <VidstackProvider
162
+ ref={ref}
163
+ source={source as VidstackProviderProps['source']}
164
+ theme={theme}
165
+ showInfo={showInfo}
166
+ errorFallback={errorFallback}
167
+ {...commonProps}
168
+ />
169
+ );
170
+
171
+ case 'streaming':
172
+ return (
173
+ <StreamProvider
174
+ ref={ref}
175
+ source={source as StreamProviderProps['source']}
176
+ videoClassName={videoClassName}
177
+ disableContextMenu={disableContextMenu}
178
+ showPreloader={showPreloader}
179
+ preloaderTimeout={preloaderTimeout}
180
+ errorFallback={errorFallback}
181
+ {...commonProps}
182
+ />
183
+ );
184
+
185
+ case 'native':
186
+ default:
187
+ return (
188
+ <NativeProvider
189
+ ref={ref}
190
+ source={source as NativeProviderProps['source']}
191
+ videoClassName={videoClassName}
192
+ disableContextMenu={disableContextMenu}
193
+ showPreloader={showPreloader}
194
+ preloaderTimeout={preloaderTimeout}
195
+ {...commonProps}
196
+ />
197
+ );
126
198
  }
127
- }, [source.url, onError]);
128
-
129
- // Expose player methods via ref
130
- useImperativeHandle(ref, () => {
131
- const player = playerRef.current;
132
-
133
- return {
134
- play: () => player?.play(),
135
- pause: () => player?.pause(),
136
- togglePlay: () => {
137
- if (player) {
138
- player.paused ? player.play() : player.pause();
139
- }
140
- },
141
- seekTo: (time: number) => {
142
- if (player) player.currentTime = time;
143
- },
144
- setVolume: (volume: number) => {
145
- if (player) player.volume = Math.max(0, Math.min(1, volume));
146
- },
147
- toggleMute: () => {
148
- if (player) player.muted = !player.muted;
149
- },
150
- enterFullscreen: () => player?.enterFullscreen(),
151
- exitFullscreen: () => player?.exitFullscreen(),
152
- };
153
- }, []);
154
-
155
- const handlePlay = () => {
156
- onPlay?.();
157
- };
158
-
159
- const handlePause = () => {
160
- onPause?.();
161
- };
162
-
163
- const handleEnded = () => {
164
- onEnded?.();
165
- };
166
-
167
- const handleError = (detail: any) => {
168
- onError?.(detail?.message || 'Video playback error');
169
- };
170
-
171
- return (
172
- <div className={cn("w-full", className)}>
173
- {/* Video Player */}
174
- <div
175
- className={cn(
176
- "relative w-full rounded-sm bg-black overflow-hidden",
177
- theme === 'minimal' && "rounded-none",
178
- theme === 'modern' && "rounded-xl shadow-2xl"
179
- )}
180
- style={{ aspectRatio: aspectRatio }}
181
- >
182
- <MediaPlayer
183
- ref={playerRef}
184
- title={source.title || 'Video'}
185
- src={normalizedUrl}
186
- autoPlay={autoplay}
187
- muted={muted}
188
- playsInline={playsInline}
189
- onPlay={handlePlay}
190
- onPause={handlePause}
191
- onEnded={handleEnded}
192
- onError={handleError}
193
- className="w-full h-full"
194
- >
195
- <MediaProvider />
196
-
197
- {/* Poster with proper aspect ratio handling */}
198
- {posterUrl && (
199
- <Poster
200
- className="vds-poster"
201
- src={posterUrl}
202
- alt={source.title || 'Video poster'}
203
- style={{ objectFit: 'cover' }}
204
- />
205
- )}
206
-
207
- {/* Use Vidstack's built-in default layout */}
208
- {controls && (
209
- <DefaultVideoLayout
210
- icons={defaultLayoutIcons}
211
- thumbnails={posterUrl}
212
- />
213
- )}
214
- </MediaPlayer>
215
- </div>
216
-
217
- {/* Video Info */}
218
- {showInfo && source.title && (
219
- <div className="mt-4 space-y-2">
220
- <h3 className="text-xl font-semibold text-foreground">{source.title}</h3>
221
- {source.description && (
222
- <p className="text-muted-foreground">{source.description}</p>
223
- )}
224
- </div>
225
- )}
226
- </div>
227
- );
228
- });
199
+ }
200
+ );
229
201
 
230
202
  VideoPlayer.displayName = 'VideoPlayer';
231
-
@@ -0,0 +1,125 @@
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 { VideoSourceUnion, StreamSource } from './types';
11
+
12
+ // ============================================================================
13
+ // Context Types
14
+ // ============================================================================
15
+
16
+ export interface VideoPlayerContextValue {
17
+ /** Function to generate stream URL (for HTTP Range streaming) */
18
+ getStreamUrl?: (sessionId: string, path: string) => string;
19
+ /** Current session ID */
20
+ sessionId?: string | null;
21
+ }
22
+
23
+ export interface VideoPlayerProviderProps extends VideoPlayerContextValue {
24
+ children: React.ReactNode;
25
+ }
26
+
27
+ // ============================================================================
28
+ // Context
29
+ // ============================================================================
30
+
31
+ const VideoPlayerContext = createContext<VideoPlayerContextValue | null>(null);
32
+
33
+ /**
34
+ * Provider for VideoPlayer streaming configuration
35
+ *
36
+ * @example
37
+ * // In your app layout or FileWorkspace
38
+ * <VideoPlayerProvider
39
+ * sessionId={sessionId}
40
+ * getStreamUrl={terminalClient.terminal_media.streamStreamRetrieveUrl}
41
+ * >
42
+ * <VideoPlayer source={{ type: 'stream', path: '/video.mp4' }} />
43
+ * </VideoPlayerProvider>
44
+ */
45
+ export function VideoPlayerProvider({
46
+ children,
47
+ getStreamUrl,
48
+ sessionId,
49
+ }: VideoPlayerProviderProps) {
50
+ const value = useMemo(
51
+ () => ({ getStreamUrl, sessionId }),
52
+ [getStreamUrl, sessionId]
53
+ );
54
+
55
+ return (
56
+ <VideoPlayerContext.Provider value={value}>
57
+ {children}
58
+ </VideoPlayerContext.Provider>
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Hook to access VideoPlayer context
64
+ */
65
+ export function useVideoPlayerContext(): VideoPlayerContextValue | null {
66
+ return useContext(VideoPlayerContext);
67
+ }
68
+
69
+ // ============================================================================
70
+ // Simplified Stream Source
71
+ // ============================================================================
72
+
73
+ /** Simplified stream source (uses context for getStreamUrl) */
74
+ export interface SimpleStreamSource {
75
+ type: 'stream';
76
+ /** File path on server */
77
+ path: string;
78
+ /** Session ID (optional, uses context if not provided) */
79
+ sessionId?: string;
80
+ /** MIME type for the video */
81
+ mimeType?: string;
82
+ title?: string;
83
+ poster?: string;
84
+ }
85
+
86
+ /**
87
+ * Check if source is a simplified stream source (without getStreamUrl)
88
+ */
89
+ export function isSimpleStreamSource(
90
+ source: VideoSourceUnion | SimpleStreamSource
91
+ ): source is SimpleStreamSource {
92
+ return source.type === 'stream' && !('getStreamUrl' in source);
93
+ }
94
+
95
+ /**
96
+ * Resolve simplified stream source to full stream source using context
97
+ */
98
+ export function resolveStreamSource(
99
+ source: SimpleStreamSource,
100
+ context: VideoPlayerContextValue | null
101
+ ): StreamSource | null {
102
+ if (!context?.getStreamUrl) {
103
+ console.warn(
104
+ 'VideoPlayer: Stream source requires getStreamUrl. ' +
105
+ 'Either provide it in source or wrap with VideoPlayerProvider.'
106
+ );
107
+ return null;
108
+ }
109
+
110
+ const sessionId = source.sessionId || context.sessionId;
111
+ if (!sessionId) {
112
+ console.warn('VideoPlayer: Stream source requires sessionId.');
113
+ return null;
114
+ }
115
+
116
+ return {
117
+ type: 'stream',
118
+ sessionId,
119
+ path: source.path,
120
+ getStreamUrl: context.getStreamUrl,
121
+ mimeType: source.mimeType,
122
+ title: source.title,
123
+ poster: source.poster,
124
+ };
125
+ }
@@ -1,16 +1,68 @@
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';
6
+ // Main component
7
+ export { VideoPlayer } from './VideoPlayer';
8
+
9
+ // Controls (can be used standalone with Vidstack)
8
10
  export { VideoControls } from './VideoControls';
11
+
12
+ // Providers (for advanced usage)
13
+ export { VidstackProvider, NativeProvider, StreamProvider } from './providers';
14
+
15
+ // Context (for streaming configuration)
16
+ export {
17
+ VideoPlayerProvider,
18
+ useVideoPlayerContext,
19
+ isSimpleStreamSource,
20
+ resolveStreamSource,
21
+ } from './VideoPlayerContext';
22
+ export type {
23
+ VideoPlayerContextValue,
24
+ VideoPlayerProviderProps,
25
+ SimpleStreamSource,
26
+ } from './VideoPlayerContext';
27
+
28
+ // Error Fallback
29
+ export {
30
+ VideoErrorFallback,
31
+ createVideoErrorFallback,
32
+ } from './VideoErrorFallback';
33
+ export type {
34
+ VideoErrorFallbackProps,
35
+ CreateVideoErrorFallbackOptions,
36
+ } from './VideoErrorFallback';
37
+
38
+ // Types
9
39
  export type {
10
- VideoSource,
40
+ // Source types
41
+ VideoSourceUnion,
42
+ UrlSource,
43
+ YouTubeSource,
44
+ VimeoSource,
45
+ HLSSource,
46
+ DASHSource,
47
+ StreamSource,
48
+ BlobSource,
49
+ DataUrlSource,
50
+ // Player types
51
+ PlayerMode,
52
+ AspectRatioValue,
11
53
  VideoPlayerProps,
12
54
  VideoPlayerRef,
13
- NativePlayerProps,
14
- NativePlayerRef,
55
+ ErrorFallbackProps,
56
+ // Provider props (internal)
57
+ VidstackProviderProps,
58
+ NativeProviderProps,
59
+ StreamProviderProps,
60
+ // Common types
61
+ CommonPlayerSettings,
62
+ CommonPlayerEvents,
63
+ // File source helper types
64
+ ResolveFileSourceOptions,
15
65
  } from './types';
16
66
 
67
+ // Helpers
68
+ export { resolvePlayerMode, resolveFileSource } from './types';