@djangocfg/ui-nextjs 2.1.89 → 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 (161) hide show
  1. package/README.md +6 -15
  2. package/package.json +6 -25
  3. package/src/blocks/SplitHero/SplitHeroMedia.tsx +1 -1
  4. package/src/components/index.ts +0 -40
  5. package/src/hooks/index.ts +0 -6
  6. package/src/index.ts +2 -11
  7. package/src/components/button-download.tsx +0 -277
  8. package/src/components/markdown/MarkdownMessage.tsx +0 -340
  9. package/src/components/markdown/index.ts +0 -5
  10. package/src/components/menubar.tsx +0 -275
  11. package/src/components/multi-select-pro/async.tsx +0 -598
  12. package/src/components/multi-select-pro/helpers.tsx +0 -84
  13. package/src/components/multi-select-pro/index.tsx +0 -612
  14. package/src/components/navigation-menu.tsx +0 -154
  15. package/src/components/otp/index.tsx +0 -197
  16. package/src/components/otp/types.ts +0 -133
  17. package/src/components/otp/use-otp-input.ts +0 -225
  18. package/src/components/phone-input.tsx +0 -277
  19. package/src/components/sonner.tsx +0 -32
  20. package/src/hooks/useLocalStorage.ts +0 -300
  21. package/src/hooks/useSessionStorage.ts +0 -290
  22. package/src/lib/index.ts +0 -5
  23. package/src/lib/logger/index.ts +0 -10
  24. package/src/lib/logger/logStore.ts +0 -122
  25. package/src/lib/logger/logger.ts +0 -175
  26. package/src/lib/logger/types.ts +0 -82
  27. package/src/stores/index.ts +0 -8
  28. package/src/stores/mediaCache.ts +0 -534
  29. package/src/tools/AudioPlayer/README.md +0 -206
  30. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +0 -216
  31. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +0 -280
  32. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +0 -279
  33. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +0 -149
  34. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +0 -110
  35. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +0 -58
  36. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +0 -45
  37. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +0 -82
  38. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +0 -8
  39. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +0 -6
  40. package/src/tools/AudioPlayer/components/index.ts +0 -22
  41. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +0 -158
  42. package/src/tools/AudioPlayer/context/index.ts +0 -16
  43. package/src/tools/AudioPlayer/effects/index.ts +0 -412
  44. package/src/tools/AudioPlayer/hooks/index.ts +0 -35
  45. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +0 -387
  46. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +0 -95
  47. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +0 -207
  48. package/src/tools/AudioPlayer/index.ts +0 -133
  49. package/src/tools/AudioPlayer/types/effects.ts +0 -73
  50. package/src/tools/AudioPlayer/types/index.ts +0 -27
  51. package/src/tools/AudioPlayer/utils/debug.ts +0 -14
  52. package/src/tools/AudioPlayer/utils/formatTime.ts +0 -10
  53. package/src/tools/AudioPlayer/utils/index.ts +0 -6
  54. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +0 -71
  55. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +0 -121
  56. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +0 -143
  57. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +0 -261
  58. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +0 -427
  59. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -126
  60. package/src/tools/ImageViewer/README.md +0 -200
  61. package/src/tools/ImageViewer/components/ImageInfo.tsx +0 -44
  62. package/src/tools/ImageViewer/components/ImageToolbar.tsx +0 -150
  63. package/src/tools/ImageViewer/components/ImageViewer.tsx +0 -241
  64. package/src/tools/ImageViewer/components/index.ts +0 -7
  65. package/src/tools/ImageViewer/hooks/index.ts +0 -9
  66. package/src/tools/ImageViewer/hooks/useImageLoading.ts +0 -204
  67. package/src/tools/ImageViewer/hooks/useImageTransform.ts +0 -101
  68. package/src/tools/ImageViewer/index.ts +0 -60
  69. package/src/tools/ImageViewer/types.ts +0 -81
  70. package/src/tools/ImageViewer/utils/constants.ts +0 -59
  71. package/src/tools/ImageViewer/utils/debug.ts +0 -14
  72. package/src/tools/ImageViewer/utils/index.ts +0 -17
  73. package/src/tools/ImageViewer/utils/lqip.ts +0 -47
  74. package/src/tools/JsonForm/JsonSchemaForm.tsx +0 -197
  75. package/src/tools/JsonForm/examples/BotConfigExample.tsx +0 -249
  76. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +0 -161
  77. package/src/tools/JsonForm/index.ts +0 -46
  78. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +0 -47
  79. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +0 -74
  80. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +0 -107
  81. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +0 -35
  82. package/src/tools/JsonForm/templates/FieldTemplate.tsx +0 -62
  83. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +0 -116
  84. package/src/tools/JsonForm/templates/index.ts +0 -12
  85. package/src/tools/JsonForm/types.ts +0 -83
  86. package/src/tools/JsonForm/utils.ts +0 -213
  87. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +0 -37
  88. package/src/tools/JsonForm/widgets/ColorWidget.tsx +0 -219
  89. package/src/tools/JsonForm/widgets/NumberWidget.tsx +0 -89
  90. package/src/tools/JsonForm/widgets/SelectWidget.tsx +0 -97
  91. package/src/tools/JsonForm/widgets/SliderWidget.tsx +0 -148
  92. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +0 -35
  93. package/src/tools/JsonForm/widgets/TextWidget.tsx +0 -96
  94. package/src/tools/JsonForm/widgets/index.ts +0 -14
  95. package/src/tools/JsonTree/index.tsx +0 -243
  96. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +0 -213
  97. package/src/tools/LottiePlayer/index.tsx +0 -55
  98. package/src/tools/LottiePlayer/types.ts +0 -108
  99. package/src/tools/LottiePlayer/useLottie.ts +0 -164
  100. package/src/tools/Mermaid/Mermaid.client.tsx +0 -82
  101. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +0 -95
  102. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +0 -103
  103. package/src/tools/Mermaid/hooks/index.ts +0 -4
  104. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +0 -73
  105. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +0 -46
  106. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +0 -226
  107. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +0 -29
  108. package/src/tools/Mermaid/index.tsx +0 -41
  109. package/src/tools/Mermaid/utils/mermaid-helpers.ts +0 -33
  110. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +0 -149
  111. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +0 -263
  112. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +0 -125
  113. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +0 -100
  114. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +0 -157
  115. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +0 -253
  116. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +0 -173
  117. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +0 -68
  118. package/src/tools/OpenapiViewer/components/index.ts +0 -14
  119. package/src/tools/OpenapiViewer/constants.ts +0 -39
  120. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +0 -337
  121. package/src/tools/OpenapiViewer/hooks/index.ts +0 -8
  122. package/src/tools/OpenapiViewer/hooks/useMobile.ts +0 -10
  123. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +0 -199
  124. package/src/tools/OpenapiViewer/index.tsx +0 -38
  125. package/src/tools/OpenapiViewer/types.ts +0 -151
  126. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +0 -149
  127. package/src/tools/OpenapiViewer/utils/formatters.ts +0 -71
  128. package/src/tools/OpenapiViewer/utils/index.ts +0 -9
  129. package/src/tools/OpenapiViewer/utils/versionManager.ts +0 -161
  130. package/src/tools/PrettyCode/PrettyCode.client.tsx +0 -208
  131. package/src/tools/PrettyCode/index.tsx +0 -45
  132. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +0 -91
  133. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +0 -284
  134. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +0 -141
  135. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +0 -178
  136. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +0 -95
  137. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -139
  138. package/src/tools/VideoPlayer/README.md +0 -264
  139. package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
  140. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -174
  141. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
  142. package/src/tools/VideoPlayer/components/index.ts +0 -14
  143. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
  144. package/src/tools/VideoPlayer/context/index.ts +0 -8
  145. package/src/tools/VideoPlayer/hooks/index.ts +0 -12
  146. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -70
  147. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -116
  148. package/src/tools/VideoPlayer/index.ts +0 -77
  149. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
  150. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
  151. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -400
  152. package/src/tools/VideoPlayer/providers/index.ts +0 -8
  153. package/src/tools/VideoPlayer/types/index.ts +0 -38
  154. package/src/tools/VideoPlayer/types/player.ts +0 -116
  155. package/src/tools/VideoPlayer/types/provider.ts +0 -93
  156. package/src/tools/VideoPlayer/types/sources.ts +0 -97
  157. package/src/tools/VideoPlayer/utils/debug.ts +0 -14
  158. package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
  159. package/src/tools/VideoPlayer/utils/index.ts +0 -12
  160. package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
  161. package/src/tools/index.ts +0 -170
