@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
@@ -8,6 +8,29 @@ interface UseKeyboardShortcutsProps {
8
8
  duration: number
9
9
  }
10
10
 
11
+ /**
12
+ * Safely sets currentTime on a video element, ensuring the value is finite
13
+ */
14
+ function safeSetCurrentTime(video: HTMLVideoElement, time: number): boolean {
15
+ if (!Number.isFinite(time)) {
16
+ console.warn(`Attempted to set non-finite currentTime: ${time}`)
17
+ return false
18
+ }
19
+
20
+ if (time < 0) {
21
+ console.warn(`Attempted to set negative currentTime: ${time}`)
22
+ return false
23
+ }
24
+
25
+ try {
26
+ video.currentTime = time
27
+ return true
28
+ } catch (error) {
29
+ console.error(`Failed to set currentTime to ${time}:`, error)
30
+ return false
31
+ }
32
+ }
33
+
11
34
  export function useKeyboardShortcuts({
12
35
  videoControls,
13
36
  toggleSubtitles,
@@ -34,11 +57,20 @@ export function useKeyboardShortcuts({
34
57
  break
35
58
  case "arrowleft":
36
59
  e.preventDefault()
37
- video.currentTime = Math.max(0, video.currentTime - 5)
60
+ const currentTime = Number.isFinite(video.currentTime) ? video.currentTime : 0
61
+ const newTimeLeft = Math.max(0, currentTime - 5)
62
+ safeSetCurrentTime(video, newTimeLeft)
38
63
  break
39
64
  case "arrowright":
40
65
  e.preventDefault()
41
- video.currentTime = Math.min(duration, video.currentTime + 5)
66
+ const validDuration = Number.isFinite(duration) && duration > 0 ? duration : Number.isFinite(video.duration) ? video.duration : 0
67
+ if (validDuration === 0) {
68
+ console.warn("Cannot seek: duration is not available")
69
+ break
70
+ }
71
+ const currentTimeRight = Number.isFinite(video.currentTime) ? video.currentTime : 0
72
+ const newTimeRight = Math.min(validDuration, currentTimeRight + 5)
73
+ safeSetCurrentTime(video, newTimeRight)
42
74
  break
43
75
  case "arrowup":
44
76
  e.preventDefault()
@@ -1,61 +1,77 @@
1
1
  import { useCallback } from "react"
2
2
 
3
+ function isCanvasTaintError(error: unknown): boolean {
4
+ if (error instanceof DOMException && error.name === "SecurityError") return true
5
+ if (error instanceof Error && /tainted/i.test(error.message)) return true
6
+ return false
7
+ }
8
+
9
+ /**
10
+ * Capture the current video frame.
11
+ * Requires the video to be loaded with CORS-friendly headers and
12
+ * `crossOrigin="anonymous"` (or `use-credentials`) on the element.
13
+ */
3
14
  export function useScreenshot(videoRef: React.RefObject<HTMLVideoElement | null>) {
4
- const takeScreenshot = (ctrlKey: boolean = false) => {
5
- if (!videoRef.current) return
6
-
7
- const video = videoRef.current
8
- const canvas = document.createElement("canvas")
9
- canvas.width = video.videoWidth
10
- canvas.height = video.videoHeight
11
- const ctx = canvas.getContext("2d")
12
- if (!ctx) return
13
-
14
- try {
15
- // Draw the video frame to canvas
16
- ctx.drawImage(video, 0, 0)
17
-
18
- // Try to export the canvas
19
- canvas.toBlob((blob) => {
20
- if (!blob) {
21
- console.error("Failed to create blob from canvas")
22
- return
23
- }
15
+ const takeScreenshot = useCallback(
16
+ (ctrlKey: boolean = false) => {
17
+ const video = videoRef.current
18
+ if (!video) return
19
+
20
+ if (!video.videoWidth || !video.videoHeight) {
21
+ console.warn("Screenshot skipped: video frame not ready yet.")
22
+ return
23
+ }
24
24
 
25
- if (ctrlKey) {
26
- // Copy to clipboard
27
- if (navigator.clipboard && window.ClipboardItem) {
28
- const item = new ClipboardItem({ "image/png": blob })
29
- navigator.clipboard.write([item]).catch((err) => {
30
- console.error("Failed to copy to clipboard:", err)
31
- })
32
- } else {
33
- console.warn("Clipboard API not supported")
25
+ const canvas = document.createElement("canvas")
26
+ canvas.width = video.videoWidth
27
+ canvas.height = video.videoHeight
28
+ const ctx = canvas.getContext("2d")
29
+ if (!ctx) return
30
+
31
+ try {
32
+ ctx.drawImage(video, 0, 0)
33
+
34
+ // SecurityError throws here when the canvas is tainted by a
35
+ // cross-origin video without usable CORS + crossOrigin on <video>.
36
+ canvas.toBlob((blob) => {
37
+ if (!blob) {
38
+ console.error("Screenshot failed: could not create image blob.")
39
+ return
40
+ }
41
+
42
+ if (ctrlKey) {
43
+ if (navigator.clipboard && window.ClipboardItem) {
44
+ const item = new ClipboardItem({ "image/png": blob })
45
+ navigator.clipboard.write([item]).catch((err) => {
46
+ console.error("Failed to copy screenshot to clipboard:", err)
47
+ })
48
+ } else {
49
+ console.warn("Clipboard API not supported for screenshot copy.")
50
+ }
51
+ return
34
52
  }
35
- } else {
36
- // Download
53
+
37
54
  const url = URL.createObjectURL(blob)
38
55
  const a = document.createElement("a")
39
56
  a.href = url
40
57
  a.download = `screenshot-${Date.now()}.png`
41
58
  a.click()
42
59
  URL.revokeObjectURL(url)
60
+ }, "image/png")
61
+ } catch (error) {
62
+ if (isCanvasTaintError(error)) {
63
+ console.error(
64
+ "Screenshot failed: canvas tainted. The video is cross-origin without CORS allowing canvas use. " +
65
+ "Serve the media with Access-Control-Allow-Origin and set crossOrigin=\"anonymous\" on the player " +
66
+ "(must be set before the media loads)."
67
+ )
68
+ return
43
69
  }
44
- }, "image/png")
45
- } catch (error) {
46
- if (error instanceof Error && error.message.includes("tainted")) {
47
- console.error(
48
- "Canvas is tainted - video source may not have proper CORS headers. Try setting crossOrigin='anonymous' on the video element."
49
- )
50
- alert(
51
- "Screenshot failed: The video source doesn't allow screenshots due to CORS policy. Please ensure the video server includes proper CORS headers or set crossOrigin='anonymous' on the video player."
52
- )
53
- } else {
54
70
  console.error("Screenshot failed:", error)
55
- alert("Screenshot failed: " + (error instanceof Error ? error.message : "Unknown error"))
56
71
  }
57
- }
58
- }
72
+ },
73
+ [videoRef]
74
+ )
59
75
 
60
76
  return { takeScreenshot }
61
77
  }
@@ -29,6 +29,32 @@ export interface VideoControls {
29
29
  seekTo: (time: number) => void
30
30
  }
31
31
 
32
+ /**
33
+ * Safely sets currentTime on a video element, ensuring the value is finite
34
+ * @param video - The video element
35
+ * @param time - The desired time value
36
+ * @returns true if successfully set, false otherwise
37
+ */
38
+ function safeSetCurrentTime(video: HTMLVideoElement, time: number): boolean {
39
+ if (!Number.isFinite(time)) {
40
+ console.warn(`Attempted to set non-finite currentTime: ${time}`)
41
+ return false
42
+ }
43
+
44
+ if (time < 0) {
45
+ console.warn(`Attempted to set negative currentTime: ${time}`)
46
+ return false
47
+ }
48
+
49
+ try {
50
+ video.currentTime = time
51
+ return true
52
+ } catch (error) {
53
+ console.error(`Failed to set currentTime to ${time}:`, error)
54
+ return false
55
+ }
56
+ }
57
+
32
58
  export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | null>) {
33
59
  const videoRef = useRef<HTMLVideoElement>(null)
34
60
  const [state, setState] = useState<VideoState>({
@@ -168,13 +194,25 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
168
194
  const seekForward = (seconds = 10) => {
169
195
  const video = videoRef.current
170
196
  if (!video) return
171
- video.currentTime = Math.min(state.duration, video.currentTime + seconds)
197
+
198
+ const duration = Number.isFinite(video.duration) ? video.duration : Number.isFinite(state.duration) ? state.duration : 0
199
+ if (duration === 0) {
200
+ console.warn("Cannot seek forward: duration is not available")
201
+ return
202
+ }
203
+
204
+ const currentTime = Number.isFinite(video.currentTime) ? video.currentTime : 0
205
+ const newTime = Math.min(duration, currentTime + seconds)
206
+ safeSetCurrentTime(video, newTime)
172
207
  }
173
208
 
174
209
  const seekBackward = (seconds = 10) => {
175
210
  const video = videoRef.current
176
211
  if (!video) return
177
- video.currentTime = Math.max(0, video.currentTime - seconds)
212
+
213
+ const currentTime = Number.isFinite(video.currentTime) ? video.currentTime : 0
214
+ const newTime = Math.max(0, currentTime - seconds)
215
+ safeSetCurrentTime(video, newTime)
178
216
  }
179
217
 
180
218
  const toggleMute = () => {
@@ -210,8 +248,16 @@ export function useVideoPlayer(containerRef: React.RefObject<HTMLDivElement | nu
210
248
 
211
249
  const seekTo = (time: number) => {
212
250
  const video = videoRef.current
213
- if (!video || state.duration === 0) return
214
- video.currentTime = Math.max(0, Math.min(state.duration, time))
251
+ if (!video) return
252
+
253
+ const duration = Number.isFinite(video.duration) ? video.duration : Number.isFinite(state.duration) ? state.duration : 0
254
+ if (duration === 0) {
255
+ console.warn("Cannot seek: duration is not available")
256
+ return
257
+ }
258
+
259
+ const clampedTime = Math.max(0, Math.min(duration, time))
260
+ safeSetCurrentTime(video, clampedTime)
215
261
  }
216
262
 
217
263
  const controls: VideoControls = {
@@ -131,11 +131,9 @@ export { useIntelligentPreloading } from "./hooks/use-intelligent-preloading"
131
131
  export { useEnhancedMediaPlayer } from "./hooks/use-enhanced-media-player"
132
132
 
133
133
  // Performance Monitoring
134
- export {
135
- PerformanceMonitor,
136
- PerformanceMetrics,
137
- usePerformanceMonitor as usePerformanceMonitoring
138
- } from "./core/performance"
134
+ export { PerformanceMonitor } from "./core/performance"
135
+ export type { PerformanceMetrics } from "./core/performance"
136
+ export { usePerformanceMonitor as usePerformanceMonitoring } from "./core/performance"
139
137
 
140
138
  // Media Resource Preloader
141
139
  export { MediaResourcePreloader } from "./components/ui/media-resource-preloader"
@@ -42,7 +42,7 @@ export const a11yTestUtils = {
42
42
 
43
43
  // Test ARIA attributes
44
44
  testAriaAttributes: (container: HTMLElement) => {
45
- const violations = []
45
+ const violations: Array<{ element: Element; issue: string }> = []
46
46
 
47
47
  // Check for required ARIA attributes
48
48
  const elementsNeedingAria = container.querySelectorAll(
@@ -77,7 +77,7 @@ export const a11yTestUtils = {
77
77
 
78
78
  // Test color contrast (mock implementation)
79
79
  testColorContrast: (container: HTMLElement) => {
80
- const violations = []
80
+ const violations: Array<{ element: Element; issue: string }> = []
81
81
  const textElements = container.querySelectorAll("p, span, div, button, a")
82
82
 
83
83
  textElements.forEach((element) => {
@@ -100,7 +100,7 @@ export const a11yTestUtils = {
100
100
  // Test screen reader announcements
101
101
  testScreenReaderAnnouncements: (container: HTMLElement) => {
102
102
  const liveRegions = container.querySelectorAll('[aria-live], [role="status"], [role="alert"]')
103
- const announcements = []
103
+ const announcements: Array<{ region: Element; text: string; priority: string }> = []
104
104
 
105
105
  liveRegions.forEach((region) => {
106
106
  const text = region.textContent?.trim()
@@ -118,7 +118,7 @@ export const a11yTestUtils = {
118
118
 
119
119
  // Test focus management
120
120
  testFocusManagement: (container: HTMLElement) => {
121
- const issues = []
121
+ const issues: Array<{ element: Element; issue: string }> = []
122
122
 
123
123
  // Test for focus traps
124
124
  const focusTraps = container.querySelectorAll('[data-focus-trap="true"]')
@@ -164,7 +164,7 @@ export const mockAxe = {
164
164
  export const accessibilityMatchers = {
165
165
  toBeAccessible: async (container: HTMLElement) => {
166
166
  const axeResults = await mockAxe.run(container)
167
- const keyboardResults = a11yTestUtils.testKeyboardNavigation(container)
167
+ const keyboardResults = await a11yTestUtils.testKeyboardNavigation(container)
168
168
  const ariaResults = a11yTestUtils.testAriaAttributes(container)
169
169
 
170
170
  const allViolations = [...axeResults.violations, ...keyboardResults, ...ariaResults]
@@ -194,8 +194,8 @@ export const accessibilityMatchers = {
194
194
  }
195
195
  },
196
196
 
197
- toSupportKeyboardNavigation: (container: HTMLElement) => {
198
- const issues = a11yTestUtils.testKeyboardNavigation(container)
197
+ toSupportKeyboardNavigation: async (container: HTMLElement) => {
198
+ const issues = await a11yTestUtils.testKeyboardNavigation(container)
199
199
 
200
200
  return {
201
201
  pass: issues.length === 0,
@@ -14,14 +14,18 @@ export class MockMediaStream {
14
14
  }
15
15
 
16
16
  export const createMockHTMLMediaElement = () => {
17
+ // Internal state
18
+ let _currentTime = 0
19
+ let _paused = true
20
+ let _volume = 1
21
+ let _muted = false
22
+
17
23
  const mockElement = {
18
24
  // Basic properties
19
25
  src: "",
20
- currentTime: 0,
21
26
  duration: NaN,
22
27
  volume: 1,
23
28
  muted: false,
24
- paused: true,
25
29
  ended: false,
26
30
  seeking: false,
27
31
  readyState: 0,
@@ -71,34 +75,26 @@ export const createMockHTMLMediaElement = () => {
71
75
  // Capture stream
72
76
  captureStream: vi.fn().mockReturnValue(new MockMediaStream()),
73
77
 
74
- // Setters with side effects
78
+ // Getters and setters with side effects
79
+ get currentTime() {
80
+ return _currentTime
81
+ },
75
82
  set currentTime(value: number) {
76
- this._currentTime = value
83
+ _currentTime = value
77
84
  // Trigger timeupdate event
78
85
  const event = new Event("timeupdate")
79
86
  this.dispatchEvent(event)
80
87
  },
81
88
 
82
- get currentTime() {
83
- return this._currentTime || 0
89
+ get paused() {
90
+ return _paused
84
91
  },
85
-
86
92
  set paused(value: boolean) {
87
- this._paused = value
93
+ _paused = value
88
94
  // Trigger play or pause events
89
95
  const event = new Event(value ? "pause" : "play")
90
96
  this.dispatchEvent(event)
91
- },
92
-
93
- get paused() {
94
- return this._paused !== false
95
- },
96
-
97
- // Internal state
98
- _currentTime: 0,
99
- _paused: true,
100
- _volume: 1,
101
- _muted: false
97
+ }
102
98
  }
103
99
 
104
100
  return mockElement
@@ -114,14 +110,23 @@ export const setupMediaMocks = () => {
114
110
  } as any
115
111
 
116
112
  // Mock MediaDevices API
117
- global.navigator.mediaDevices = {
118
- getUserMedia: vi.fn().mockResolvedValue(new MockMediaStream()),
119
- enumerateDevices: vi.fn().mockResolvedValue([])
120
- } as any
113
+ Object.defineProperty(global.navigator, "mediaDevices", {
114
+ writable: true,
115
+ value: {
116
+ getUserMedia: vi.fn().mockResolvedValue(new MockMediaStream()),
117
+ enumerateDevices: vi.fn().mockResolvedValue([])
118
+ }
119
+ })
121
120
 
122
121
  // Mock Picture-in-Picture API
123
- global.document.pictureInPictureEnabled = true
124
- global.document.pictureInPictureElement = null
122
+ Object.defineProperty(global.document, "pictureInPictureEnabled", {
123
+ writable: true,
124
+ value: true
125
+ })
126
+ Object.defineProperty(global.document, "pictureInPictureElement", {
127
+ writable: true,
128
+ value: null
129
+ })
125
130
 
126
131
  // Mock Fullscreen API
127
132
  Object.defineProperty(document, "fullscreenEnabled", {
@@ -136,8 +141,12 @@ export const setupMediaMocks = () => {
136
141
 
137
142
  Object.defineProperty(Element.prototype, "requestFullscreen", {
138
143
  writable: true,
139
- value: vi.fn().mockImplementation(function () {
140
- document.fullscreenElement = this
144
+ value: vi.fn().mockImplementation(function (this: Element) {
145
+ Object.defineProperty(document, "fullscreenElement", {
146
+ writable: true,
147
+ configurable: true,
148
+ value: this
149
+ })
141
150
  const event = new Event("fullscreenchange")
142
151
  document.dispatchEvent(event)
143
152
  return Promise.resolve()
@@ -147,7 +156,11 @@ export const setupMediaMocks = () => {
147
156
  Object.defineProperty(document, "exitFullscreen", {
148
157
  writable: true,
149
158
  value: vi.fn().mockImplementation(function () {
150
- document.fullscreenElement = null
159
+ Object.defineProperty(document, "fullscreenElement", {
160
+ writable: true,
161
+ configurable: true,
162
+ value: null
163
+ })
151
164
  const event = new Event("fullscreenchange")
152
165
  document.dispatchEvent(event)
153
166
  return Promise.resolve()
package/src/test/setup.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import "@testing-library/jest-dom"
2
- import { vi } from "vitest"
2
+ import { afterEach, vi } from "vitest"
3
3
  import { setupMediaMocks } from "./media-mocks"
4
4
 
5
5
  // Setup comprehensive media mocking
@@ -20,8 +20,8 @@ global.ResizeObserver = vi.fn().mockImplementation(() => ({
20
20
  }))
21
21
 
22
22
  // Mock requestAnimationFrame for performance testing
23
- global.requestAnimationFrame = vi.fn((cb) => setTimeout(cb, 16))
24
- global.cancelAnimationFrame = vi.fn(clearTimeout)
23
+ global.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => setTimeout(cb, 16) as unknown as number)
24
+ global.cancelAnimationFrame = vi.fn((id: number) => clearTimeout(id as unknown as NodeJS.Timeout))
25
25
 
26
26
  // Mock performance API
27
27
  Object.defineProperty(global, "performance", {
@@ -54,7 +54,17 @@ global.MediaSource = vi.fn().mockImplementation(() => ({
54
54
  sourceBuffers: [],
55
55
  addSourceBuffer: vi.fn(),
56
56
  endOfStream: vi.fn()
57
- }))
57
+ })) as unknown as typeof MediaSource
58
+
59
+ Object.defineProperty(global.MediaSource, "canConstructInDedicatedWorker", {
60
+ writable: true,
61
+ value: false
62
+ })
63
+
64
+ Object.defineProperty(global.MediaSource, "isTypeSupported", {
65
+ writable: true,
66
+ value: vi.fn((type: string) => true)
67
+ })
58
68
 
59
69
  global.SourceBuffer = vi.fn().mockImplementation(() => ({
60
70
  appendBuffer: vi.fn(),
@@ -173,6 +183,14 @@ global.AudioContext = vi.fn().mockImplementation(() => ({
173
183
  }))
174
184
 
175
185
  // Custom test utilities
186
+ declare global {
187
+ var testUtils: {
188
+ simulateMediaEvent: (element: HTMLMediaElement, eventType: string, properties?: any) => void
189
+ simulateNetworkCondition: (condition: "slow" | "fast" | "offline") => void
190
+ simulateDevice: (device: "mobile" | "tablet" | "desktop") => void
191
+ }
192
+ }
193
+
176
194
  global.testUtils = {
177
195
  // Helper to simulate media events
178
196
  simulateMediaEvent: (element: HTMLMediaElement, eventType: string, properties?: any) => {
@@ -0,0 +1,29 @@
1
+ declare module "*.gif" {
2
+ const src: string
3
+ export default src
4
+ }
5
+
6
+ declare module "*.png" {
7
+ const src: string
8
+ export default src
9
+ }
10
+
11
+ declare module "*.jpg" {
12
+ const src: string
13
+ export default src
14
+ }
15
+
16
+ declare module "*.jpeg" {
17
+ const src: string
18
+ export default src
19
+ }
20
+
21
+ declare module "*.webp" {
22
+ const src: string
23
+ export default src
24
+ }
25
+
26
+ declare module "*.svg" {
27
+ const src: string
28
+ export default src
29
+ }
@@ -69,6 +69,7 @@ export interface MediaPlayerControls {
69
69
  pause: () => void
70
70
  toggle: () => void
71
71
  seek: (time: number) => void
72
+ setCurrentTime: (time: number) => void
72
73
  setVolume: (volume: number) => void
73
74
  mute: () => void
74
75
  unmute: () => void
@@ -89,6 +90,10 @@ export interface MediaPlayerConfig {
89
90
  keyboardControls?: boolean
90
91
  showControls?: boolean
91
92
  customControls?: boolean
93
+ enablePerformanceMonitoring?: boolean
94
+ bufferingStrategy?: "aggressive" | "conservative" | "adaptive"
95
+ preloadStrategy?: "eager" | "lazy" | "progressive"
96
+ enableOptimizations?: boolean
92
97
  }
93
98
 
94
99
  export interface MediaPlayerProps extends MediaPlayerConfig {
@@ -188,3 +193,11 @@ export type VolumeOrientation = "horizontal" | "vertical"
188
193
 
189
194
  // Import BufferedRange type for external use
190
195
  import type { BufferedRange } from "../hooks/useVideoPlayer"
196
+
197
+ // Media Player Instance for plugins
198
+ export interface MediaPlayerInstance {
199
+ state: MediaPlayerState
200
+ controls: MediaPlayerControls
201
+ element: HTMLVideoElement | HTMLAudioElement | null
202
+ config: MediaPlayerConfig
203
+ }
@@ -1 +0,0 @@
1
- {"version":3,"file":"media.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"media.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"store.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";"}