@therealtinhtute/baroiplayer 1.0.0

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 (244) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/context/media.js +2 -0
  4. package/dist/context/media.js.map +1 -0
  5. package/dist/context/media.mjs +15 -0
  6. package/dist/context/media.mjs.map +1 -0
  7. package/dist/context/store.js +2 -0
  8. package/dist/context/store.js.map +1 -0
  9. package/dist/context/store.mjs +5 -0
  10. package/dist/context/store.mjs.map +1 -0
  11. package/dist/context-BMteAJDN.js +1140 -0
  12. package/dist/context-BMteAJDN.js.map +1 -0
  13. package/dist/context-DCJ_scmO.cjs +23 -0
  14. package/dist/context-DCJ_scmO.cjs.map +1 -0
  15. package/dist/core-events-B5W_j8yU.js +480 -0
  16. package/dist/core-events-B5W_j8yU.js.map +1 -0
  17. package/dist/core-events-CN0-Vhhq.cjs +2 -0
  18. package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
  19. package/dist/core-plugins-Bq9KLany.js +696 -0
  20. package/dist/core-plugins-Bq9KLany.js.map +1 -0
  21. package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
  22. package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
  23. package/dist/core.js +37 -0
  24. package/dist/core.js.map +1 -0
  25. package/dist/core.mjs +1217 -0
  26. package/dist/core.mjs.map +1 -0
  27. package/dist/enhanced-components-DHv0MFvG.js +1073 -0
  28. package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
  29. package/dist/enhanced-components-DTIObgB7.cjs +2 -0
  30. package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
  31. package/dist/hooks-C_r8wexn.cjs +4 -0
  32. package/dist/hooks-C_r8wexn.cjs.map +1 -0
  33. package/dist/hooks-Da2wVXlF.js +1630 -0
  34. package/dist/hooks-Da2wVXlF.js.map +1 -0
  35. package/dist/index.css +1 -0
  36. package/dist/index.js +112 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +6126 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/minimal-components-DSq5sQpy.js +2002 -0
  41. package/dist/minimal-components-DSq5sQpy.js.map +1 -0
  42. package/dist/minimal-components-DflWiihN.cjs +2 -0
  43. package/dist/minimal-components-DflWiihN.cjs.map +1 -0
  44. package/dist/minimal-components.css +1 -0
  45. package/dist/mona-loading.gif +0 -0
  46. package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
  47. package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
  48. package/dist/performance-monitor-rMW1RgV3.js +302 -0
  49. package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
  50. package/dist/utils-BnscpYYd.js +2798 -0
  51. package/dist/utils-BnscpYYd.js.map +1 -0
  52. package/dist/utils-D_czS_0K.cjs +9 -0
  53. package/dist/utils-D_czS_0K.cjs.map +1 -0
  54. package/dist/utils.js +2 -0
  55. package/dist/utils.js.map +1 -0
  56. package/dist/utils.mjs +7 -0
  57. package/dist/utils.mjs.map +1 -0
  58. package/package.json +114 -0
  59. package/src/adapters/videojs-adapter.tsx +470 -0
  60. package/src/assets/mona-loading.gif +0 -0
  61. package/src/components/AnimateLoading.tsx +115 -0
  62. package/src/components/LoadingState.tsx +248 -0
  63. package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
  64. package/src/components/audio-player.tsx +85 -0
  65. package/src/components/basic/index.ts +37 -0
  66. package/src/components/enhanced/advanced-features.tsx +316 -0
  67. package/src/components/enhanced/debug-panel.tsx +422 -0
  68. package/src/components/enhanced/enhanced-controls.tsx +600 -0
  69. package/src/components/enhanced/enhanced-media-player.tsx +509 -0
  70. package/src/components/enhanced/error-handling.tsx +345 -0
  71. package/src/components/enhanced/index.ts +39 -0
  72. package/src/components/enhanced/media-player-enhanced.tsx +349 -0
  73. package/src/components/examples/basic-video-player.tsx +55 -0
  74. package/src/components/examples/custom-controls-example.tsx +63 -0
  75. package/src/components/examples/enhanced-controls-example.tsx +114 -0
  76. package/src/components/examples/enhanced-media-player-example.tsx +293 -0
  77. package/src/components/examples/loading-states-demo.tsx +142 -0
  78. package/src/components/examples/react-19-demo.tsx +381 -0
  79. package/src/components/examples/volume-orientation-demo.tsx +114 -0
  80. package/src/components/icon-demo.tsx +209 -0
  81. package/src/components/icons/index.tsx +166 -0
  82. package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
  83. package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
  84. package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
  85. package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
  86. package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
  87. package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
  88. package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
  89. package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
  90. package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
  91. package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
  92. package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
  93. package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
  94. package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
  95. package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
  96. package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
  97. package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
  98. package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
  99. package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
  100. package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
  101. package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
  102. package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
  103. package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
  104. package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
  105. package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
  106. package/src/components/legacy.tsx +93 -0
  107. package/src/components/media-metadata-display.tsx +100 -0
  108. package/src/components/media-player-audio.tsx +39 -0
  109. package/src/components/media-player-controls.tsx +226 -0
  110. package/src/components/media-player-error.tsx +163 -0
  111. package/src/components/media-player-fullscreen.tsx +109 -0
  112. package/src/components/media-player-hls.tsx +111 -0
  113. package/src/components/media-player-loading.tsx +76 -0
  114. package/src/components/media-player-play.tsx +75 -0
  115. package/src/components/media-player-root.tsx +340 -0
  116. package/src/components/media-player-seek.tsx +168 -0
  117. package/src/components/media-player-time.tsx +99 -0
  118. package/src/components/media-player-tooltip.tsx +58 -0
  119. package/src/components/media-player-video.tsx +39 -0
  120. package/src/components/media-player-volume.tsx +155 -0
  121. package/src/components/media-player.tsx +257 -0
  122. package/src/components/media-suspense.tsx +362 -0
  123. package/src/components/player-settings-form.tsx +163 -0
  124. package/src/components/react-player-wrapper.tsx +651 -0
  125. package/src/components/simple-video-player.tsx +60 -0
  126. package/src/components/ui/enhanced-seek-bar.tsx +235 -0
  127. package/src/components/ui/fullscreen-button.tsx +60 -0
  128. package/src/components/ui/media-resource-preloader.tsx +395 -0
  129. package/src/components/ui/play-button.tsx +107 -0
  130. package/src/components/ui/seek-bar.tsx +161 -0
  131. package/src/components/ui/time-display.tsx +40 -0
  132. package/src/components/ui/volume-control.tsx +143 -0
  133. package/src/components/unified/base-media-player.tsx +246 -0
  134. package/src/components/unified/media-player.tsx +75 -0
  135. package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
  136. package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
  137. package/src/components/variants/minimal/MinimalControls.tsx +142 -0
  138. package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
  139. package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
  140. package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
  141. package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
  142. package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
  143. package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
  144. package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
  145. package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
  146. package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
  147. package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
  148. package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
  149. package/src/components/variants/minimal/components/RightControls.tsx +173 -0
  150. package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
  151. package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
  152. package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
  153. package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
  154. package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
  155. package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
  156. package/src/components/variants/minimal/index.ts +23 -0
  157. package/src/components/video-player.tsx +100 -0
  158. package/src/context/error-handling.ts +394 -0
  159. package/src/context/media-context.tsx +208 -0
  160. package/src/context/media-provider.tsx +265 -0
  161. package/src/context/media-store.ts +379 -0
  162. package/src/context/persistence.ts +453 -0
  163. package/src/core/events/event-system.ts +442 -0
  164. package/src/core/events/hooks.ts +302 -0
  165. package/src/core/events/index.ts +22 -0
  166. package/src/core/events/middleware.ts +269 -0
  167. package/src/core/index.ts +22 -0
  168. package/src/core/optimization/bundle-optimizer.tsx +308 -0
  169. package/src/core/performance/cicd.ts +96 -0
  170. package/src/core/performance/index.ts +38 -0
  171. package/src/core/performance/media-loading-optimizer.ts +538 -0
  172. package/src/core/performance/performance-budgets.ts +432 -0
  173. package/src/core/performance/performance-monitor.tsx +476 -0
  174. package/src/core/performance/performance-testing.ts +459 -0
  175. package/src/core/plugins/base-plugin.ts +224 -0
  176. package/src/core/plugins/enhanced-plugin-system.ts +411 -0
  177. package/src/core/plugins/examples/analytics-plugin.ts +242 -0
  178. package/src/core/plugins/index.ts +39 -0
  179. package/src/core/plugins/registry.ts +327 -0
  180. package/src/core/plugins/types.ts +159 -0
  181. package/src/core/plugins/utils.ts +316 -0
  182. package/src/core/progressive-loading.tsx +322 -0
  183. package/src/core/providers/enhanced-hls-provider.ts +339 -0
  184. package/src/core/providers/service-provider.tsx +168 -0
  185. package/src/core/utils.tsx +38 -0
  186. package/src/features/media-session/media-session-manager.ts +429 -0
  187. package/src/features/picture-in-picture/pip-manager.ts +429 -0
  188. package/src/features/playlist/playlist-manager.ts +606 -0
  189. package/src/features/subtitles/subtitle-manager.ts +441 -0
  190. package/src/features/subtitles/subtitle-parser.ts +280 -0
  191. package/src/features/thumbnails/thumbnail-generator.ts +519 -0
  192. package/src/hooks/accessibility/index.ts +27 -0
  193. package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
  194. package/src/hooks/accessibility/use-focus-management.ts +176 -0
  195. package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
  196. package/src/hooks/accessibility/use-screen-reader.ts +182 -0
  197. package/src/hooks/core/index.ts +47 -0
  198. package/src/hooks/legacy.ts +27 -0
  199. package/src/hooks/use-enhanced-media-player.ts +468 -0
  200. package/src/hooks/use-intelligent-preloading.ts +358 -0
  201. package/src/hooks/use-keyboard-controls.ts +115 -0
  202. package/src/hooks/use-media-actions.ts +175 -0
  203. package/src/hooks/use-media-metadata.ts +89 -0
  204. package/src/hooks/use-media-optimistic.ts +270 -0
  205. package/src/hooks/use-media-player.ts +312 -0
  206. package/src/hooks/use-media-state.ts +190 -0
  207. package/src/hooks/use-memory-optimization.ts +358 -0
  208. package/src/hooks/use-optimistic-controls.ts +92 -0
  209. package/src/hooks/use-optimized-media.ts +263 -0
  210. package/src/hooks/use-touch-gestures.ts +417 -0
  211. package/src/hooks/useEnhancedSubtitles.ts +258 -0
  212. package/src/hooks/useKeyboardShortcuts.ts +74 -0
  213. package/src/hooks/useMediaControls.ts +52 -0
  214. package/src/hooks/useScreenshot.ts +57 -0
  215. package/src/hooks/useSubtitles.ts +91 -0
  216. package/src/hooks/useVideoPlayer.ts +232 -0
  217. package/src/icons/components/FullscreenIcon.tsx +37 -0
  218. package/src/icons/components/SettingsIcon.tsx +38 -0
  219. package/src/icons/components/animated/LoadingIcon.tsx +43 -0
  220. package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
  221. package/src/icons/components/media/PauseIcon.tsx +39 -0
  222. package/src/icons/components/media/PlayIcon.tsx +38 -0
  223. package/src/icons/components/volume/VolumeIcon.tsx +39 -0
  224. package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
  225. package/src/icons/index.ts +56 -0
  226. package/src/icons/provider.tsx +64 -0
  227. package/src/icons/types.ts +40 -0
  228. package/src/index-enhanced.ts +35 -0
  229. package/src/index-legacy.ts +326 -0
  230. package/src/index.css +102 -0
  231. package/src/index.ts +216 -0
  232. package/src/lib/deprecation.ts +51 -0
  233. package/src/lib/format-time.ts +37 -0
  234. package/src/lib/index.ts +3 -0
  235. package/src/lib/utils.ts +6 -0
  236. package/src/styles/globals.css +462 -0
  237. package/src/styles/minimal-player.css +589 -0
  238. package/src/styles/pixel-art.css +46 -0
  239. package/src/styles/videojs.css +100 -0
  240. package/src/test/setup.ts +79 -0
  241. package/src/types/events.ts +47 -0
  242. package/src/types/formats.ts +95 -0
  243. package/src/types/index.ts +190 -0
  244. package/src/types/videojs-plugins.d.ts +14 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Unified MediaPlayer - The primary media player component