@@ -1,505 +0,0 @@
1
- /**
2
- * StreamProvider - HTTP Range streaming and Blob video player
3
- * Supports:
4
- * - HTTP Range requests with authorization (for large files)
5
- * - Blob/ArrayBuffer sources
6
- * - Data URL sources
7
- * - Fill parent container mode
8
- * - Custom error fallback
9
- */
10
-
11
- 'use client';
12
-
13
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
14
-
15
- import { cn } from '@djangocfg/ui-core/lib';
16
- import { Preloader, AspectRatio } from '@djangocfg/ui-core';
17
- import { useMediaCacheStore, generateContentKey } from '../../../stores/mediaCache';
18
- import { useVideoPlayerSettings } from '../hooks/useVideoPlayerSettings';
19
-
20
- import type { StreamProviderProps, VideoPlayerRef, StreamSource, BlobSource, DataUrlSource, ErrorFallbackProps } from '../types';
21
- import { videoDebug } from '../utils/debug';
22
-
23
- /** Default error fallback UI */
24
- function DefaultErrorFallback({ error }: ErrorFallbackProps) {
25
- return (
26
- <div className="absolute inset-0 flex flex-col items-center justify-center gap-4 text-white">
27
- <svg
28
- className="w-16 h-16 text-muted-foreground"
29
- fill="none"
30
- stroke="currentColor"
31
- viewBox="0 0 24 24"
32
- >
33
- <path
34
- strokeLinecap="round"
35
- strokeLinejoin="round"
36
- strokeWidth={2}
37
- 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"
38
- />
39
- </svg>
40
- <p className="text-lg">{error || 'Video cannot be previewed'}</p>
41
- </div>
42
- );
43
- }
44
-
45
- export const StreamProvider = forwardRef<VideoPlayerRef, StreamProviderProps>(
46
- (
47
- {
48
- source,
49
- aspectRatio = 16 / 9,
50
- autoPlay = false,
51
- muted = false,
52
- loop = false,
53
- playsInline = true,
54
- preload = 'metadata',
55
- controls = true,
56
- disableContextMenu = false,
57
- showPreloader = true,
58
- preloaderTimeout = 10000,
59
- className,
60
- videoClassName,
61
- errorFallback,
62
- onPlay,
63
- onPause,
64
- onEnded,
65
- onError,
66
- onLoadStart,
67
- onCanPlay,
68
- onTimeUpdate,
69
- onBufferProgress,
70
- },
71
- ref
72
- ) => {
73
- const [videoUrl, setVideoUrl] = useState<string | null>(null);
74
- const [isLoading, setIsLoading] = useState(true);
75
- const [hasError, setHasError] = useState(false);
76
- const [errorMessage, setErrorMessage] = useState<string>('Video cannot be previewed');
77
- const videoRef = useRef<HTMLVideoElement>(null);
78
- const contentKeyRef = useRef<string | null>(null);
79
- const lastSavedTimeRef = useRef<number>(0);
80
-
81
- // Get stable function references from store (not from hook to avoid re-renders)
82
- const getOrCreateBlobUrl = useMediaCacheStore.getState().getOrCreateBlobUrl;
83
- const releaseBlobUrl = useMediaCacheStore.getState().releaseBlobUrl;
84
- const getOrCreateStreamUrl = useMediaCacheStore.getState().getOrCreateStreamUrl;
85
- const saveVideoPosition = useMediaCacheStore.getState().saveVideoPosition;
86
- const getVideoPosition = useMediaCacheStore.getState().getVideoPosition;
87
-
88
- // Persisted player settings
89
- const { settings: savedSettings, updateVolume } = useVideoPlayerSettings();
90
-
91
- // Retry function for error fallback
92
- // Regenerates URL for stream sources to get fresh token/session
93
- const retry = useCallback(() => {
94
- setHasError(false);
95
- setIsLoading(true);
96
-
97
- // For stream sources, regenerate URL bypassing cache
98
- if (source.type === 'stream') {
99
- const streamSource = source as StreamSource;
100
- const freshUrl = streamSource.getStreamUrl(streamSource.sessionId, streamSource.path);
101
- setVideoUrl(freshUrl);
102
- return;
103
- }
104
-
105
- // For other sources, just reload
106
- const video = videoRef.current;
107
- if (video && videoUrl) {
108
- video.load();
109
- }
110
- }, [source, videoUrl]);
111
-
112
- // Expose video element methods via ref
113
- useImperativeHandle(
114
- ref,
115
- () => ({
116
- play: () => videoRef.current?.play(),
117
- pause: () => videoRef.current?.pause(),
118
- togglePlay: () => {
119
- const video = videoRef.current;
120
- if (video) {
121
- video.paused ? video.play() : video.pause();
122
- }
123
- },
124
- seekTo: (time: number) => {
125
- if (videoRef.current) videoRef.current.currentTime = time;
126
- },
127
- setVolume: (volume: number) => {
128
- if (videoRef.current) videoRef.current.volume = Math.max(0, Math.min(1, volume));
129
- },
130
- toggleMute: () => {
131
- if (videoRef.current) videoRef.current.muted = !videoRef.current.muted;
132
- },
133
- enterFullscreen: () => videoRef.current?.requestFullscreen(),
134
- exitFullscreen: () => document.exitFullscreen(),
135
- get currentTime() {
136
- return videoRef.current?.currentTime ?? 0;
137
- },
138
- get duration() {
139
- return videoRef.current?.duration ?? 0;
140
- },
141
- get paused() {
142
- return videoRef.current?.paused ?? true;
143
- },
144
- }),
145
- []
146
- );
147
-
148
- // Track unmount for cleanup
149
- const isMountedRef = useRef(true);
150
- useEffect(() => {
151
- isMountedRef.current = true;
152
- return () => {
153
- isMountedRef.current = false;
154
- // Release blob URL only on actual unmount
155
- if (contentKeyRef.current) {
156
- useMediaCacheStore.getState().releaseBlobUrl(contentKeyRef.current);
157
- contentKeyRef.current = null;
158
- }
159
- };
160
- }, []);
161
-
162
- // Create video URL based on source type with caching
163
- useEffect(() => {
164
- // Release previous blob URL if source changed
165
- if (contentKeyRef.current) {
166
- const newKey = source.type === 'blob'
167
- ? generateContentKey((source as BlobSource).data)
168
- : null;
169
- if (newKey !== contentKeyRef.current) {
170
- releaseBlobUrl(contentKeyRef.current);
171
- contentKeyRef.current = null;
172
- }
173
- }
174
-
175
- setHasError(false);
176
- setIsLoading(true);
177
-
178
- switch (source.type) {
179
- case 'stream': {
180
- const streamSource = source as StreamSource;
181
- // Use cached stream URL
182
- const url = getOrCreateStreamUrl(
183
- streamSource.sessionId,
184
- streamSource.path,
185
- streamSource.getStreamUrl
186
- );
187
- videoDebug.load(url, 'stream');
188
- setVideoUrl(url);
189
- break;
190
- }
191
-
192
- case 'blob': {
193
- const blobSource = source as BlobSource;
194
- // Generate content key for caching
195
- const contentKey = generateContentKey(blobSource.data);
196
- contentKeyRef.current = contentKey;
197
- // Use cached blob URL
198
- const url = getOrCreateBlobUrl(
199
- contentKey,
200
- blobSource.data,
201
- blobSource.mimeType || 'video/mp4'
202
- );
203
- videoDebug.load(url, 'blob');
204
- setVideoUrl(url);
205
- break;
206
- }
207
-
208
- case 'data-url': {
209
- const dataUrlSource = source as DataUrlSource;
210
- videoDebug.load(dataUrlSource.data.slice(0, 50) + '...', 'data-url');
211
- setVideoUrl(dataUrlSource.data);
212
- break;
213
- }
214
-
215
- default:
216
- videoDebug.error('Invalid video source type', { type: (source as { type: string }).type });
217
- setVideoUrl(null);
218
- setHasError(true);
219
- setErrorMessage('Invalid video source');
220
- }
221
-
222
- // No cleanup here - cleanup happens in unmount effect above
223
- // eslint-disable-next-line react-hooks/exhaustive-deps
224
- }, [source]);
225
-
226
- // Get source key for position caching
227
- const getSourceKey = useCallback(() => {
228
- switch (source.type) {
229
- case 'stream':
230
- return `stream:${(source as StreamSource).sessionId}:${(source as StreamSource).path}`;
231
- case 'blob':
232
- return contentKeyRef.current ? `blob:${contentKeyRef.current}` : null;
233
- case 'data-url':
234
- return `data:${(source as DataUrlSource).data.slice(0, 50)}`;
235
- default:
236
- return null;
237
- }
238
- }, [source]);
239
-
240
- // Restore cached playback position and settings when video is ready
241
- const handleCanPlay = useCallback(() => {
242
- const video = videoRef.current;
243
- if (video) {
244
- videoDebug.state('canplay', { duration: video.duration, buffered: video.buffered.length });
245
- videoDebug.buffer(video.buffered, video.duration);
246
-
247
- // Apply saved volume (user preference)
248
- video.volume = savedSettings.volume;
249
- }
250
- setIsLoading(false);
251
-
252
- // Restore position from cache
253
- const sourceKey = getSourceKey();
254
- if (sourceKey && video) {
255
- const cachedPosition = getVideoPosition(sourceKey);
256
- if (cachedPosition && cachedPosition > 0) {
257
- const duration = video.duration;
258
- // Only restore if position is valid (not at the end)
259
- if (cachedPosition < duration - 1) {
260
- videoDebug.debug(`Restoring position: ${cachedPosition}s`);
261
- video.currentTime = cachedPosition;
262
- }
263
- }
264
- }
265
-
266
- onCanPlay?.();
267
- // eslint-disable-next-line react-hooks/exhaustive-deps
268
- }, [getSourceKey, onCanPlay, savedSettings.volume]);
269
-
270
- // Save playback position periodically
271
- const handleTimeUpdate = useCallback(() => {
272
- const video = videoRef.current;
273
- if (!video) return;
274
-
275
- // Save position every 5 seconds
276
- const sourceKey = getSourceKey();
277
- if (sourceKey && video.currentTime > 0) {
278
- const timeSinceLastSave = video.currentTime - lastSavedTimeRef.current;
279
- if (timeSinceLastSave >= 5 || timeSinceLastSave < 0) {
280
- saveVideoPosition(sourceKey, video.currentTime);
281
- lastSavedTimeRef.current = video.currentTime;
282
- }
283
- }
284
-
285
- onTimeUpdate?.(video.currentTime, video.duration);
286
- // eslint-disable-next-line react-hooks/exhaustive-deps
287
- }, [getSourceKey, onTimeUpdate]);
288
-
289
- // Save position on pause
290
- const handlePause = useCallback(() => {
291
- const video = videoRef.current;
292
- const sourceKey = getSourceKey();
293
- if (sourceKey && video && video.currentTime > 0) {
294
- saveVideoPosition(sourceKey, video.currentTime);
295
- lastSavedTimeRef.current = video.currentTime;
296
- }
297
- onPause?.();
298
- // eslint-disable-next-line react-hooks/exhaustive-deps
299
- }, [getSourceKey, onPause]);
300
-
301
- // Handle buffer progress
302
- const handleProgress = useCallback(() => {
303
- const video = videoRef.current;
304
- if (!video || !onBufferProgress) return;
305
-
306
- // Get the buffered time ranges
307
- if (video.buffered.length > 0) {
308
- // Get the end of the last buffered range
309
- const bufferedEnd = video.buffered.end(video.buffered.length - 1);
310
- const duration = video.duration;
311
-
312
- if (duration > 0 && !isNaN(bufferedEnd)) {
313
- onBufferProgress(bufferedEnd, duration);
314
- }
315
- }
316
- }, [onBufferProgress]);
317
-
318
- // Preloader timeout
319
- useEffect(() => {
320
- if (!showPreloader || !isLoading) return;
321
-
322
- const timeout = setTimeout(() => {
323
- setIsLoading(false);
324
- }, preloaderTimeout);
325
-
326
- return () => clearTimeout(timeout);
327
- }, [showPreloader, isLoading, preloaderTimeout]);
328
-
329
- const handleContextMenu = (e: React.MouseEvent) => {
330
- if (disableContextMenu) {
331
- e.preventDefault();
332
- }
333
- };
334
-
335
- const handleLoadedData = () => {
336
- setIsLoading(false);
337
- };
338
-
339
- const handleError = () => {
340
- const video = videoRef.current;
341
- if (video) {
342
- videoDebug.error('Video error', { code: video.error?.code, message: video.error?.message });
343
- }
344
- setIsLoading(false);
345
- setHasError(true);
346
- setErrorMessage('Failed to load video');
347
- onError?.('Video playback error');
348
- };
349
-
350
- // Debug: Log video events
351
- useEffect(() => {
352
- const video = videoRef.current;
353
- if (!video) return;
354
-
355
- const handleLoadedMetadata = () => {
356
- videoDebug.state('loadedmetadata', { duration: video.duration });
357
- };
358
-
359
- const handleSeeking = () => {
360
- videoDebug.event('seeking', { currentTime: video.currentTime });
361
- };
362
-
363
- const handleSeeked = () => {
364
- videoDebug.event('seeked', { currentTime: video.currentTime });
365
- videoDebug.buffer(video.buffered, video.duration);
366
- };
367
-
368
- const handleWaiting = () => {
369
- videoDebug.warn('WAITING - buffering...');
370
- videoDebug.buffer(video.buffered, video.duration);
371
- };
372
-
373
- const handleStalled = () => {
374
- videoDebug.warn('STALLED - network issue');
375
- videoDebug.buffer(video.buffered, video.duration);
376
- };
377
-
378
- video.addEventListener('loadedmetadata', handleLoadedMetadata);
379
- video.addEventListener('seeking', handleSeeking);
380
- video.addEventListener('seeked', handleSeeked);
381
- video.addEventListener('waiting', handleWaiting);
382
- video.addEventListener('stalled', handleStalled);
383
-
384
- return () => {
385
- video.removeEventListener('loadedmetadata', handleLoadedMetadata);
386
- video.removeEventListener('seeking', handleSeeking);
387
- video.removeEventListener('seeked', handleSeeked);
388
- video.removeEventListener('waiting', handleWaiting);
389
- video.removeEventListener('stalled', handleStalled);
390
- };
391
- }, [videoUrl]);
392
-
393
- // Persist volume when user changes it via native controls
394
- useEffect(() => {
395
- const video = videoRef.current;
396
- if (!video) return;
397
-
398
- const handleVolumeChange = () => {
399
- updateVolume(video.volume);
400
- };
401
-
402
- video.addEventListener('volumechange', handleVolumeChange);
403
- return () => video.removeEventListener('volumechange', handleVolumeChange);
404
- }, [videoUrl, updateVolume]);
405
-
406
- // Determine if we should use AspectRatio wrapper or fill mode
407
- const isFillMode = aspectRatio === 'fill';
408
- const computedAspectRatio = aspectRatio === 'auto' || aspectRatio === 'fill' ? undefined : aspectRatio;
409
-
410
- // Render error fallback
411
- const renderErrorFallback = () => {
412
- const fallbackProps: ErrorFallbackProps = { error: errorMessage, retry };
413
-
414
- if (typeof errorFallback === 'function') {
415
- return errorFallback(fallbackProps);
416
- }
417
-
418
- if (errorFallback) {
419
- return errorFallback;
420
- }
421
-
422
- return <DefaultErrorFallback {...fallbackProps} />;
423
- };
424
-
425
- // Error state
426
- if (!videoUrl || hasError) {
427
- if (isFillMode) {
428
- return (
429
- <div className={cn('relative w-full h-full overflow-hidden bg-black', className)}>
430
- {renderErrorFallback()}
431
- </div>
432
- );
433
- }
434
-
435
- return (
436
- <div className={cn('relative overflow-hidden bg-black', className)}>
437
- <AspectRatio ratio={computedAspectRatio}>
438
- {renderErrorFallback()}
439
- </AspectRatio>
440
- </div>
441
- );
442
- }
443
-
444
- // Video content
445
- const videoContent = (
446
- <>
447
- {/* Loading indicator */}
448
- {showPreloader && isLoading && (
449
- <div className="absolute inset-0 flex items-center justify-center bg-black/50 z-10">
450
- <Preloader size="lg" spinnerClassName="text-white" />
451
- </div>
452
- )}
453
-
454
- {/* Video element */}
455
- <video
456
- ref={videoRef}
457
- src={videoUrl}
458
- className={cn(
459
- 'w-full h-full object-contain',
460
- isLoading && 'opacity-0',
461
- videoClassName
462
- )}
463
- autoPlay={autoPlay}
464
- muted={muted}
465
- loop={loop}
466
- playsInline={playsInline}
467
- preload={preload}
468
- controls={controls}
469
- crossOrigin="anonymous"
470
- poster={source.poster}
471
- onContextMenu={handleContextMenu}
472
- onLoadStart={onLoadStart}
473
- onCanPlay={handleCanPlay}
474
- onLoadedData={handleLoadedData}
475
- onPlay={onPlay}
476
- onPause={handlePause}
477
- onEnded={onEnded}
478
- onError={handleError}
479
- onTimeUpdate={handleTimeUpdate}
480
- onProgress={handleProgress}
481
- />
482
- </>
483
- );
484
-
485
- // Fill mode - no AspectRatio wrapper
486
- if (isFillMode) {
487
- return (
488
- <div className={cn('relative w-full h-full overflow-hidden bg-black', className)}>
489
- {videoContent}
490
- </div>
491
- );
492
- }
493
-
494
- // Normal mode with AspectRatio
495
- return (
496
- <div className={cn('relative overflow-hidden bg-black', className)}>
497
- <AspectRatio ratio={computedAspectRatio}>
498
- {videoContent}
499
- </AspectRatio>
500
- </div>
501
- );
502
- }
503
- );
504
-
505
- StreamProvider.displayName = 'StreamProvider';