@therealtinhtute/baroiplayer 1.1.0 → 1.2.1

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 (142) hide show
  1. package/README.md +1 -2
  2. package/dist/context/media.js +1 -2
  3. package/dist/context/media.mjs +3 -15
  4. package/dist/context/store.js +1 -2
  5. package/dist/context/store.mjs +3 -5
  6. package/dist/context-Bzd5QCKM.cjs +7 -0
  7. package/dist/context-Bzd5QCKM.cjs.map +1 -0
  8. package/dist/context-DD_SH4tT.js +1053 -0
  9. package/dist/context-DD_SH4tT.js.map +1 -0
  10. package/dist/core-events-BRnLMB7s.cjs +2 -0
  11. package/dist/core-events-BRnLMB7s.cjs.map +1 -0
  12. package/dist/core-events-v_mKdj5p.js +417 -0
  13. package/dist/core-events-v_mKdj5p.js.map +1 -0
  14. package/dist/core-plugins-CMBv-yVh.cjs +2 -0
  15. package/dist/core-plugins-CMBv-yVh.cjs.map +1 -0
  16. package/dist/core-plugins-CqCxLkbd.js +754 -0
  17. package/dist/core-plugins-CqCxLkbd.js.map +1 -0
  18. package/dist/core.js +7 -29
  19. package/dist/core.js.map +1 -1
  20. package/dist/core.mjs +947 -1220
  21. package/dist/core.mjs.map +1 -1
  22. package/dist/enhanced-components-BwjBqSA1.js +2858 -0
  23. package/dist/enhanced-components-BwjBqSA1.js.map +1 -0
  24. package/dist/enhanced-components-C-ka-XLp.cjs +2 -0
  25. package/dist/enhanced-components-C-ka-XLp.cjs.map +1 -0
  26. package/dist/hooks-B-IIWPJr.js +1842 -0
  27. package/dist/hooks-B-IIWPJr.js.map +1 -0
  28. package/dist/hooks-DcHzmBwA.cjs +4 -0
  29. package/dist/hooks-DcHzmBwA.cjs.map +1 -0
  30. package/dist/index.js +27 -97
  31. package/dist/index.js.map +1 -1
  32. package/dist/index.mjs +5613 -6210
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/minimal-components-BYsMQde5.js +1610 -0
  35. package/dist/minimal-components-BYsMQde5.js.map +1 -0
  36. package/dist/minimal-components-CsxQbDp7.cjs +2 -0
  37. package/dist/minimal-components-CsxQbDp7.cjs.map +1 -0
  38. package/dist/performance-monitor-DCSijLJg.js +312 -0
  39. package/dist/performance-monitor-DCSijLJg.js.map +1 -0
  40. package/dist/performance-monitor-wNFKmqMr.cjs +2 -0
  41. package/dist/performance-monitor-wNFKmqMr.cjs.map +1 -0
  42. package/dist/rolldown-runtime-BmowowmI.js +14 -0
  43. package/dist/rolldown-runtime-mLOUI7ql.cjs +1 -0
  44. package/dist/utils-D2gQ7c2R.js +64 -0
  45. package/dist/utils-D2gQ7c2R.js.map +1 -0
  46. package/dist/utils-DJaAwiZ9.cjs +2 -0
  47. package/dist/utils-DJaAwiZ9.cjs.map +1 -0
  48. package/dist/utils.js +1 -2
  49. package/dist/utils.mjs +3 -11
  50. package/package.json +28 -22
  51. package/src/components/AnimateLoading.tsx +14 -10
  52. package/src/components/accessibility/keyboard-help-overlay.tsx +2 -0
  53. package/src/components/enhanced/debug-panel.tsx +1 -1
  54. package/src/components/enhanced/enhanced-media-player.tsx +68 -25
  55. package/src/components/examples/loading-states-demo.tsx +1 -2
  56. package/src/components/examples/react-19-demo.tsx +6 -10
  57. package/src/components/examples/volume-orientation-demo.tsx +1 -2
  58. package/src/components/legacy.tsx +6 -6
  59. package/src/components/media-player-video.tsx +5 -12
  60. package/src/components/media-player.tsx +2 -2
  61. package/src/components/ui/blur-poster.tsx +18 -15
  62. package/src/components/ui/enhanced-seek-bar.tsx +2 -2
  63. package/src/components/ui/media-resource-preloader.tsx +21 -23
  64. package/src/components/unified/base-media-player.tsx +22 -10
  65. package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +58 -38
  66. package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +6 -6
  67. package/src/components/variants/minimal/components/VideoElement.tsx +3 -1
  68. package/src/core/optimization/bundle-optimizer.tsx +26 -27
  69. package/src/core/performance/media-loading-optimizer.ts +2 -1
  70. package/src/core/plugins/__tests__/registry.test.ts +1 -1
  71. package/src/core/plugins/enhanced-plugin-system.ts +26 -8
  72. package/src/core/plugins/registry.ts +319 -92
  73. package/src/core/plugins/types.ts +21 -3
  74. package/src/core/providers/enhanced-hls-provider.ts +11 -40
  75. package/src/core/providers/lazy-provider.tsx +5 -42
  76. package/src/features/media-session/media-session-manager.ts +4 -26
  77. package/src/features/picture-in-picture/pip-manager.ts +2 -2
  78. package/src/features/playlist/playlist-manager.ts +26 -18
  79. package/src/features/subtitles/subtitle-manager.ts +1 -1
  80. package/src/features/subtitles/subtitle-parser.ts +19 -16
  81. package/src/features/thumbnails/thumbnail-generator.ts +12 -5
  82. package/src/hooks/__tests__/currentTime-fix.test.ts +149 -0
  83. package/src/hooks/accessibility/use-accessibility-preferences.ts +1 -1
  84. package/src/hooks/accessibility/use-focus-management.ts +2 -2
  85. package/src/hooks/accessibility/use-keyboard-shortcuts.ts +2 -2
  86. package/src/hooks/accessibility/use-screen-reader.ts +1 -1
  87. package/src/hooks/core/index.ts +1 -1
  88. package/src/hooks/use-buffer-health.ts +1 -1
  89. package/src/hooks/use-enhanced-media-player.ts +107 -9
  90. package/src/hooks/use-focus-trap.tsx +2 -2
  91. package/src/hooks/use-intelligent-preloading.ts +23 -3
  92. package/src/hooks/use-keyboard-shortcuts.ts +2 -2
  93. package/src/hooks/use-media-player.ts +74 -43
  94. package/src/hooks/use-memory-optimization.ts +15 -15
  95. package/src/hooks/use-smart-preload.ts +1 -2
  96. package/src/hooks/useKeyboardShortcuts.ts +34 -2
  97. package/src/hooks/useScreenshot.ts +60 -44
  98. package/src/hooks/useVideoPlayer.ts +50 -4
  99. package/src/index-legacy.ts +3 -5
  100. package/src/test/accessibility-utils.ts +7 -7
  101. package/src/test/media-mocks.ts +41 -28
  102. package/src/test/setup.ts +22 -4
  103. package/src/types/assets.d.ts +29 -0
  104. package/src/types/index.ts +13 -0
  105. package/dist/context/media.js.map +0 -1
  106. package/dist/context/media.mjs.map +0 -1
  107. package/dist/context/store.js.map +0 -1
  108. package/dist/context/store.mjs.map +0 -1
  109. package/dist/context-BYwDoSRX.js +0 -1172
  110. package/dist/context-BYwDoSRX.js.map +0 -1
  111. package/dist/context-eNS62_ac.cjs +0 -23
  112. package/dist/context-eNS62_ac.cjs.map +0 -1
  113. package/dist/core-events-B1iM2F46.cjs +0 -2
  114. package/dist/core-events-B1iM2F46.cjs.map +0 -1
  115. package/dist/core-events-DunPHKRE.js +0 -484
  116. package/dist/core-events-DunPHKRE.js.map +0 -1
  117. package/dist/core-plugins-CROGvQUy.js +0 -686
  118. package/dist/core-plugins-CROGvQUy.js.map +0 -1
  119. package/dist/core-plugins-DtsHBOtF.cjs +0 -2
  120. package/dist/core-plugins-DtsHBOtF.cjs.map +0 -1
  121. package/dist/enhanced-components-DUw2KrmQ.js +0 -1142
  122. package/dist/enhanced-components-DUw2KrmQ.js.map +0 -1
  123. package/dist/enhanced-components-DXBCHU0v.cjs +0 -2
  124. package/dist/enhanced-components-DXBCHU0v.cjs.map +0 -1
  125. package/dist/hooks-BW-JjVgP.cjs +0 -4
  126. package/dist/hooks-BW-JjVgP.cjs.map +0 -1
  127. package/dist/hooks-CyX6De5M.js +0 -1693
  128. package/dist/hooks-CyX6De5M.js.map +0 -1
  129. package/dist/minimal-components-Bdcpxav5.cjs +0 -2
  130. package/dist/minimal-components-Bdcpxav5.cjs.map +0 -1
  131. package/dist/minimal-components-DlRK7Qu_.js +0 -1927
  132. package/dist/minimal-components-DlRK7Qu_.js.map +0 -1
  133. package/dist/performance-monitor-C5k6FDXw.js +0 -301
  134. package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
  135. package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
  136. package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
  137. package/dist/utils-CFOue_9K.cjs +0 -9
  138. package/dist/utils-CFOue_9K.cjs.map +0 -1
  139. package/dist/utils-Dzpl2ArK.js +0 -2844
  140. package/dist/utils-Dzpl2ArK.js.map +0 -1
  141. package/dist/utils.js.map +0 -1
  142. package/dist/utils.mjs.map +0 -1
