@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.
- package/README.md +1 -2
- package/dist/context/media.js +1 -2
- package/dist/context/media.mjs +3 -15
- package/dist/context/store.js +1 -2
- package/dist/context/store.mjs +3 -5
- package/dist/context-Bzd5QCKM.cjs +7 -0
- package/dist/context-Bzd5QCKM.cjs.map +1 -0
- package/dist/context-DD_SH4tT.js +1053 -0
- package/dist/context-DD_SH4tT.js.map +1 -0
- package/dist/core-events-BRnLMB7s.cjs +2 -0
- package/dist/core-events-BRnLMB7s.cjs.map +1 -0
- package/dist/core-events-v_mKdj5p.js +417 -0
- package/dist/core-events-v_mKdj5p.js.map +1 -0
- package/dist/core-plugins-CMBv-yVh.cjs +2 -0
- package/dist/core-plugins-CMBv-yVh.cjs.map +1 -0
- package/dist/core-plugins-CqCxLkbd.js +754 -0
- package/dist/core-plugins-CqCxLkbd.js.map +1 -0
- package/dist/core.js +7 -29
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +947 -1220
- package/dist/core.mjs.map +1 -1
- package/dist/enhanced-components-BwjBqSA1.js +2858 -0
- package/dist/enhanced-components-BwjBqSA1.js.map +1 -0
- package/dist/enhanced-components-C-ka-XLp.cjs +2 -0
- package/dist/enhanced-components-C-ka-XLp.cjs.map +1 -0
- package/dist/hooks-B-IIWPJr.js +1842 -0
- package/dist/hooks-B-IIWPJr.js.map +1 -0
- package/dist/hooks-DcHzmBwA.cjs +4 -0
- package/dist/hooks-DcHzmBwA.cjs.map +1 -0
- package/dist/index.js +27 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5613 -6210
- package/dist/index.mjs.map +1 -1
- package/dist/minimal-components-BYsMQde5.js +1610 -0
- package/dist/minimal-components-BYsMQde5.js.map +1 -0
- package/dist/minimal-components-CsxQbDp7.cjs +2 -0
- package/dist/minimal-components-CsxQbDp7.cjs.map +1 -0
- package/dist/performance-monitor-DCSijLJg.js +312 -0
- package/dist/performance-monitor-DCSijLJg.js.map +1 -0
- package/dist/performance-monitor-wNFKmqMr.cjs +2 -0
- package/dist/performance-monitor-wNFKmqMr.cjs.map +1 -0
- package/dist/rolldown-runtime-BmowowmI.js +14 -0
- package/dist/rolldown-runtime-mLOUI7ql.cjs +1 -0
- package/dist/utils-D2gQ7c2R.js +64 -0
- package/dist/utils-D2gQ7c2R.js.map +1 -0
- package/dist/utils-DJaAwiZ9.cjs +2 -0
- package/dist/utils-DJaAwiZ9.cjs.map +1 -0
- package/dist/utils.js +1 -2
- package/dist/utils.mjs +3 -11
- package/package.json +28 -22
- package/src/components/AnimateLoading.tsx +14 -10
- package/src/components/accessibility/keyboard-help-overlay.tsx +2 -0
- package/src/components/enhanced/debug-panel.tsx +1 -1
- package/src/components/enhanced/enhanced-media-player.tsx +68 -25
- package/src/components/examples/loading-states-demo.tsx +1 -2
- package/src/components/examples/react-19-demo.tsx +6 -10
- package/src/components/examples/volume-orientation-demo.tsx +1 -2
- package/src/components/legacy.tsx +6 -6
- package/src/components/media-player-video.tsx +5 -12
- package/src/components/media-player.tsx +2 -2
- package/src/components/ui/blur-poster.tsx +18 -15
- package/src/components/ui/enhanced-seek-bar.tsx +2 -2
- package/src/components/ui/media-resource-preloader.tsx +21 -23
- package/src/components/unified/base-media-player.tsx +22 -10
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +58 -38
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +6 -6
- package/src/components/variants/minimal/components/VideoElement.tsx +3 -1
- package/src/core/optimization/bundle-optimizer.tsx +26 -27
- package/src/core/performance/media-loading-optimizer.ts +2 -1
- package/src/core/plugins/__tests__/registry.test.ts +1 -1
- package/src/core/plugins/enhanced-plugin-system.ts +26 -8
- package/src/core/plugins/registry.ts +319 -92
- package/src/core/plugins/types.ts +21 -3
- package/src/core/providers/enhanced-hls-provider.ts +11 -40
- package/src/core/providers/lazy-provider.tsx +5 -42
- package/src/features/media-session/media-session-manager.ts +4 -26
- package/src/features/picture-in-picture/pip-manager.ts +2 -2
- package/src/features/playlist/playlist-manager.ts +26 -18
- package/src/features/subtitles/subtitle-manager.ts +1 -1
- package/src/features/subtitles/subtitle-parser.ts +19 -16
- package/src/features/thumbnails/thumbnail-generator.ts +12 -5
- package/src/hooks/__tests__/currentTime-fix.test.ts +149 -0
- package/src/hooks/accessibility/use-accessibility-preferences.ts +1 -1
- package/src/hooks/accessibility/use-focus-management.ts +2 -2
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +2 -2
- package/src/hooks/accessibility/use-screen-reader.ts +1 -1
- package/src/hooks/core/index.ts +1 -1
- package/src/hooks/use-buffer-health.ts +1 -1
- package/src/hooks/use-enhanced-media-player.ts +107 -9
- package/src/hooks/use-focus-trap.tsx +2 -2
- package/src/hooks/use-intelligent-preloading.ts +23 -3
- package/src/hooks/use-keyboard-shortcuts.ts +2 -2
- package/src/hooks/use-media-player.ts +74 -43
- package/src/hooks/use-memory-optimization.ts +15 -15
- package/src/hooks/use-smart-preload.ts +1 -2
- package/src/hooks/useKeyboardShortcuts.ts +34 -2
- package/src/hooks/useScreenshot.ts +60 -44
- package/src/hooks/useVideoPlayer.ts +50 -4
- package/src/index-legacy.ts +3 -5
- package/src/test/accessibility-utils.ts +7 -7
- package/src/test/media-mocks.ts +41 -28
- package/src/test/setup.ts +22 -4
- package/src/types/assets.d.ts +29 -0
- package/src/types/index.ts +13 -0
- package/dist/context/media.js.map +0 -1
- package/dist/context/media.mjs.map +0 -1
- package/dist/context/store.js.map +0 -1
- package/dist/context/store.mjs.map +0 -1
- package/dist/context-BYwDoSRX.js +0 -1172
- package/dist/context-BYwDoSRX.js.map +0 -1
- package/dist/context-eNS62_ac.cjs +0 -23
- package/dist/context-eNS62_ac.cjs.map +0 -1
- package/dist/core-events-B1iM2F46.cjs +0 -2
- package/dist/core-events-B1iM2F46.cjs.map +0 -1
- package/dist/core-events-DunPHKRE.js +0 -484
- package/dist/core-events-DunPHKRE.js.map +0 -1
- package/dist/core-plugins-CROGvQUy.js +0 -686
- package/dist/core-plugins-CROGvQUy.js.map +0 -1
- package/dist/core-plugins-DtsHBOtF.cjs +0 -2
- package/dist/core-plugins-DtsHBOtF.cjs.map +0 -1
- package/dist/enhanced-components-DUw2KrmQ.js +0 -1142
- package/dist/enhanced-components-DUw2KrmQ.js.map +0 -1
- package/dist/enhanced-components-DXBCHU0v.cjs +0 -2
- package/dist/enhanced-components-DXBCHU0v.cjs.map +0 -1
- package/dist/hooks-BW-JjVgP.cjs +0 -4
- package/dist/hooks-BW-JjVgP.cjs.map +0 -1
- package/dist/hooks-CyX6De5M.js +0 -1693
- package/dist/hooks-CyX6De5M.js.map +0 -1
- package/dist/minimal-components-Bdcpxav5.cjs +0 -2
- package/dist/minimal-components-Bdcpxav5.cjs.map +0 -1
- package/dist/minimal-components-DlRK7Qu_.js +0 -1927
- package/dist/minimal-components-DlRK7Qu_.js.map +0 -1
- package/dist/performance-monitor-C5k6FDXw.js +0 -301
- package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
- package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
- package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
- package/dist/utils-CFOue_9K.cjs +0 -9
- package/dist/utils-CFOue_9K.cjs.map +0 -1
- package/dist/utils-Dzpl2ArK.js +0 -2844
- package/dist/utils-Dzpl2ArK.js.map +0 -1
- package/dist/utils.js.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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 = (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
214
|
-
|
|
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 = {
|
package/src/index-legacy.ts
CHANGED
|
@@ -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
|
-
|
|
136
|
-
|
|
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,
|
package/src/test/media-mocks.ts
CHANGED
|
@@ -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
|
-
//
|
|
78
|
+
// Getters and setters with side effects
|
|
79
|
+
get currentTime() {
|
|
80
|
+
return _currentTime
|
|
81
|
+
},
|
|
75
82
|
set currentTime(value: number) {
|
|
76
|
-
|
|
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
|
|
83
|
-
return
|
|
89
|
+
get paused() {
|
|
90
|
+
return _paused
|
|
84
91
|
},
|
|
85
|
-
|
|
86
92
|
set paused(value: boolean) {
|
|
87
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
124
|
-
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -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":";"}
|