3
+ * Consolidates MediaPlayer, VideoPlayer, and AudioPlayer into one component
4
+ */
5
+
6
+ "use client"
7
+
8
+ import { forwardRef } from "react"
9
+ import { BaseMediaPlayer } from "./base-media-player"
10
+ import type { MediaPlayerProps } from "../../types"
11
+
12
+ export interface UnifiedMediaPlayerProps extends MediaPlayerProps {
13
+ // Media type - auto-detect if not specified
14
+ mediaType?: 'video' | 'audio' | 'auto'
15
+
16
+ // Video-specific props (ignored for audio)
17
+ poster?: string
18
+ aspectRatio?: string
19
+ width?: string | number
20
+ height?: string | number
21
+ }
22
+
23
+ /**
24
+ * The primary MediaPlayer component
25
+ *
26
+ * @example
27
+ * // Video player (auto-detected)
28
+ * <MediaPlayer src="/video.mp4" poster="/poster.jpg" />
29
+ *
30
+ * @example
31
+ * // Audio player (auto-detected)
32
+ * <MediaPlayer src="/audio.mp3" />
33
+ *
34
+ * @example
35
+ * // Explicit media type
36
+ * <MediaPlayer src="/media.mp4" mediaType="video" />
37
+ */
38
+ export const MediaPlayer = forwardRef<HTMLDivElement, UnifiedMediaPlayerProps>((props, ref) => {
39
+ return <BaseMediaPlayer {...props} ref={ref} />
40
+ })
41
+
42
+ MediaPlayer.displayName = "MediaPlayer"
43
+
44
+ /**
45
+ * Video-specific component with video defaults
46
+ */
47
+ export const VideoPlayer = forwardRef<HTMLDivElement, UnifiedMediaPlayerProps>((props, ref) => {
48
+ return (
49
+ <BaseMediaPlayer
50
+ {...props}
51
+ ref={ref}
52
+ mediaType="video"
53
+ aspectRatio={props.aspectRatio || "16/9"}
54
+ showControls={props.showControls ?? true}
55
+ />
56
+ )
57
+ })
58
+
59
+ VideoPlayer.displayName = "VideoPlayer"
60
+
61
+ /**
62
+ * Audio-specific component with audio defaults
63
+ */
64
+ export const AudioPlayer = forwardRef<HTMLDivElement, UnifiedMediaPlayerProps>((props, ref) => {
65
+ return (
66
+ <BaseMediaPlayer
67
+ {...props}
68
+ ref={ref}
69
+ mediaType="audio"
70
+ showControls={props.showControls ?? true}
71
+ />
72
+ )
73
+ })
74
+
75
+ AudioPlayer.displayName = "AudioPlayer"
@@ -0,0 +1,561 @@
1
+ "use client"
2
+
3
+ import React, { useCallback, useEffect, useRef, useState } from "react"
4
+ import { MediaPlayerRoot } from "../../media-player-root"
5
+ import { MediaPlayerVideo } from "../../media-player-video"
6
+ import { MinimalControls } from "./MinimalControls"
7
+ import { MinimalOverlay } from "./MinimalOverlay"
8
+ import { MinimalLoading } from "./MinimalLoading"
9
+ import { cn } from "../../../lib/utils"
10
+ import type { IconSet } from "../../icons"
11
+ import { defaultMinimalIcons } from "../../icons"
12
+ import {
13
+ useKeyboardShortcuts,
14
+ createMediaKeyboardShortcuts,
15
+ useMediaPlayerFocus,
16
+ useMediaScreenReader,
17
+ useAccessibilityStyles,
18
+ useFocusVisible
19
+ } from '../../../hooks/accessibility'
20
+ import { KeyboardHelpOverlay } from '../../accessibility/keyboard-help-overlay'
21
+
22
+ export interface AccessibleMinimalPlayerProps {
23
+ src: string | string[]
24
+ poster?: string
25
+ autoPlay?: boolean
26
+ muted?: boolean
27
+ loop?: boolean
28
+ autoHideControls?: boolean
29
+ hideDelay?: number
30
+ theme?: "dark" | "light" | "transparent"
31
+ customIcons?: IconSet
32
+ showCenterPlayButton?: boolean
33
+ showLoadingSpinner?: boolean
34
+ className?: string
35
+ onPlay?: () => void
36
+ onPause?: () => void
37
+ onEnded?: () => void
38
+ onTimeUpdate?: (currentTime: number) => void
39
+ onVolumeChange?: (volume: number) => void
40
+ onSeek?: (time: number) => void
41
+ onError?: (error: Error) => void
42
+ watermark?: {
43
+ src: string
44
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"
45
+ opacity?: number
46
+ size?: "small" | "medium" | "large"
47
+ }
48
+ qualities?: Array<{
49
+ src: string
50
+ quality: string
51
+ default?: boolean
52
+ }>
53
+ showQualitySelector?: boolean
54
+ showSpeedControl?: boolean
55
+ thumbnails?: string
56
+ // Accessibility props
57
+ enableKeyboardShortcuts?: boolean
58
+ showKeyboardHelpButton?: boolean
59
+ ariaLabel?: string
60
+ ariaDescribedBy?: string
61
+ }
62
+
63
+ export function AccessibleMinimalPlayer({
64
+ src,
65
+ poster,
66
+ autoPlay = false,
67
+ muted = false,
68
+ loop = false,
69
+ autoHideControls = true,
70
+ hideDelay = 2500,
71
+ theme = "dark",
72
+ customIcons,
73
+ showCenterPlayButton = true,
74
+ showLoadingSpinner = true,
75
+ className,
76
+ onPlay,
77
+ onPause,
78
+ onEnded,
79
+ onTimeUpdate,
80
+ onVolumeChange,
81
+ onSeek,
82
+ onError,
83
+ watermark,
84
+ qualities,
85
+ showQualitySelector = false,
86
+ showSpeedControl = false,
87
+ thumbnails,
88
+ enableKeyboardShortcuts = true,
89
+ showKeyboardHelpButton = true,
90
+ ariaLabel = "Media player",
91
+ ariaDescribedBy,
92
+ }: AccessibleMinimalPlayerProps) {
93
+ // Existing state
94
+ const [controlsVisible, setControlsVisible] = useState(true)
95
+ const [isPlaying, setIsPlaying] = useState(false)
96
+ const [isLoading, setIsLoading] = useState(false)
97
+ const [error, setError] = useState<string | null>(null)
98
+ const [volume, setVolume] = useState(1)
99
+ const [isMuted, setIsMuted] = useState(muted)
100
+ const [currentTime, setCurrentTime] = useState(0)
101
+ const [duration, setDuration] = useState(0)
102
+ const [isFullscreen, setIsFullscreen] = useState(false)
103
+
104
+ // Accessibility state
105
+ const [showKeyboardHelp, setShowKeyboardHelp] = useState(false)
106
+
107
+ // Refs
108
+ const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null)
109
+ const containerRef = useRef<HTMLDivElement | null>(null)
110
+ const videoRef = useRef<HTMLVideoElement | null>(null)
111
+ const [isHovered, setIsHovered] = useState(false)
112
+ const [userInteracting, setUserInteracting] = useState(false)
113
+
114
+ // Accessibility hooks
115
+ const { applyStyles, preferences } = useAccessibilityStyles()
116
+ const focusManager = useMediaPlayerFocus()
117
+ const screenReader = useMediaScreenReader()
118
+ const isFocusVisible = useFocusVisible()
119
+
120
+ // Merge default icons with custom icons
121
+ const icons = { ...defaultMinimalIcons, ...customIcons }
122
+
123
+ // Media control functions
124
+ const togglePlayPause = useCallback(() => {
125
+ if (!videoRef.current) return
126
+
127
+ if (isPlaying) {
128
+ videoRef.current.pause()
129
+ } else {
130
+ videoRef.current.play()
131
+ }
132
+ }, [isPlaying])
133
+
134
+ const seekForward = useCallback((seconds = 10) => {
135
+ if (!videoRef.current) return
136
+
137
+ const newTime = Math.min(videoRef.current.currentTime + seconds, duration)
138
+ videoRef.current.currentTime = newTime
139
+ screenReader.announceTime(newTime, duration)
140
+ }, [duration, screenReader])
141
+
142
+ const seekBackward = useCallback((seconds = 10) => {
143
+ if (!videoRef.current) return
144
+
145
+ const newTime = Math.max(videoRef.current.currentTime - seconds, 0)
146
+ videoRef.current.currentTime = newTime
147
+ screenReader.announceTime(newTime, duration)
148
+ }, [duration, screenReader])
149
+
150
+ const volumeUp = useCallback(() => {
151
+ if (!videoRef.current) return
152
+
153
+ const newVolume = Math.min(volume + 0.1, 1)
154
+ setVolume(newVolume)
155
+ videoRef.current.volume = newVolume
156
+ if (isMuted) {
157
+ setIsMuted(false)
158
+ videoRef.current.muted = false
159
+ }
160
+ screenReader.announceVolume(newVolume, false)
161
+ }, [volume, isMuted, screenReader])
162
+
163
+ const volumeDown = useCallback(() => {
164
+ if (!videoRef.current) return
165
+
166
+ const newVolume = Math.max(volume - 0.1, 0)
167
+ setVolume(newVolume)
168
+ videoRef.current.volume = newVolume
169
+ if (isMuted) {
170
+ setIsMuted(false)
171
+ videoRef.current.muted = false
172
+ }
173
+ screenReader.announceVolume(newVolume, false)
174
+ }, [volume, isMuted, screenReader])
175
+
176
+ const toggleMute = useCallback(() => {
177
+ if (!videoRef.current) return
178
+
179
+ const newMuted = !isMuted
180
+ setIsMuted(newMuted)
181
+ videoRef.current.muted = newMuted
182
+ screenReader.announceVolume(volume, newMuted)
183
+ }, [isMuted, volume, screenReader])
184
+
185
+ const toggleFullscreen = useCallback(async () => {
186
+ if (!containerRef.current) return
187
+
188
+ try {
189
+ if (!isFullscreen) {
190
+ if (containerRef.current.requestFullscreen) {
191
+ await containerRef.current.requestFullscreen()
192
+ }
193
+ } else {
194
+ if (document.exitFullscreen) {
195
+ await document.exitFullscreen()
196
+ }
197
+ }
198
+ } catch (error) {
199
+ console.error('Fullscreen toggle failed:', error)
200
+ }
201
+ }, [isFullscreen])
202
+
203
+ const showHelp = useCallback(() => {
204
+ setShowKeyboardHelp(true)
205
+ }, [])
206
+
207
+ // Create keyboard shortcuts
208
+ const keyboardShortcuts = createMediaKeyboardShortcuts({
209
+ play: () => videoRef.current?.play(),
210
+ pause: () => videoRef.current?.pause(),
211
+ togglePlayPause,
212
+ seekForward,
213
+ seekBackward,
214
+ volumeUp,
215
+ volumeDown,
216
+ toggleMute,
217
+ toggleFullscreen,
218
+ showHelp: showKeyboardHelpButton ? showHelp : undefined
219
+ })
220
+
221
+ // Enable keyboard shortcuts
222
+ const { shortcuts } = useKeyboardShortcuts({
223
+ shortcuts: enableKeyboardShortcuts ? keyboardShortcuts : [],
224
+ enabled: enableKeyboardShortcuts,
225
+ target: containerRef.current
226
+ })
227
+
228
+ // Auto-hide controls logic
229
+ const resetHideTimer = useCallback(() => {
230
+ if (!autoHideControls) return
231
+
232
+ if (hideTimeoutRef.current) {
233
+ clearTimeout(hideTimeoutRef.current)
234
+ }
235
+ setControlsVisible(true)
236
+
237
+ if (!userInteracting && isPlaying && !preferences.prefersReducedMotion) {
238
+ hideTimeoutRef.current = setTimeout(() => {
239
+ setControlsVisible(false)
240
+ }, hideDelay)
241
+ }
242
+ }, [autoHideControls, userInteracting, isPlaying, hideDelay, preferences.prefersReducedMotion])
243
+
244
+ // Handle mouse movement
245
+ const handleMouseMove = useCallback(() => {
246
+ resetHideTimer()
247
+ }, [resetHideTimer])
248
+
249
+ // Handle mouse enter/leave
250
+ const handleMouseEnter = useCallback(() => {
251
+ setIsHovered(true)
252
+ setControlsVisible(true)
253
+ if (hideTimeoutRef.current) {
254
+ clearTimeout(hideTimeoutRef.current)
255
+ }
256
+ }, [])
257
+
258
+ const handleMouseLeave = useCallback(() => {
259
+ setIsHovered(false)
260
+ if (isPlaying && autoHideControls && !userInteracting) {
261
+ hideTimeoutRef.current = setTimeout(() => {
262
+ setControlsVisible(false)
263
+ }, 500)
264
+ }
265
+ }, [isPlaying, autoHideControls, userInteracting])
266
+
267
+ // Handle user interaction
268
+ const handleUserInteractionStart = useCallback(() => {
269
+ setUserInteracting(true)
270
+ setControlsVisible(true)
271
+ if (hideTimeoutRef.current) {
272
+ clearTimeout(hideTimeoutRef.current)
273
+ }
274
+ }, [])
275
+
276
+ const handleUserInteractionEnd = useCallback(() => {
277
+ setUserInteracting(false)
278
+ resetHideTimer()
279
+ }, [resetHideTimer])
280
+
281
+ // Video event handlers
282
+ const handlePlayStateChange = useCallback((playing: boolean) => {
283
+ setIsPlaying(playing)
284
+ screenReader.announcePlaybackState(playing)
285
+
286
+ if (playing) {
287
+ onPlay?.()
288
+ resetHideTimer()
289
+ } else {
290
+ onPause?.()
291
+ setControlsVisible(true)
292
+ if (hideTimeoutRef.current) {
293
+ clearTimeout(hideTimeoutRef.current)
294
+ }
295
+ }
296
+ }, [onPlay, onPause, resetHideTimer, screenReader])
297
+
298
+ const handleTimeUpdate = useCallback((time: number) => {
299
+ setCurrentTime(time)
300
+ onTimeUpdate?.(time)
301
+ }, [onTimeUpdate])
302
+
303
+ const handleLoadedMetadata = useCallback((event: React.SyntheticEvent<HTMLVideoElement>) => {
304
+ const video = event.currentTarget
305
+ setDuration(video.duration)
306
+ setIsLoading(false)
307
+ }, [])
308
+
309
+ const handleLoadStart = useCallback(() => {
310
+ setIsLoading(true)
311
+ setError(null)
312
+ screenReader.announceLoading(true)
313
+ }, [screenReader])
314
+
315
+ const handleLoadedData = useCallback(() => {
316
+ setIsLoading(false)
317
+ screenReader.announceLoading(false)
318
+ }, [screenReader])
319
+
320
+ const handleError = useCallback((e: Error) => {
321
+ setIsLoading(false)
322
+ setError(e.message || "An error occurred")
323
+ screenReader.announceError(e.message || "An error occurred")
324
+ onError?.(e)
325
+ }, [onError, screenReader])
326
+
327
+ const handleVolumeChange = useCallback((event: React.SyntheticEvent<HTMLVideoElement>) => {
328
+ const video = event.currentTarget
329
+ const newVolume = video.volume
330
+ const newMuted = video.muted
331
+
332
+ setVolume(newVolume)
333
+ setIsMuted(newMuted)
334
+ onVolumeChange?.(newVolume)
335
+ }, [onVolumeChange])
336
+
337
+ // Fullscreen change handler
338
+ useEffect(() => {
339
+ const handleFullscreenChange = () => {
340
+ const isCurrentlyFullscreen = !!document.fullscreenElement
341
+ setIsFullscreen(isCurrentlyFullscreen)
342
+ screenReader.announceFullscreen(isCurrentlyFullscreen)
343
+ }
344
+
345
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
346
+ return () => {
347
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
348
+ }
349
+ }, [screenReader])
350
+
351
+ // Cleanup on unmount
352
+ useEffect(() => {
353
+ return () => {
354
+ if (hideTimeoutRef.current) {
355
+ clearTimeout(hideTimeoutRef.current)
356
+ }
357
+ }
358
+ }, [])
359
+
360
+ // Determine video source
361
+ const videoSrc = qualities && qualities.length > 0
362
+ ? qualities.find(q => q.default)?.src || qualities[0]?.src || (typeof src === 'string' ? src : src[0])
363
+ : (typeof src === 'string' ? src : src[0])
364
+
365
+ return (
366
+ <>
367
+ <div
368
+ ref={containerRef}
369
+ className={cn(
370
+ "accessible-minimal-player theme-minimal relative overflow-hidden rounded-lg bg-black",
371
+ `theme-minimal-${theme}`,
372
+ isFocusVisible && "focus-visible-enabled",
373
+ className
374
+ )}
375
+ data-theme={theme}
376
+ onMouseMove={handleMouseMove}
377
+ onMouseEnter={handleMouseEnter}
378
+ onMouseLeave={handleMouseLeave}
379
+ role="application"
380
+ aria-label={ariaLabel}
381
+ aria-describedby={ariaDescribedBy}
382
+ tabIndex={0}
383
+ data-testid="media-player"
384
+ >
385
+ <MediaPlayerRoot
386
+ className="relative h-full w-full"
387
+ ref={focusManager.containerRef}
388
+ >
389
+ <MediaPlayerVideo
390
+ ref={videoRef}
391
+ src={videoSrc}
392
+ poster={poster}
393
+ autoPlay={autoPlay}
394
+ muted={muted}
395
+ loop={loop}
396
+ onPlay={() => handlePlayStateChange(true)}
397
+ onPause={() => handlePlayStateChange(false)}
398
+ onEnded={onEnded}
399
+ onTimeUpdate={(e) => handleTimeUpdate(e.currentTarget.currentTime)}
400
+ onLoadStart={handleLoadStart}
401
+ onLoadedData={handleLoadedData}
402
+ onLoadedMetadata={handleLoadedMetadata}
403
+ onError={(e) => handleError(new Error('Video loading failed'))}
404
+ onVolumeChange={handleVolumeChange}
405
+ className="h-full w-full object-contain"
406
+ aria-hidden="true"
407
+ />
408
+
409
+ {watermark && (
410
+ <div
411
+ className={cn(
412
+ "pointer-events-none absolute z-10",
413
+ {
414
+ "top-4 left-4": watermark.position === "top-left",
415
+ "top-4 right-4": watermark.position === "top-right",
416
+ "bottom-4 left-4": watermark.position === "bottom-left",
417
+ "right-4 bottom-4": watermark.position === "bottom-right",
418
+ }
419
+ )}
420
+ style={{ opacity: watermark.opacity || 0.7 }}
421
+ aria-hidden="true"
422
+ >
423
+ <img
424
+ src={watermark.src}
425
+ alt=""
426
+ className={cn(
427
+ "h-auto",
428
+ {
429
+ "w-12": watermark.size === "small",
430
+ "w-16": watermark.size === "medium",
431
+ "w-24": watermark.size === "large",
432
+ }
433
+ )}
434
+ />
435
+ </div>
436
+ )}
437
+
438
+ <MinimalOverlay
439
+ isVisible={showCenterPlayButton && (!isPlaying || isLoading)}
440
+ icons={icons}
441
+ onPlay={togglePlayPause}
442
+ isLoading={isLoading}
443
+ showLoadingSpinner={showLoadingSpinner}
444
+ error={error}
445
+ theme={theme}
446
+ />
447
+
448
+ <MinimalControls
449
+ isVisible={controlsVisible || !autoHideControls || isHovered}
450
+ isPlaying={isPlaying}
451
+ volume={volume}
452
+ isMuted={isMuted}
453
+ currentTime={currentTime}
454
+ duration={duration}
455
+ isFullscreen={isFullscreen}
456
+ icons={icons}
457
+ theme={theme}
458
+ qualities={qualities}
459
+ showQualitySelector={showQualitySelector}
460
+ showSpeedControl={showSpeedControl}
461
+ thumbnails={thumbnails}
462
+ onPlayPause={togglePlayPause}
463
+ onVolumeChange={(vol) => {
464
+ setVolume(vol)
465
+ if (videoRef.current) {
466
+ videoRef.current.volume = vol
467
+ }
468
+ }}
469
+ onMuteToggle={toggleMute}
470
+ onSeek={(time) => {
471
+ if (videoRef.current) {
472
+ videoRef.current.currentTime = time
473
+ onSeek?.(time)
474
+ }
475
+ }}
476
+ onFullscreenToggle={toggleFullscreen}
477
+ onUserInteractionStart={handleUserInteractionStart}
478
+ onUserInteractionEnd={handleUserInteractionEnd}
479
+ showKeyboardHelpButton={showKeyboardHelpButton}
480
+ onShowKeyboardHelp={showHelp}
481
+ />
482
+
483
+ {showLoadingSpinner && (
484
+ <MinimalLoading
485
+ isVisible={isLoading}
486
+ theme={theme}
487
+ />
488
+ )}
489
+ </MediaPlayerRoot>
490
+ </div>
491
+
492
+ {/* Keyboard Help Overlay */}
493
+ <KeyboardHelpOverlay
494
+ isOpen={showKeyboardHelp}
495
+ onClose={() => setShowKeyboardHelp(false)}
496
+ shortcuts={shortcuts}
497
+ />
498
+
499
+ {/* Screen reader only status region */}
500
+ <div
501
+ id={`${ariaDescribedBy || 'media-player'}-status`}
502
+ className="sr-only"
503
+ aria-live="polite"
504
+ aria-atomic="true"
505
+ >
506
+ {isLoading && "Loading media"}
507
+ {error && `Error: ${error}`}
508
+ {!isLoading && !error && (isPlaying ? "Playing" : "Paused")}
509
+ </div>
510
+
511
+ <style jsx global>{`
512
+ .accessible-minimal-player {
513
+ /* Accessibility-specific styles */
514
+ --media-focus-outline: var(--media-focus-outline-width, 2px) solid #3b82f6;
515
+ --media-transition: ${preferences.prefersReducedMotion ? 'none' : 'var(--media-transition-duration, 200ms)'};
516
+ }
517
+
518
+ .accessible-minimal-player:focus-visible {
519
+ outline: var(--media-focus-outline);
520
+ outline-offset: 2px;
521
+ }
522
+
523
+ .accessible-minimal-player .focus-visible-enabled button:focus-visible,
524
+ .accessible-minimal-player .focus-visible-enabled [role="slider"]:focus-visible {
525
+ outline: var(--media-focus-outline);
526
+ outline-offset: 2px;
527
+ }
528
+
529
+ /* High contrast mode support */
530
+ @media (prefers-contrast: high) {
531
+ .accessible-minimal-player {
532
+ --media-bg-opacity: 1;
533
+ --media-border-width: 2px;
534
+ }
535
+ }
536
+
537
+ /* Reduced motion support */
538
+ @media (prefers-reduced-motion: reduce) {
539
+ .accessible-minimal-player * {
540
+ animation-duration: 0.01ms !important;
541
+ animation-iteration-count: 1 !important;
542
+ transition-duration: 0.01ms !important;
543
+ }
544
+ }
545
+
546
+ /* Screen reader only class */
547
+ .sr-only {
548
+ position: absolute;
549
+ width: 1px;
550
+ height: 1px;
551
+ padding: 0;
552
+ margin: -1px;
553
+ overflow: hidden;
554
+ clip: rect(0, 0, 0, 0);
555
+ white-space: nowrap;
556
+ border: 0;
557
+ }
558
+ `}</style>
559
+ </>
560
+ )
561
+ }