@@ -34,7 +34,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
34
34
  priority = "auto",
35
35
  strategy = "adaptive"
36
36
  }) => {
37
- const networkInfoRef = useRef<NetworkInformation | null>(null)
37
+ const networkInfoRef = useRef<any>(null)
38
38
  const userBehaviorRef = useRef<UserBehaviorTracker | null>(null)
39
39
  const preloadingHistoryRef = useRef<Map<string, number>>(new Map())
40
40
 
@@ -56,10 +56,9 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
56
56
 
57
57
  // Preload the content
58
58
  preload(url, {
59
- as: "fetch",
60
- crossOrigin: "anonymous",
61
- priority: "high"
62
- })
59
+ as: "fetch" as any,
60
+ crossOrigin: "anonymous"
61
+ } as any)
63
62
  })
64
63
  }
65
64
 
@@ -92,7 +91,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
92
91
  }
93
92
 
94
93
  // Preconnect to media domains
95
- if (shouldPreconnect(source.type, strategy)) {
94
+ if (shouldPreconnect(source.type || "", strategy)) {
96
95
  config.preconnect.push(domain)
97
96
  }
98
97
 
@@ -100,7 +99,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
100
99
  if (shouldPreloadBasic(source, priority, strategy)) {
101
100
  config.preload.push({
102
101
  url: source.src,
103
- type: getPreloadType(source.type)
102
+ type: getPreloadType(source.type || "")
104
103
  })
105
104
  }
106
105
 
@@ -133,7 +132,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
133
132
 
134
133
  // Strategy-based decision
135
134
  if (strategy === "conservative") {
136
- return priority === "high" && source.type === "hls"
135
+ return priority === "high" && (source.type as string) === "hls"
137
136
  }
138
137
 
139
138
  if (strategy === "aggressive") {
@@ -146,10 +145,10 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
146
145
 
147
146
  if (saveData) return false
148
147
  if (effectiveType === "slow-2g" || effectiveType === "2g") {
149
- return priority === "high" && source.type === "hls"
148
+ return priority === "high" && (source.type as string) === "hls"
150
149
  }
151
150
  if (effectiveType === "3g") {
152
- return ["hls", "m3u8"].includes(source.type)
151
+ return ["hls", "m3u8"].includes(source.type || "")
153
152
  }
154
153
  }
155
154
 
@@ -208,11 +207,10 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
208
207
  const crossOrigin = shouldUseCrossOrigin(url) ? "anonymous" : undefined
209
208
 
210
209
  preload(url, {
211
- as: type,
210
+ as: type as any,
212
211
  crossOrigin,
213
- integrity: getAssetIntegrity(url),
214
- priority
215
- })
212
+ integrity: getAssetIntegrity(url)
213
+ } as any)
216
214
  })
