@djangocfg/ui-tools 2.1.91

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 (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. package/src/tools/index.ts +172 -0
@@ -0,0 +1,400 @@
1
+ /**
2
+ * VidstackProvider - Full-featured video player using Vidstack
3
+ * Supports YouTube, Vimeo, HLS, DASH, and direct URLs
4
+ */
5
+
6
+ 'use client';
7
+
8
+ // Import Vidstack base styles
9
+ import '@vidstack/react/player/styles/base.css';
10
+ import '@vidstack/react/player/styles/default/theme.css';
11
+ import '@vidstack/react/player/styles/default/layouts/video.css';
12
+
13
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
14
+
15
+ import { cn, generateOgImageUrl } from '@djangocfg/ui-core/lib';
16
+ import { MediaPlayer, MediaProvider, Poster } from '@vidstack/react';
17
+ import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
18
+ import { useVideoCache } from '../../../stores/mediaCache';
19
+
20
+ import type { MediaPlayerInstance, PlayerSrc } from '@vidstack/react';
21
+ import type { VidstackProviderProps, VideoPlayerRef, ErrorFallbackProps } from '../types';
22
+ import { videoDebug } from '../utils/debug';
23
+
24
+ /**
25
+ * Convert source to Vidstack-compatible format
26
+ * Returns object with explicit type for HLS/DASH to ensure proper loader selection
27
+ */
28
+ function getVidstackSrc(source: VidstackProviderProps['source']): PlayerSrc {
29
+ switch (source.type) {
30
+ case 'youtube':
31
+ return `youtube/${source.id}`;
32
+ case 'vimeo':
33
+ return `vimeo/${source.id}`;
34
+ case 'hls':
35
+ // Explicit type needed because URL may have query params that hide .m3u8 extension
36
+ return { src: source.url, type: 'application/x-mpegurl' } as PlayerSrc;
37
+ case 'dash':
38
+ return { src: source.url, type: 'application/dash+xml' } as PlayerSrc;
39
+ case 'url':
40
+ return source.url;
41
+ default:
42
+ return '';
43
+ }
44
+ }
45
+
46
+ /** Default error fallback UI */
47
+ function DefaultErrorFallback({ error }: ErrorFallbackProps) {
48
+ return (
49
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-4 text-white bg-black">
50
+ <svg
51
+ className="w-16 h-16 text-muted-foreground"
52
+ fill="none"
53
+ stroke="currentColor"
54
+ viewBox="0 0 24 24"
55
+ >
56
+ <path
57
+ strokeLinecap="round"
58
+ strokeLinejoin="round"
59
+ strokeWidth={2}
60
+ d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
61
+ />
62
+ </svg>
63
+ <p className="text-lg">{error || 'Video cannot be played'}</p>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ export const VidstackProvider = forwardRef<VideoPlayerRef, VidstackProviderProps>(
69
+ (
70
+ {
71
+ source,
72
+ aspectRatio = 16 / 9,
73
+ autoPlay = false,
74
+ muted = false,
75
+ loop = false,
76
+ playsInline = true,
77
+ controls = true,
78
+ className,
79
+ showInfo = false,
80
+ theme = 'default',
81
+ errorFallback,
82
+ onPlay,
83
+ onPause,
84
+ onEnded,
85
+ onError,
86
+ onLoadStart,
87
+ onCanPlay,
88
+ onTimeUpdate,
89
+ },
90
+ ref
91
+ ) => {
92
+ const playerRef = useRef<MediaPlayerInstance | null>(null);
93
+ const [hasError, setHasError] = useState(false);
94
+ const [errorMessage, setErrorMessage] = useState<string>('Video cannot be played');
95
+ const lastSavedTimeRef = useRef<number>(0);
96
+ const hasRestoredPositionRef = useRef(false);
97
+
98
+ // Cache hooks
99
+ const {
100
+ getPosterUrl,
101
+ cachePosterUrl,
102
+ saveVideoPosition,
103
+ getVideoPosition,
104
+ } = useVideoCache();
105
+
106
+ // Get source key for position caching
107
+ const sourceKey = useMemo(() => {
108
+ switch (source.type) {
109
+ case 'youtube':
110
+ return `youtube:${source.id}`;
111
+ case 'vimeo':
112
+ return `vimeo:${source.id}`;
113
+ case 'hls':
114
+ case 'dash':
115
+ case 'url':
116
+ return `url:${source.url}`;
117
+ default:
118
+ return null;
119
+ }
120
+ }, [source]);
121
+
122
+ // Generate poster if not provided, with caching
123
+ const posterUrl = useMemo(() => {
124
+ if (source.poster) return source.poster;
125
+ if (!source.title) return undefined;
126
+
127
+ // Check cache first
128
+ const cached = getPosterUrl(source.title);
129
+ if (cached) return cached;
130
+
131
+ // Generate and cache
132
+ const url = generateOgImageUrl({ title: source.title });
133
+ cachePosterUrl(source.title, url);
134
+ return url;
135
+ }, [source.poster, source.title, getPosterUrl, cachePosterUrl]);
136
+
137
+ // Get Vidstack-compatible source URL
138
+ const vidstackSrc = useMemo(() => getVidstackSrc(source), [source]);
139
+
140
+ // Debug: Log video source
141
+ useEffect(() => {
142
+ const srcString = typeof vidstackSrc === 'string' ? vidstackSrc : (vidstackSrc as { src: string }).src;
143
+ videoDebug.load(srcString, source.type);
144
+ }, [vidstackSrc, source.type]);
145
+
146
+ // Retry function
147
+ const retry = useCallback(() => {
148
+ setHasError(false);
149
+ setErrorMessage('Video cannot be played');
150
+ // Force reload by updating key would be needed, but for now just reset state
151
+ const player = playerRef.current;
152
+ if (player) {
153
+ player.currentTime = 0;
154
+ player.play();
155
+ }
156
+ }, []);
157
+
158
+ // Expose player methods via ref
159
+ useImperativeHandle(
160
+ ref,
161
+ () => {
162
+ const player = playerRef.current;
163
+
164
+ return {
165
+ play: () => player?.play(),
166
+ pause: () => player?.pause(),
167
+ togglePlay: () => {
168
+ if (player) {
169
+ player.paused ? player.play() : player.pause();
170
+ }
171
+ },
172
+ seekTo: (time: number) => {
173
+ if (player) player.currentTime = time;
174
+ },
175
+ setVolume: (volume: number) => {
176
+ if (player) player.volume = Math.max(0, Math.min(1, volume));
177
+ },
178
+ toggleMute: () => {
179
+ if (player) player.muted = !player.muted;
180
+ },
181
+ enterFullscreen: () => player?.enterFullscreen(),
182
+ exitFullscreen: () => player?.exitFullscreen(),
183
+ get currentTime() {
184
+ return player?.currentTime ?? 0;
185
+ },
186
+ get duration() {
187
+ return player?.duration ?? 0;
188
+ },
189
+ get paused() {
190
+ return player?.paused ?? true;
191
+ },
192
+ };
193
+ },
194
+ []
195
+ );
196
+
197
+ const handlePlay = () => onPlay?.();
198
+
199
+ const handlePause = useCallback(() => {
200
+ // Save position on pause
201
+ const player = playerRef.current;
202
+ if (sourceKey && player && player.currentTime > 0) {
203
+ saveVideoPosition(sourceKey, player.currentTime);
204
+ lastSavedTimeRef.current = player.currentTime;
205
+ }
206
+ onPause?.();
207
+ }, [sourceKey, saveVideoPosition, onPause]);
208
+
209
+ const handleEnded = () => onEnded?.();
210
+
211
+ const handleError = (detail: unknown) => {
212
+ const error = detail as { message?: string };
213
+ const msg = error?.message || 'Video playback error';
214
+ videoDebug.error('Vidstack error', { message: msg });
215
+ setHasError(true);
216
+ setErrorMessage(msg);
217
+ onError?.(msg);
218
+ };
219
+
220
+ const handleLoadStart = () => onLoadStart?.();
221
+
222
+ const handleCanPlay = useCallback(() => {
223
+ const player = playerRef.current;
224
+ if (player) {
225
+ videoDebug.state('canplay', { duration: player.duration });
226
+ // Log buffer state if media element is available
227
+ const mediaEl = (player.provider as { media?: HTMLVideoElement } | null)?.media;
228
+ if (mediaEl?.buffered) {
229
+ videoDebug.buffer(mediaEl.buffered, player.duration);
230
+ }
231
+ }
232
+ setHasError(false);
233
+
234
+ // Restore position from cache (only once per source)
235
+ if (sourceKey && player && !hasRestoredPositionRef.current) {
236
+ const cachedPosition = getVideoPosition(sourceKey);
237
+ if (cachedPosition && cachedPosition > 0) {
238
+ const duration = player.duration;
239
+ // Only restore if position is valid (not at the end)
240
+ if (cachedPosition < duration - 1) {
241
+ videoDebug.debug(`Restoring position: ${cachedPosition}s`);
242
+ player.currentTime = cachedPosition;
243
+ }
244
+ }
245
+ hasRestoredPositionRef.current = true;
246
+ }
247
+
248
+ onCanPlay?.();
249
+ }, [sourceKey, getVideoPosition, onCanPlay]);
250
+
251
+ const handleTimeUpdate = useCallback(() => {
252
+ const player = playerRef.current;
253
+ if (!player) return;
254
+
255
+ // Save position every 5 seconds
256
+ if (sourceKey && player.currentTime > 0) {
257
+ const timeSinceLastSave = player.currentTime - lastSavedTimeRef.current;
258
+ if (timeSinceLastSave >= 5 || timeSinceLastSave < 0) {
259
+ saveVideoPosition(sourceKey, player.currentTime);
260
+ lastSavedTimeRef.current = player.currentTime;
261
+ }
262
+ }
263
+
264
+ onTimeUpdate?.(player.currentTime, player.duration);
265
+ }, [sourceKey, saveVideoPosition, onTimeUpdate]);
266
+
267
+ // Reset position restoration flag when source changes
268
+ useEffect(() => {
269
+ hasRestoredPositionRef.current = false;
270
+ lastSavedTimeRef.current = 0;
271
+ }, [sourceKey]);
272
+
273
+ // Debug: Log player events
274
+ useEffect(() => {
275
+ const player = playerRef.current;
276
+ if (!player) return;
277
+
278
+ const handleSeeking = () => {
279
+ videoDebug.event('seeking', { currentTime: player.currentTime });
280
+ };
281
+
282
+ const handleSeeked = () => {
283
+ videoDebug.event('seeked', { currentTime: player.currentTime });
284
+ const mediaEl = (player.provider as { media?: HTMLVideoElement } | null)?.media;
285
+ if (mediaEl?.buffered) {
286
+ videoDebug.buffer(mediaEl.buffered, player.duration);
287
+ }
288
+ };
289
+
290
+ const handleWaiting = () => {
291
+ videoDebug.warn('WAITING - buffering...');
292
+ const mediaEl = (player.provider as { media?: HTMLVideoElement } | null)?.media;
293
+ if (mediaEl?.buffered) {
294
+ videoDebug.buffer(mediaEl.buffered, player.duration);
295
+ }
296
+ };
297
+
298
+ const handleStalled = () => {
299
+ videoDebug.warn('STALLED - network issue');
300
+ const mediaEl = (player.provider as { media?: HTMLVideoElement } | null)?.media;
301
+ if (mediaEl?.buffered) {
302
+ videoDebug.buffer(mediaEl.buffered, player.duration);
303
+ }
304
+ };
305
+
306
+ player.addEventListener('seeking', handleSeeking);
307
+ player.addEventListener('seeked', handleSeeked);
308
+ player.addEventListener('waiting', handleWaiting);
309
+ player.addEventListener('stalled', handleStalled);
310
+
311
+ return () => {
312
+ player.removeEventListener('seeking', handleSeeking);
313
+ player.removeEventListener('seeked', handleSeeked);
314
+ player.removeEventListener('waiting', handleWaiting);
315
+ player.removeEventListener('stalled', handleStalled);
316
+ };
317
+ }, [vidstackSrc]);
318
+
319
+ // Determine layout mode
320
+ const isFillMode = aspectRatio === 'fill';
321
+ const computedAspectRatio = aspectRatio === 'auto' || aspectRatio === 'fill' ? undefined : aspectRatio;
322
+
323
+ // Render error fallback
324
+ const renderErrorFallback = () => {
325
+ const fallbackProps: ErrorFallbackProps = { error: errorMessage, retry };
326
+
327
+ if (typeof errorFallback === 'function') {
328
+ return errorFallback(fallbackProps);
329
+ }
330
+
331
+ if (errorFallback) {
332
+ return errorFallback;
333
+ }
334
+
335
+ return <DefaultErrorFallback {...fallbackProps} />;
336
+ };
337
+
338
+ // Container styles based on mode
339
+ const containerStyles = isFillMode
340
+ ? { width: '100%', height: '100%' }
341
+ : { aspectRatio: computedAspectRatio };
342
+
343
+ return (
344
+ <div className={cn(isFillMode ? 'w-full h-full' : 'w-full', className)}>
345
+ <div
346
+ className={cn(
347
+ 'relative w-full rounded-sm bg-black overflow-hidden',
348
+ isFillMode && 'h-full',
349
+ theme === 'minimal' && 'rounded-none',
350
+ theme === 'modern' && 'rounded-xl shadow-2xl'
351
+ )}
352
+ style={containerStyles}
353
+ >
354
+ {hasError ? (
355
+ renderErrorFallback()
356
+ ) : (
357
+ <MediaPlayer
358
+ ref={playerRef}
359
+ title={source.title || 'Video'}
360
+ src={vidstackSrc}
361
+ autoPlay={autoPlay}
362
+ muted={muted}
363
+ loop={loop}
364
+ playsInline={playsInline}
365
+ onPlay={handlePlay}
366
+ onPause={handlePause}
367
+ onEnded={handleEnded}
368
+ onError={handleError}
369
+ onLoadStart={handleLoadStart}
370
+ onCanPlay={handleCanPlay}
371
+ onTimeUpdate={handleTimeUpdate}
372
+ className="w-full h-full"
373
+ >
374
+ <MediaProvider />
375
+
376
+ {posterUrl && (
377
+ <Poster
378
+ className="vds-poster"
379
+ src={posterUrl}
380
+ alt={source.title || 'Video poster'}
381
+ style={{ objectFit: 'cover' }}
382
+ />
383
+ )}
384
+
385
+ {controls && <DefaultVideoLayout icons={defaultLayoutIcons} thumbnails={posterUrl} />}
386
+ </MediaPlayer>
387
+ )}
388
+ </div>
389
+
390
+ {showInfo && source.title && (
391
+ <div className="mt-4 space-y-2">
392
+ <h3 className="text-xl font-semibold text-foreground">{source.title}</h3>
393
+ </div>
394
+ )}
395
+ </div>
396
+ );
397
+ }
398
+ );
399
+
400
+ VidstackProvider.displayName = 'VidstackProvider';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * VideoPlayer Providers
3
+ * Each provider handles a specific type of video playback
4
+ */
5
+
6
+ export { VidstackProvider } from './VidstackProvider';
7
+ export { NativeProvider } from './NativeProvider';
8
+ export { StreamProvider } from './StreamProvider';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * VideoPlayer types - Public API
3
+ */
4
+
5
+ // Source types
6
+ export type {
7
+ UrlSource,
8
+ YouTubeSource,
9
+ VimeoSource,
10
+ HLSSource,
11
+ DASHSource,
12
+ StreamSource,
13
+ BlobSource,
14
+ DataUrlSource,
15
+ VideoSourceUnion,
16
+ } from './sources';
17
+
18
+ // Player types
19
+ export type {
20
+ PlayerMode,
21
+ AspectRatioValue,
22
+ CommonPlayerSettings,
23
+ CommonPlayerEvents,
24
+ ErrorFallbackProps,
25
+ VideoPlayerProps,
26
+ VideoPlayerRef,
27
+ } from './player';
28
+
29
+ // Provider types
30
+ export type {
31
+ VidstackProviderProps,
32
+ NativeProviderProps,
33
+ StreamProviderProps,
34
+ ResolveFileSourceOptions,
35
+ VideoPlayerContextValue,
36
+ VideoPlayerProviderProps,
37
+ SimpleStreamSource,
38
+ } from './provider';
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Player configuration types
3
+ */
4
+
5
+ import type React from 'react';
6
+ import type { VideoSourceUnion } from './sources';
7
+
8
+ // =============================================================================
9
+ // Player Mode
10
+ // =============================================================================
11
+
12
+ /** Player mode - determines which provider to use */
13
+ export type PlayerMode =
14
+ | 'auto' // Auto-select based on source type
15
+ | 'vidstack' // Force Vidstack (full-featured)
16
+ | 'native' // Force native HTML5 <video>
17
+ | 'streaming'; // Force streaming provider
18
+
19
+ // =============================================================================
20
+ // Player Props
21
+ // =============================================================================
22
+
23
+ /** Aspect ratio options */
24
+ export type AspectRatioValue = number | 'auto' | 'fill';
25
+
26
+ /** Common player settings */
27
+ export interface CommonPlayerSettings {
28
+ /** Auto-play video */
29
+ autoPlay?: boolean;
30
+ /** Mute video by default */
31
+ muted?: boolean;
32
+ /** Loop video */
33
+ loop?: boolean;
34
+ /** Play video inline on mobile */
35
+ playsInline?: boolean;
36
+ /** Show player controls */
37
+ controls?: boolean;
38
+ /**
39
+ * Aspect ratio:
40
+ * - number (e.g. 16/9): Fixed aspect ratio
41
+ * - 'auto': Natural video aspect ratio
42
+ * - 'fill': Fill parent container (100% width & height)
43
+ */
44
+ aspectRatio?: AspectRatioValue;
45
+ /** Preload strategy */
46
+ preload?: 'auto' | 'metadata' | 'none';
47
+ }
48
+
49
+ /** Common player events */
50
+ export interface CommonPlayerEvents {
51
+ onPlay?: () => void;
52
+ onPause?: () => void;
53
+ onEnded?: () => void;
54
+ onError?: (error: string) => void;
55
+ onLoadStart?: () => void;
56
+ onCanPlay?: () => void;
57
+ onTimeUpdate?: (currentTime: number, duration: number) => void;
58
+ /** Called when buffering progress changes (buffered seconds, total duration) */
59
+ onBufferProgress?: (buffered: number, duration: number) => void;
60
+ }
61
+
62
+ /** Error fallback render props */
63
+ export interface ErrorFallbackProps {
64
+ error: string;
65
+ retry?: () => void;
66
+ }
67
+
68
+ /** Main VideoPlayer props */
69
+ export interface VideoPlayerProps extends CommonPlayerSettings, CommonPlayerEvents {
70
+ /** Video source configuration */
71
+ source: VideoSourceUnion;
72
+ /** Player mode (default: 'auto') */
73
+ mode?: PlayerMode;
74
+ /** Player theme (Vidstack only) */
75
+ theme?: 'default' | 'minimal' | 'modern';
76
+ /** Show video info below player */
77
+ showInfo?: boolean;
78
+ /** Container className */
79
+ className?: string;
80
+ /** Video element className (native/streaming only) */
81
+ videoClassName?: string;
82
+ /** Disable right-click context menu */
83
+ disableContextMenu?: boolean;
84
+ /** Show loading spinner */
85
+ showPreloader?: boolean;
86
+ /** Preloader timeout in ms */
87
+ preloaderTimeout?: number;
88
+ /** Custom error fallback UI */
89
+ errorFallback?: React.ReactNode | ((props: ErrorFallbackProps) => React.ReactNode);
90
+ }
91
+
92
+ /** VideoPlayer ref methods */
93
+ export interface VideoPlayerRef {
94
+ /** Play video */
95
+ play: () => Promise<void> | void;
96
+ /** Pause video */
97
+ pause: () => void;
98
+ /** Toggle play/pause */
99
+ togglePlay: () => void;
100
+ /** Seek to time in seconds */
101
+ seekTo: (time: number) => void;
102
+ /** Set volume (0-1) */
103
+ setVolume: (volume: number) => void;
104
+ /** Toggle mute */
105
+ toggleMute: () => void;
106
+ /** Enter fullscreen */
107
+ enterFullscreen: () => void;
108
+ /** Exit fullscreen */
109
+ exitFullscreen: () => void;
110
+ /** Current playback time in seconds */
111
+ readonly currentTime: number;
112
+ /** Video duration in seconds */
113
+ readonly duration: number;
114
+ /** Whether video is paused */
115
+ readonly paused: boolean;
116
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Provider-specific types
3
+ */
4
+
5
+ import type React from 'react';
6
+ import type { VideoSourceUnion, UrlSource, YouTubeSource, VimeoSource, HLSSource, DASHSource, StreamSource, BlobSource, DataUrlSource } from './sources';
7
+ import type { CommonPlayerSettings, CommonPlayerEvents, ErrorFallbackProps } from './player';
8
+
9
+ // =============================================================================
10
+ // Provider Props
11
+ // =============================================================================
12
+
13
+ /** Props passed to Vidstack provider */
14
+ export interface VidstackProviderProps extends CommonPlayerSettings, CommonPlayerEvents {
15
+ source: UrlSource | YouTubeSource | VimeoSource | HLSSource | DASHSource;
16
+ theme?: 'default' | 'minimal' | 'modern';
17
+ showInfo?: boolean;
18
+ className?: string;
19
+ errorFallback?: React.ReactNode | ((props: ErrorFallbackProps) => React.ReactNode);
20
+ }
21
+
22
+ /** Props passed to Native provider */
23
+ export interface NativeProviderProps extends CommonPlayerSettings, CommonPlayerEvents {
24
+ source: UrlSource | DataUrlSource;
25
+ className?: string;
26
+ videoClassName?: string;
27
+ disableContextMenu?: boolean;
28
+ showPreloader?: boolean;
29
+ preloaderTimeout?: number;
30
+ }
31
+
32
+ /** Props passed to Stream provider */
33
+ export interface StreamProviderProps extends CommonPlayerSettings, CommonPlayerEvents {
34
+ source: StreamSource | BlobSource | DataUrlSource;
35
+ className?: string;
36
+ videoClassName?: string;
37
+ disableContextMenu?: boolean;
38
+ showPreloader?: boolean;
39
+ preloaderTimeout?: number;
40
+ errorFallback?: React.ReactNode | ((props: ErrorFallbackProps) => React.ReactNode);
41
+ }
42
+
43
+ // =============================================================================
44
+ // File Source Helper Types
45
+ // =============================================================================
46
+
47
+ /** Options for resolving file source */
48
+ export interface ResolveFileSourceOptions {
49
+ /** File content - can be data URL string or binary ArrayBuffer */
50
+ content: string | ArrayBuffer | null | undefined;
51
+ /** File path for streaming */
52
+ path: string;
53
+ /** MIME type of the file */
54
+ mimeType?: string;
55
+ /** Session ID for authenticated streaming */
56
+ sessionId?: string | null;
57
+ /** Load method hint - 'http_stream' enables streaming mode */
58
+ loadMethod?: 'http_stream' | 'rpc' | 'unspecified' | 'skip' | string;
59
+ /** Function to generate stream URL (required for streaming) */
60
+ getStreamUrl?: (sessionId: string, path: string) => string;
61
+ /** Optional title */
62
+ title?: string;
63
+ /** Optional poster */
64
+ poster?: string;
65
+ }
66
+
67
+ // =============================================================================
68
+ // Context Types
69
+ // =============================================================================
70
+
71
+ export interface VideoPlayerContextValue {
72
+ /** Function to generate stream URL (for HTTP Range streaming) */
73
+ getStreamUrl?: (sessionId: string, path: string) => string;
74
+ /** Current session ID */
75
+ sessionId?: string | null;
76
+ }
77
+
78
+ export interface VideoPlayerProviderProps extends VideoPlayerContextValue {
79
+ children: React.ReactNode;
80
+ }
81
+
82
+ /** Simplified stream source (uses context for getStreamUrl) */
83
+ export interface SimpleStreamSource {
84
+ type: 'stream';
85
+ /** File path on server */
86
+ path: string;
87
+ /** Session ID (optional, uses context if not provided) */
88
+ sessionId?: string;
89
+ /** MIME type for the video */
90
+ mimeType?: string;
91
+ title?: string;
92
+ poster?: string;
93
+ }