@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,258 @@
1
+ import { useState, useCallback, useMemo, useEffect } from "react"
2
+
3
+ interface SubtitleCue {
4
+ start: number
5
+ end: number
6
+ text: string
7
+ }
8
+
9
+ export interface SubtitleTrack {
10
+ id: string
11
+ label: string
12
+ language?: string
13
+ src?: string
14
+ }
15
+
16
+ export interface UseEnhancedSubtitlesOptions {
17
+ subtitleSrc?: string
18
+ subtitleTracks?: SubtitleTrack[]
19
+ defaultFontSize?: "small" | "medium" | "large"
20
+ onLoadError?: (error: Error) => void
21
+ }
22
+
23
+ export function useEnhancedSubtitles({
24
+ subtitleSrc,
25
+ subtitleTracks = [],
26
+ defaultFontSize = "medium",
27
+ onLoadError
28
+ }: UseEnhancedSubtitlesOptions = {}) {
29
+ const [subtitlesEnabled, setSubtitlesEnabled] = useState(false)
30
+ const [subtitleCues, setSubtitleCues] = useState<SubtitleCue[]>([])
31
+ const [currentSubtitle, setCurrentSubtitle] = useState("")
32
+ const [currentTrackId, setCurrentTrackId] = useState<string | null>(null)
33
+ const [fontSize, setFontSize] = useState<"small" | "medium" | "large">(defaultFontSize)
34
+ const [loadError, setLoadError] = useState<Error | null>(null)
35
+ const [isLoading, setIsLoading] = useState(false)
36
+
37
+ // Size mapping for subtitle font sizes
38
+ const sizeMap = {
39
+ small: "text-sm",
40
+ medium: "text-base",
41
+ large: "text-lg"
42
+ }
43
+
44
+ // Determine if subtitles are available
45
+ const hasSubtitles = !!(subtitleSrc || (subtitleTracks && subtitleTracks.length > 0))
46
+
47
+ // Parse VTT/WebVTT timestamp
48
+ const parseVTTTime = (timeStr: string): number => {
49
+ const parts = timeStr.split(":")
50
+ const seconds = parts[2] ? parseFloat(parts[2]) : 0
51
+ const minutes = parts[1] ? parseInt(parts[1], 10) * 60 : 0
52
+ const hours = parts[0] ? parseInt(parts[0], 10) * 3600 : 0
53
+ return hours + minutes + seconds
54
+ }
55
+
56
+ // Parse SRT timestamp
57
+ const parseSRTTime = (timeStr: string): number => {
58
+ // SRT format: 00:00:00,000 (hours:minutes:seconds,milliseconds)
59
+ const [time, ms] = timeStr.split(",")
60
+ const parts = (time || "").split(":")
61
+ const milliseconds = ms ? parseInt(ms, 10) / 1000 : 0
62
+ const seconds = parts[2] ? parseInt(parts[2], 10) : 0
63
+ const minutes = parts[1] ? parseInt(parts[1], 10) * 60 : 0
64
+ const hours = parts[0] ? parseInt(parts[0], 10) * 3600 : 0
65
+ return hours + minutes + seconds + milliseconds
66
+ }
67
+
68
+ // Load and parse subtitle file
69
+ const loadSubtitles = async (url: string) => {
70
+ setIsLoading(true)
71
+ setLoadError(null)
72
+
73
+ try {
74
+ const response = await fetch(url)
75
+ if (!response.ok) {
76
+ throw new Error(`Failed to load subtitles: ${response.status} ${response.statusText}`)
77
+ }
78
+
79
+ const text = await response.text()
80
+ const cues: SubtitleCue[] = []
81
+ const lines = text.split("\n")
82
+ let i = 0
83
+
84
+ // Detect format (VTT or SRT)
85
+ const isVTT = text.includes("WEBVTT") || url.endsWith(".vtt")
86
+ const isSRT = url.endsWith(".srt") || /^\d+$/.test(lines[0]?.trim() || "")
87
+
88
+ if (isVTT) {
89
+ // Parse VTT format
90
+ while (i < lines.length) {
91
+ if (lines[i]?.includes("-->")) {
92
+ const [startStr, endStr] = (lines[i] || "").split("-->").map((s) => s.trim())
93
+ const start = parseVTTTime(startStr || "")
94
+ const end = parseVTTTime(endStr || "")
95
+
96
+ i++
97
+ let subtitleText = ""
98
+ while (i < lines.length && lines[i]?.trim() !== "") {
99
+ subtitleText += (subtitleText ? " " : "") + lines[i]?.trim()
100
+ i++
101
+ }
102
+
103
+ if (subtitleText) {
104
+ cues.push({ start, end, text: subtitleText })
105
+ }
106
+ }
107
+ i++
108
+ }
109
+ } else if (isSRT) {
110
+ // Parse SRT format
111
+ while (i < lines.length) {
112
+ // Skip the sequence number
113
+ if (/^\d+$/.test(lines[i]?.trim() || "")) {
114
+ i++
115
+ // Parse timestamp line
116
+ if (lines[i]?.includes("-->")) {
117
+ const [startStr, endStr] = (lines[i] || "").split("-->").map((s) => s.trim())
118
+ const start = parseSRTTime(startStr || "")
119
+ const end = parseSRTTime(endStr || "")
120
+
121
+ i++
122
+ let subtitleText = ""
123
+ while (i < lines.length && lines[i]?.trim() !== "") {
124
+ subtitleText += (subtitleText ? " " : "") + lines[i]?.trim()
125
+ i++
126
+ }
127
+
128
+ if (subtitleText) {
129
+ cues.push({ start, end, text: subtitleText })
130
+ }
131
+ }
132
+ }
133
+ i++
134
+ }
135
+ } else {
136
+ throw new Error("Unsupported subtitle format. Only VTT and SRT are supported.")
137
+ }
138
+
139
+ if (cues.length === 0) {
140
+ throw new Error("No subtitle cues found in file")
141
+ }
142
+
143
+ setSubtitleCues(cues)
144
+ setIsLoading(false)
145
+ } catch (error) {
146
+ const err = error instanceof Error ? error : new Error("Failed to load subtitles")
147
+ console.error("Failed to load subtitles:", err)
148
+ setLoadError(err)
149
+ setIsLoading(false)
150
+ setSubtitleCues([])
151
+ onLoadError?.(err)
152
+ }
153
+ }
154
+
155
+ // Get current subtitle text based on time
156
+ const getCurrentSubtitle = (currentTime: number) => {
157
+ if (!subtitlesEnabled || subtitleCues.length === 0) return ""
158
+
159
+ const currentCue = subtitleCues.find(
160
+ (cue) => currentTime >= cue.start && currentTime <= cue.end
161
+ )
162
+ return currentCue?.text || ""
163
+ }
164
+
165
+ // Toggle subtitles on/off
166
+ const toggleSubtitles = () => {
167
+ setSubtitlesEnabled(prev => {
168
+ const newEnabled = !prev
169
+
170
+ if (newEnabled && !loadError) {
171
+ // Try to load subtitles if not already loaded
172
+ if (subtitleCues.length === 0 && !isLoading) {
173
+ // Load default subtitle source or first track
174
+ const srcToLoad = subtitleSrc || subtitleTracks[0]?.src
175
+ if (srcToLoad) {
176
+ loadSubtitles(srcToLoad)
177
+ if (subtitleTracks[0]) {
178
+ setCurrentTrackId(subtitleTracks[0].id)
179
+ }
180
+ }
181
+ }
182
+ } else if (!newEnabled) {
183
+ setCurrentSubtitle("")
184
+ }
185
+
186
+ return newEnabled
187
+ })
188
+ }
189
+
190
+ // Select a specific subtitle track
191
+ const selectSubtitleTrack = (trackId: string | null) => {
192
+ if (!trackId) {
193
+ // Turn off subtitles
194
+ setSubtitlesEnabled(false)
195
+ setCurrentTrackId(null)
196
+ setCurrentSubtitle("")
197
+ return
198
+ }
199
+
200
+ const track = subtitleTracks.find(t => t.id === trackId)
201
+ if (track?.src) {
202
+ setCurrentTrackId(trackId)
203
+ loadSubtitles(track.src)
204
+ setSubtitlesEnabled(true)
205
+ }
206
+ }
207
+
208
+ // Change subtitle font size
209
+ const changeSubtitleFontSize = (size: "small" | "medium" | "large") => {
210
+ setFontSize(size)
211
+ }
212
+
213
+ // Get CSS classes for subtitle display
214
+ const getSubtitleStyles = {
215
+ fontSize: sizeMap[fontSize],
216
+ className: `${sizeMap[fontSize]} font-medium`
217
+ }
218
+
219
+ // Clear subtitle on disable
220
+ useEffect(() => {
221
+ if (!subtitlesEnabled) {
222
+ setCurrentSubtitle("")
223
+ }
224
+ }, [subtitlesEnabled])
225
+
226
+ // Auto-load default subtitles on mount if available
227
+ useEffect(() => {
228
+ if (subtitleSrc && !subtitleCues.length && !loadError && !isLoading) {
229
+ // Don't auto-load, wait for user to enable
230
+ // This prevents errors on initial load
231
+ }
232
+ }, [])
233
+
234
+ return {
235
+ // State
236
+ subtitlesEnabled,
237
+ currentSubtitle,
238
+ subtitleCues,
239
+ currentTrackId,
240
+ fontSize,
241
+ loadError,
242
+ isLoading,
243
+ hasSubtitles,
244
+
245
+ // Actions
246
+ toggleSubtitles,
247
+ selectSubtitleTrack,
248
+ changeSubtitleFontSize,
249
+ setCurrentSubtitle,
250
+
251
+ // Utilities
252
+ getCurrentSubtitle,
253
+ getSubtitleStyles,
254
+
255
+ // Track info
256
+ availableTracks: subtitleTracks
257
+ }
258
+ }
@@ -0,0 +1,74 @@
1
+ import { useEffect, useCallback } from "react"
2
+ import type { VideoControls } from "./useVideoPlayer"
3
+
4
+ interface UseKeyboardShortcutsProps {
5
+ videoControls: VideoControls
6
+ toggleSubtitles: () => void
7
+ takeScreenshot: (ctrlKey?: boolean) => void
8
+ duration: number
9
+ }
10
+
11
+ export function useKeyboardShortcuts({
12
+ videoControls,
13
+ toggleSubtitles,
14
+ takeScreenshot,
15
+ duration
16
+ }: UseKeyboardShortcutsProps) {
17
+ const handleKeyDown = (e: KeyboardEvent) => {
18
+ const video = document.querySelector('video')
19
+ if (!video) return
20
+
21
+ switch (e.key.toLowerCase()) {
22
+ case " ":
23
+ case "k":
24
+ e.preventDefault()
25
+ videoControls.togglePlay()
26
+ break
27
+ case "j":
28
+ e.preventDefault()
29
+ videoControls.seekBackward()
30
+ break
31
+ case "l":
32
+ e.preventDefault()
33
+ videoControls.seekForward()
34
+ break
35
+ case "arrowleft":
36
+ e.preventDefault()
37
+ video.currentTime = Math.max(0, video.currentTime - 5)
38
+ break
39
+ case "arrowright":
40
+ e.preventDefault()
41
+ video.currentTime = Math.min(duration, video.currentTime + 5)
42
+ break
43
+ case "arrowup":
44
+ e.preventDefault()
45
+ video.volume = Math.min(1, video.volume + 0.1)
46
+ break
47
+ case "arrowdown":
48
+ e.preventDefault()
49
+ video.volume = Math.max(0, video.volume - 0.1)
50
+ break
51
+ case "m":
52
+ e.preventDefault()
53
+ videoControls.toggleMute()
54
+ break
55
+ case "f":
56
+ e.preventDefault()
57
+ videoControls.toggleFullscreen()
58
+ break
59
+ case "c":
60
+ e.preventDefault()
61
+ toggleSubtitles()
62
+ break
63
+ case "s":
64
+ e.preventDefault()
65
+ takeScreenshot(e.ctrlKey || e.metaKey)
66
+ break
67
+ }
68
+ }
69
+
70
+ useEffect(() => {
71
+ document.addEventListener("keydown", handleKeyDown)
72
+ return () => document.removeEventListener("keydown", handleKeyDown)
73
+ }, [handleKeyDown])
74
+ }
@@ -0,0 +1,52 @@
1
+ import { useState, useRef, useCallback, useEffect } from "react"
2
+
3
+ export function useMediaControls(isPlaying: boolean, showSettings: boolean) {
4
+ const [controlsVisible, setControlsVisible] = useState(true)
5
+ const [showVolumeSlider, setShowVolumeSlider] = useState(false)
6
+ const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null)
7
+
8
+ const showControls = () => {
9
+ setControlsVisible(true)
10
+ if (hideTimeoutRef.current) {
11
+ clearTimeout(hideTimeoutRef.current)
12
+ }
13
+
14
+ if (isPlaying && !showSettings) {
15
+ hideTimeoutRef.current = setTimeout(() => {
16
+ setControlsVisible(false)
17
+ }, 3000)
18
+ }
19
+ }
20
+
21
+ const handleMouseMove = () => {
22
+ showControls()
23
+ }
24
+
25
+ const handleMouseEnter = () => {
26
+ setControlsVisible(true)
27
+ }
28
+
29
+ const handleMouseLeave = () => {
30
+ if (isPlaying) {
31
+ setControlsVisible(false)
32
+ }
33
+ }
34
+
35
+ useEffect(() => {
36
+ showControls()
37
+ return () => {
38
+ if (hideTimeoutRef.current) {
39
+ clearTimeout(hideTimeoutRef.current)
40
+ }
41
+ }
42
+ }, [showControls])
43
+
44
+ return {
45
+ controlsVisible,
46
+ showVolumeSlider,
47
+ setShowVolumeSlider,
48
+ handleMouseMove,
49
+ handleMouseEnter,
50
+ handleMouseLeave
51
+ }
52
+ }
@@ -0,0 +1,57 @@
1
+ import { useCallback } from "react"
2
+
3
+ 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
+ }
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")
34
+ }
35
+ } else {
36
+ // Download
37
+ const url = URL.createObjectURL(blob)
38
+ const a = document.createElement("a")
39
+ a.href = url
40
+ a.download = `screenshot-${Date.now()}.png`
41
+ a.click()
42
+ URL.revokeObjectURL(url)
43
+ }
44
+ }, "image/png")
45
+ } catch (error) {
46
+ if (error instanceof Error && error.message.includes("tainted")) {
47
+ console.error("Canvas is tainted - video source may not have proper CORS headers. Try setting crossOrigin='anonymous' on the video element.")
48
+ alert("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.")
49
+ } else {
50
+ console.error("Screenshot failed:", error)
51
+ alert("Screenshot failed: " + (error instanceof Error ? error.message : "Unknown error"))
52
+ }
53
+ }
54
+ }
55
+
56
+ return { takeScreenshot }
57
+ }
@@ -0,0 +1,91 @@
1
+ import { useState, useCallback, useMemo, useEffect } from "react"
2
+
3
+ interface SubtitleCue {
4
+ start: number
5
+ end: number
6
+ text: string
7
+ }
8
+
9
+ export function useSubtitles(subtitleSrc?: string) {
10
+ const [subtitlesEnabled, setSubtitlesEnabled] = useState(false)
11
+ const [subtitleCues, setSubtitleCues] = useState<SubtitleCue[]>([])
12
+ const [currentSubtitle, setCurrentSubtitle] = useState("")
13
+
14
+ const parseVTTTime = (timeStr: string): number => {
15
+ const parts = timeStr.split(":")
16
+ const seconds = parts[2] ? parseFloat(parts[2]) : 0
17
+ const minutes = parts[1] ? parseInt(parts[1], 10) * 60 : 0
18
+ const hours = parts[0] ? parseInt(parts[0], 10) * 3600 : 0
19
+ return hours + minutes + seconds
20
+ }
21
+
22
+ const loadSubtitles = async (url: string) => {
23
+ try {
24
+ const response = await fetch(url)
25
+ const text = await response.text()
26
+
27
+ const cues: SubtitleCue[] = []
28
+ const lines = text.split("\n")
29
+ let i = 0
30
+
31
+ while (i < lines.length) {
32
+ if (lines[i]?.includes("-->")) {
33
+ const [startStr, endStr] = (lines[i] || "").split("-->").map((s) => s.trim())
34
+ const start = parseVTTTime(startStr || "")
35
+ const end = parseVTTTime(endStr || "")
36
+
37
+ i++
38
+ let subtitleText = ""
39
+ while (i < lines.length && lines[i]?.trim() !== "") {
40
+ subtitleText += (subtitleText ? " " : "") + lines[i]?.trim()
41
+ i++
42
+ }
43
+
44
+ if (subtitleText) {
45
+ cues.push({ start, end, text: subtitleText })
46
+ }
47
+ }
48
+ i++
49
+ }
50
+
51
+ setSubtitleCues(cues)
52
+ } catch (error) {
53
+ console.error("Failed to load subtitles:", error)
54
+ }
55
+ }
56
+
57
+ const getCurrentSubtitle = (currentTime: number) => {
58
+ if (!subtitlesEnabled || subtitleCues.length === 0) return ""
59
+
60
+ const currentCue = subtitleCues.find(
61
+ (cue) => currentTime >= cue.start && currentTime <= cue.end
62
+ )
63
+ return currentCue?.text || ""
64
+ }
65
+
66
+ const toggleSubtitles = () => {
67
+ setSubtitlesEnabled(prev => {
68
+ const newEnabled = !prev
69
+ if (newEnabled && subtitleSrc && subtitleCues.length === 0) {
70
+ loadSubtitles(subtitleSrc)
71
+ } else if (!newEnabled) {
72
+ setCurrentSubtitle("")
73
+ }
74
+ return newEnabled
75
+ })
76
+ }
77
+
78
+ useEffect(() => {
79
+ if (!subtitlesEnabled) {
80
+ setCurrentSubtitle("")
81
+ }
82
+ }, [subtitlesEnabled])
83
+
84
+ return {
85
+ subtitlesEnabled,
86
+ currentSubtitle,
87
+ toggleSubtitles,
88
+ getCurrentSubtitle,
89
+ setCurrentSubtitle
90
+ }
91
+ }