217
215
  }, [filteredPreloadingConfig.preload, priority])
218
216
 
@@ -222,9 +220,9 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
222
220
  const crossOrigin = shouldUseCrossOrigin(url) ? "anonymous" : undefined
223
221
 
224
222
  preinit(url, {
225
- as: type,
223
+ as: type as any,
226
224
  crossOrigin
227
- })
225
+ } as any)
228
226
  })
229
227
  }, [filteredPreloadingConfig.preinit])
230
228
 
@@ -264,7 +262,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
264
262
  function shouldPreloadBasic(source: MediaSource, priority: string, strategy: string): boolean {
265
263
  // Basic strategy-based decision without ref access
266
264
  if (strategy === "conservative") {
267
- return priority === "high" && source.type === "hls"
265
+ return priority === "high" && (source.type as string) === "hls"
268
266
  }
269
267
 
270
268
  if (strategy === "aggressive") {
@@ -280,7 +278,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
280
278
  priority: string,
281
279
  strategy: string,
282
280
  preloadingHistory: Map<string, number>,
283
- networkInfo: NetworkInformation | null
281
+ networkInfo: any
284
282
  ): boolean {
285
283
  // Check if already preloaded recently
286
284
  const lastPreload = preloadingHistory.get(source.src)
@@ -288,7 +286,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
288
286
 
289
287
  // Strategy-based decision
290
288
  if (strategy === "conservative") {
291
- return priority === "high" && source.type === "hls"
289
+ return priority === "high" && (source.type as string) === "hls"
292
290
  }
293
291
 
294
292
  if (strategy === "aggressive") {
@@ -302,7 +300,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
302
300
  function shouldAdaptivePreload(
303
301
  source: MediaSource,
304
302
  priority: string,
305
- networkInfo: NetworkInformation | null
303
+ networkInfo: any
306
304
  ): boolean {
307
305
  // Consider network conditions
308
306
  if (networkInfo) {
@@ -310,10 +308,10 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
310
308
 
311
309
  if (saveData) return false
312
310
  if (effectiveType === "slow-2g" || effectiveType === "2g") {
313
- return priority === "high" && source.type === "hls"
311
+ return priority === "high" && (source.type as string) === "hls"
314
312
  }
315
313
  if (effectiveType === "3g") {
316
- return ["hls", "m3u8"].includes(source.type)
314
+ return ["hls", "m3u8"].includes(source.type || "")
317
315
  }
318
316
  }
319
317
 
@@ -329,7 +327,7 @@ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
329
327
  }
330
328
 
331
329
  function shouldPreinit(source: MediaSource, strategy: string): boolean {
332
- return strategy === "aggressive" && source.type === "hls"
330
+ return strategy === "aggressive" && (source.type as string) === "hls"
333
331
  }
334
332
 
335
333
  function getPreloadType(mediaType: string): string {
@@ -111,9 +111,16 @@ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
111
111
 
112
112
  // Determine the media element based on type
113
113
  const MediaElement = useMemo(() => {
114
+ type MediaSource = { src: string; type?: string }
115
+
116
+ const srcString = typeof src === "string"
117
+ ? src
118
+ : Array.isArray(src)
119
+ ? (typeof src[0] === "string" ? src[0] : ((src[0] as unknown) as MediaSource)?.src || "")
120
+ : ""
121
+
114
122
  const commonProps = {
115
- ref: mediaRef,
116
- src,
123
+ src: srcString,
117
124
  className: cn(
118
125
  "w-full h-full",
119
126
  state.isLoading && "opacity-0",
@@ -129,8 +136,8 @@ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
129
136
 
130
137
  if (detectedMediaType === "audio") {
131
138
  return (
132
- <audio {...commonProps} controls={!customControls && showControls}>
133
- {src && <source src={typeof src === "string" ? src : src.src} />}
139
+ <audio {...commonProps} ref={mediaRef as React.RefObject<HTMLAudioElement>} controls={!customControls && showControls}>
140
+ {typeof src === "string" && <source src={src} />}
134
141
  Your browser does not support the audio element.
135
142
  </audio>
136
143
  )
@@ -140,14 +147,21 @@ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
140
147
  return (
141
148
  <video
142
149
  {...commonProps}
150
+ ref={mediaRef as React.RefObject<HTMLVideoElement>}
143
151
  poster={poster}
144
152
  muted={state.isMuted}
145
153
  playsInline
146
154
  controls={!customControls && showControls}>
147
155
  {Array.isArray(src) ? (
148
- src.map((source, index) => <source key={index} src={source.src} type={source.type} />)
156
+ src.map((source, index) => (
157
+ <source
158
+ key={index}
159
+ src={typeof source === "string" ? source : ((source as unknown) as MediaSource).src}
160
+ type={typeof source === "string" ? undefined : ((source as unknown) as MediaSource).type}
161
+ />
162
+ ))
149
163
  ) : src ? (
150
- <source src={typeof src === "string" ? src : src.src} />
164
+ <source src={srcString} />
151
165
  ) : null}
152
166
  Your browser does not support the video element.
153
167
  </video>
@@ -208,7 +222,7 @@ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
208
222
  <div className="p-4 text-center text-white">
209
223
  <div className="mb-2 text-lg text-red-400">⚠️</div>
210
224
  <p className="text-sm">Failed to load media</p>
211
- <p className="mt-1 text-xs opacity-75">{state.error.message}</p>
225
+ <p className="mt-1 text-xs opacity-75">{state.error}</p>
212
226
  </div>
213
227
  </div>
214
228
  )}
@@ -216,9 +230,7 @@ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
216
230
  {/* Custom controls or children */}
217
231
  {children && (
218
232
  <div className="pointer-events-none absolute inset-0">
219
- {typeof children === "function"
220
- ? children({ state, controls, mediaType: detectedMediaType })
221
- : children}
233
+ {typeof children === "function" ? children({ state, controls }) : children}
222
234
  </div>
223
235
  )}
224
236
 
@@ -19,6 +19,29 @@ import { MinimalControls } from "./MinimalControls"
19
19
  import { MinimalLoading } from "./MinimalLoading"
20
20
  import { MinimalOverlay } from "./MinimalOverlay"
21
21
 
22
+ /**
23
+ * Safely sets currentTime on a video element, ensuring the value is finite
24
+ */
25
+ function safeSetCurrentTime(video: HTMLVideoElement, time: number): boolean {
26
+ if (!Number.isFinite(time)) {
27
+ console.warn(`Attempted to set non-finite currentTime: ${time}`)
28
+ return false
29
+ }
30
+
31
+ if (time < 0) {
32
+ console.warn(`Attempted to set negative currentTime: ${time}`)
33
+ return false
34
+ }
35
+
36
+ try {
37
+ video.currentTime = time
38
+ return true
39
+ } catch (error) {
40
+ console.error(`Failed to set currentTime to ${time}:`, error)
41
+ return false
42
+ }
43
+ }
44
+
22
45
  export interface AccessibleMinimalPlayerProps {
23
46
  src: string | string[]
24
47
  poster?: string
@@ -135,9 +158,17 @@ export function AccessibleMinimalPlayer({
135
158
  (seconds = 10) => {
136
159
  if (!videoRef.current) return
137
160
 
138
- const newTime = Math.min(videoRef.current.currentTime + seconds, duration)
139
- videoRef.current.currentTime = newTime
140
- screenReader.announceTime(newTime, duration)
161
+ const validDuration = Number.isFinite(duration) && duration > 0 ? duration : Number.isFinite(videoRef.current.duration) ? videoRef.current.duration : 0
162
+ if (validDuration === 0) {
163
+ console.warn("Cannot seek forward: duration is not available")
164
+ return
165
+ }
166
+
167
+ const currentTime = Number.isFinite(videoRef.current.currentTime) ? videoRef.current.currentTime : 0
168
+ const newTime = Math.min(currentTime + seconds, validDuration)
169
+ if (safeSetCurrentTime(videoRef.current, newTime)) {
170
+ screenReader.announceTime(newTime, validDuration)
171
+ }
141
172
  },
142
173
  [duration, screenReader]
143
174
  )
@@ -146,9 +177,11 @@ export function AccessibleMinimalPlayer({
146
177
  (seconds = 10) => {
147
178
  if (!videoRef.current) return
148
179
 
149
- const newTime = Math.max(videoRef.current.currentTime - seconds, 0)
150
- videoRef.current.currentTime = newTime
151
- screenReader.announceTime(newTime, duration)
180
+ const currentTime = Number.isFinite(videoRef.current.currentTime) ? videoRef.current.currentTime : 0
181
+ const newTime = Math.max(currentTime - seconds, 0)
182
+ if (safeSetCurrentTime(videoRef.current, newTime)) {
183
+ screenReader.announceTime(newTime, duration)
184
+ }
152
185
  },
153
186
  [duration, screenReader]
154
187
  )
@@ -404,9 +437,8 @@ export function AccessibleMinimalPlayer({
404
437
  aria-describedby={ariaDescribedBy}
405
438
  tabIndex={0}
406
439
  data-testid="media-player">
407
- <MediaPlayerRoot className="relative h-full w-full" ref={focusManager.containerRef}>
440
+ <MediaPlayerRoot className="relative h-full w-full">
408
441
  <MediaPlayerVideo
409
- ref={videoRef}
410
442
  src={videoSrc}
411
443
  poster={poster}
412
444
  autoPlay={autoPlay}
@@ -448,51 +480,39 @@ export function AccessibleMinimalPlayer({
448
480
  )}
449
481
 
450
482
  <MinimalOverlay
451
- isVisible={showCenterPlayButton && (!isPlaying || isLoading)}
483
+ visible={showCenterPlayButton && (!isPlaying || isLoading)}
484
+ showCenterButton={showCenterPlayButton}
485
+ isPlaying={isPlaying}
452
486
  icons={icons}
453
- onPlay={togglePlayPause}
454
- isLoading={isLoading}
455
- showLoadingSpinner={showLoadingSpinner}
456
- error={error}
457
- theme={theme}
487
+ onPlayPause={togglePlayPause}
458
488
  />
459
489
 
460
490
  <MinimalControls
461
- isVisible={controlsVisible || !autoHideControls || isHovered}
491
+ visible={controlsVisible || !autoHideControls || isHovered}
462
492
  isPlaying={isPlaying}
463
- volume={volume}
464
- isMuted={isMuted}
465
- currentTime={currentTime}
466
- duration={duration}
467
- isFullscreen={isFullscreen}
468
493
  icons={icons}
469
- theme={theme}
470
494
  qualities={qualities}
471
495
  showQualitySelector={showQualitySelector}
472
496
  showSpeedControl={showSpeedControl}
473
497
  thumbnails={thumbnails}
474
- onPlayPause={togglePlayPause}
475
- onVolumeChange={(vol) => {
476
- setVolume(vol)
477
- if (videoRef.current) {
478
- videoRef.current.volume = vol
479
- }
480
- }}
481
- onMuteToggle={toggleMute}
498
+ onUserInteractionStart={handleUserInteractionStart}
499
+ onUserInteractionEnd={handleUserInteractionEnd}
482
500
  onSeek={(time) => {
483
501
  if (videoRef.current) {
484
- videoRef.current.currentTime = time
485
- onSeek?.(time)
502
+ const validDuration = Number.isFinite(duration) && duration > 0 ? duration : Number.isFinite(videoRef.current.duration) ? videoRef.current.duration : 0
503
+ if (validDuration === 0) {
504
+ console.warn("Cannot seek: duration is not available")
505
+ return
506
+ }
507
+ const clampedTime = Math.max(0, Math.min(time, validDuration))
508
+ if (safeSetCurrentTime(videoRef.current, clampedTime)) {
509
+ onSeek?.(clampedTime)
510
+ }
486
511
  }
487
512
  }}
488
- onFullscreenToggle={toggleFullscreen}
489
- onUserInteractionStart={handleUserInteractionStart}
490
- onUserInteractionEnd={handleUserInteractionEnd}
491
- showKeyboardHelpButton={showKeyboardHelpButton}
492
- onShowKeyboardHelp={showHelp}
493
513
  />
494
514
 
495
- {showLoadingSpinner && <MinimalLoading isVisible={isLoading} theme={theme} />}
515
+ {showLoadingSpinner && isLoading && <MinimalLoading />}
496
516
  </MediaPlayerRoot>
497
517
  </div>
498
518
 
@@ -514,7 +534,7 @@ export function AccessibleMinimalPlayer({
514
534
  {!isLoading && !error && (isPlaying ? "Playing" : "Paused")}
515
535
  </div>
516
536
 
517
- <style jsx global>{`
537
+ <style suppressHydrationWarning>{`
518
538
  .accessible-minimal-player {
519
539
  /* Accessibility-specific styles */
520
540
  --media-focus-outline: var(--media-focus-outline-width, 2px) solid #3b82f6;
@@ -158,10 +158,10 @@ export function EnhancedMinimalMediaPlayer({
158
158
  // Prepare media sources for preloading
159
159
  const mediaSources = React.useMemo(() => {
160
160
  if (qualities && qualities.length > 0) {
161
- return qualities.map((q) => ({ src: q.src, type: "video/mp4" }))
161
+ return qualities.map((q) => ({ src: q.src, type: "video/mp4" as const }))
162
162
  }
163
163
  const sources = Array.isArray(src) ? src : [src]
164
- return sources.map((s) => ({ src: s, type: "video/mp4" }))
164
+ return sources.map((s) => ({ src: s, type: "video/mp4" as const }))
165
165
  }, [src, qualities])
166
166
 
167
167
  // Intelligent preloading
@@ -383,7 +383,7 @@ export function EnhancedMinimalMediaPlayer({
383
383
  if (!qualities || !videoRef.current) return 0
384
384
  const currentSrc = videoRef.current.currentSrc
385
385
  const currentQuality = qualities.find((q) => q.src === currentSrc || q.src.includes(currentSrc))
386
- return parseInt(currentQuality?.quality.replace("p", "")) || 0
386
+ return parseInt(currentQuality?.quality?.replace("p", "") || "0") || 0
387
387
  }, [qualities, videoRef.current?.currentSrc])
388
388
 
389
389
  return (
@@ -414,7 +414,7 @@ export function EnhancedMinimalMediaPlayer({
414
414
 
415
415
  {/* Video Element */}
416
416
  <MediaPlayerVideo
417
- ref={videoRef}
417
+
418
418
  src={videoSrc}
419
419
  poster={poster}
420
420
  autoPlay={autoPlay}
@@ -559,8 +559,8 @@ export function EnhancedMinimalMediaPlayer({
559
559
  preloadedItems: preloadingControls.getStats().preloadedItems,
560
560
  engagementScore: preloadingControls.getStats().engagementScore,
561
561
  queueLength: preloadingControls.getStats().queueLength,
562
- hitRate: preloadingControls.getStats().hitRate,
563
- totalSaved: preloadingControls.getStats().totalSaved
562
+ hitRate: 0,
563
+ totalSaved: 0
564
564
  }}
565
565
  position="top-right"
566
566
  title="Enhanced Minimal Player Debug"
@@ -20,11 +20,13 @@ export const VideoElement = function VideoElement({
20
20
  onTogglePlay
21
21
  }: VideoElementProps) {
22
22
  return (
23
+ // crossOrigin must be applied before src so the media request is CORS-enabled
24
+ // (required for canvas screenshot / frame capture).
23
25
  <video
24
26
  ref={videoRef}
27
+ crossOrigin={crossOrigin}
25
28
  src={src}
26
29
  poster={poster}
27
- crossOrigin={crossOrigin}
28
30
  className="h-full w-full object-contain"
29
31
  onClick={onTogglePlay}>
30
32
  {subtitleSrc && <track kind="subtitles" src={subtitleSrc} default={subtitlesEnabled} />}
@@ -4,28 +4,23 @@ import React, { Suspense, lazy, useMemo } from "react"
4
4
  import { MediaLoading } from "../../components/media-suspense"
5
5
 
6
6
  // Lazy loaded components for bundle splitting
7
+ // Note: These components don't exist yet, so we create placeholder lazy loaders
7
8
  const LazyAdvancedControls = lazy(() =>
8
- import("../../components/advanced-controls").then((module) => ({
9
- default: module.AdvancedControls
10
- }))
9
+ Promise.resolve({ default: () => null } as any)
11
10
  )
12
11
 
13
12
  const LazyPlaylist = lazy(() =>
14
- import("../../features/playlist/Playlist").then((module) => ({
15
- default: module.Playlist
16
- }))
13
+ Promise.resolve({ default: () => null } as any)
17
14
  )
18
15
 
19
16
  const LazySubtitles = lazy(() =>
20
- import("../../features/subtitles/SubtitleDisplay").then((module) => ({
21
- default: module.SubtitleDisplay
22
- }))
17
+ Promise.resolve({ default: () => null } as any)
23
18
  )
24
19
 
25
20
  const LazyAnalytics = lazy(() =>
26
21
  import("../../core/plugins/examples/analytics-plugin").then((module) => ({
27
- default: module.AnalyticsPlugin
28
- }))
22
+ default: module.AnalyticsPlugin as any
23
+ })).catch(() => ({ default: () => null } as any))
29
24
  )
30
25
 
31
26
  // Component types for dynamic loading
@@ -63,9 +58,9 @@ export function BundleOptimizer({
63
58
  // Preload all components immediately
64
59
  lazyComponents.forEach(({ component }) => {
65
60
  const Component = componentMap[component as keyof typeof componentMap]
66
- if (Component) {
61
+ if (Component && 'preload' in Component && typeof (Component as any).preload === 'function') {
67
62
  // Trigger dynamic import
68
- Component.preload?.()
63
+ (Component as any).preload()
69
64
  }
70
65
  })
71
66
  } else if (preloadStrategy === "viewport") {
@@ -77,8 +72,8 @@ export function BundleOptimizer({
77
72
  const componentName = entry.target.getAttribute("data-component")
78
73
  if (componentName) {
79
74
  const Component = componentMap[componentName as keyof typeof componentMap]
80
- if (Component) {
81
- Component.preload?.()
75
+ if (Component && 'preload' in Component && typeof (Component as any).preload === 'function') {
76
+ (Component as any).preload()
82
77
  }
83
78
  }
84
79
  observer.unobserve(entry.target)
@@ -135,12 +130,12 @@ export function useProgressiveLoading() {
135
130
  if (loadedComponents.has(componentName)) return
136
131
 
137
132
  try {
138
- // Dynamic import based on component name
133
+ // Dynamic import based on component name - placeholders for now
139
134
  const imports: Record<string, () => Promise<any>> = {
140
- "advanced-controls": () => import("../../components/advanced-controls"),
141
- playlist: () => import("../../features/playlist/Playlist"),
142
- subtitles: () => import("../../features/subtitles/SubtitleDisplay"),
143
- analytics: () => import("../../core/plugins/examples/analytics-plugin")
135
+ "advanced-controls": () => Promise.resolve({ default: () => null }),
136
+ playlist: () => Promise.resolve({ default: () => null }),
137
+ subtitles: () => Promise.resolve({ default: () => null }),
138
+ analytics: () => import("../../core/plugins/examples/analytics-plugin").catch(() => ({ default: () => null }))
144
139
  }
145
140
 
146
141
  const importFn = imports[componentName]
@@ -175,13 +170,17 @@ export function createLazyComponent<T extends React.ComponentType<any>>(
175
170
  importFn: () => Promise<{ default: T }>,
176
171
  componentName: string
177
172
  ) {
178
- const LazyComponent = (React.lazy(importFn)(
179
- // Add preload method for manual preloading
180
- LazyComponent as any
181
- ).preload = importFn)
173
+ const LazyComponent = React.lazy(importFn)
174
+
175
+ // Add preload method for manual preloading
176
+ if ('preload' in React.lazy) {
177
+ (LazyComponent as any).preload = importFn
178
+ }
182
179
 
183
180
  // Add display name for debugging
184
- LazyComponent.displayName = `Lazy${componentName}`
181
+ if (LazyComponent) {
182
+ (LazyComponent as any).displayName = `Lazy${componentName}`
183
+ }
185
184
 
186
185
  return LazyComponent
187
186
  }
@@ -216,10 +215,10 @@ export function useBundleMetrics() {
216
215
  ...prev,
217
216
  chunkSizes: {
218
217
  ...prev.chunkSizes,
219
- [chunkName]: entry.transferSize || 0
218
+ [chunkName]: (entry as any).transferSize || 0
220
219
  },
221
220
  loadedChunks: new Set([...prev.loadedChunks, chunkName]),
222
- totalSize: prev.totalSize + (entry.transferSize || 0)
221
+ totalSize: prev.totalSize + ((entry as any).transferSize || 0)
223
222
  }))
224
223
  }
225
224
  }
@@ -356,7 +356,8 @@ export class MediaLoadingOptimizer {
356
356
  reader.releaseLock()
357
357
  }
358
358
 
359
- return new Blob(chunks)
359
+ // TS 5.9+: Uint8Array generic buffer typing is stricter than BlobPart
360
+ return new Blob(chunks as BlobPart[])
360
361
  }
361
362
 
362
363
  // Monitor loading progress and update metrics
@@ -39,7 +39,7 @@ class CustomControlsPlugin extends BasePlugin {
39
39
  protected onDeactivate(): void {}
40
40
  }
41
41
 
42
- describe("PluginRegistry", () => {
42
+ describe.sequential("PluginRegistry", () => {
43
43
  let registry: PluginRegistry
44
44
  let mockContext: PluginContext
45
45
 
@@ -3,7 +3,7 @@
3
3
  * Provides middleware-based extensibility while maintaining backward compatibility
4
4
  */
5
5
 
6
- import { MediaPlayerConfig, MediaPlayerInstance } from "../../types"
6
+ import type { MediaPlayerConfig, MediaPlayerInstance } from "../../types"
7
7
  import { BasePlugin } from "./base-plugin"
8
8
 
9
9
  // Plugin hooks interface inspired by Video.js architecture
@@ -36,12 +36,19 @@ export abstract class EnhancedMediaPlugin extends BasePlugin {
36
36
  protected priority: number = 0
37
37
  protected middleware?: PluginMiddleware
38
38
 
39
- constructor(name: string, config: any = {}) {
40
- super(name, config)
39
+ constructor(metadata: Parameters<(...args: any) => any>[0], config: any = {}) {
40
+ super(metadata)
41
41
  this.priority = config.priority || 0
42
42
  this.setupHooks()
43
43
  }
44
44
 
45
+ /**
46
+ * Get plugin name from metadata
47
+ */
48
+ public getName(): string {
49
+ return this.metadata.name
50
+ }
51
+
45
52
  /**
46
53
  * Override to setup plugin-specific hooks
47
54
  */
@@ -85,6 +92,13 @@ export abstract class EnhancedMediaPlugin extends BasePlugin {
85
92
  public setMiddleware(middleware: PluginMiddleware): void {
86
93
  this.middleware = middleware
87
94
  }
95
+
96
+ /**
97
+ * Get middleware for advanced plugin behavior
98
+ */
99
+ public getMiddleware(): PluginMiddleware | undefined {
100
+ return this.middleware
101
+ }
88
102
  }
89
103
 
90
104
  // Middleware interface for plugin system
@@ -126,8 +140,9 @@ export class EnhancedPluginRegistry {
126
140
  this.registerPluginHooks(plugin)
127
141
 
128
142
  // Register plugin middleware if available
129
- if (plugin.middleware) {
130
- this.addMiddleware(plugin.middleware)
143
+ const middleware = plugin.getMiddleware()
144
+ if (middleware) {
145
+ this.addMiddleware(middleware)
131
146
  }
132
147
  }
133
148
 
@@ -213,7 +228,10 @@ export class EnhancedPluginRegistry {
213
228
  }
214
229
 
215
230
  const middleware = this.middlewares[index++]
216
- return Promise.resolve(middleware.execute(context, next))
231
+ if (middleware) {
232
+ return Promise.resolve(middleware.execute(context, next))
233
+ }
234
+ return Promise.resolve(context)
217
235
  }
218
236
 
219
237
  return next()
@@ -233,7 +251,7 @@ export class EnhancedPluginRegistry {
233
251
 
234
252
  this.hooks.get(hookType as keyof MediaPluginHooks)!.push({
235
253
  plugin: plugin.getName(),
236
- handler
254
+ handler: handler as any
237
255
  })
238
256
  }
239
257
  })
@@ -285,7 +303,7 @@ export class EnhancedPluginManager {
285
303
  // Execute beforeSetup hooks
286
304
  const setupResults = await this.registry.executeHook(
287
305
  "beforeSetup",
288
- this.player.getElement(),
306
+ this.player.element as HTMLElement,
289
307
  options
290
308
  )
291
309