@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,361 @@
1
+ import React, { memo, useState, useCallback, useEffect } from "react"
2
+ import { cn } from "../../../../lib/utils"
3
+ import { defaultMinimalIcons } from "../../../icons"
4
+
5
+ export interface SubtitleTrack {
6
+ id: string
7
+ label: string
8
+ language?: string
9
+ src?: string
10
+ }
11
+
12
+ interface EnhancedSettingsMenuProps {
13
+ showSettings: boolean
14
+ playbackRate: number
15
+ subtitlesEnabled: boolean
16
+ subtitleTracks: SubtitleTrack[]
17
+ currentSubtitleTrack?: string
18
+ subtitleFontSize: "small" | "medium" | "large"
19
+ hasSubtitles: boolean
20
+ subtitleLoadError?: boolean
21
+ onToggleSettings: () => void
22
+ onChangePlaybackRate: (rate: number) => void
23
+ onToggleSubtitles: () => void
24
+ onSelectSubtitleTrack?: (trackId: string | null) => void
25
+ onChangeSubtitleFontSize: (size: "small" | "medium" | "large") => void
26
+ }
27
+
28
+ type MenuView = "main" | "subtitles" | "fontSize" | "speed"
29
+
30
+ export const EnhancedSettingsMenu = function EnhancedSettingsMenu({
31
+ showSettings,
32
+ playbackRate,
33
+ subtitlesEnabled,
34
+ subtitleTracks,
35
+ currentSubtitleTrack,
36
+ subtitleFontSize = "medium",
37
+ hasSubtitles,
38
+ subtitleLoadError,
39
+ onToggleSettings,
40
+ onChangePlaybackRate,
41
+ onToggleSubtitles,
42
+ onSelectSubtitleTrack,
43
+ onChangeSubtitleFontSize
44
+ }: EnhancedSettingsMenuProps) {
45
+ const [currentView, setCurrentView] = useState<MenuView>("main")
46
+ const playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 2]
47
+ const fontSizes = [
48
+ { value: "small", label: "Small", size: "12px" },
49
+ { value: "medium", label: "Medium", size: "16px" },
50
+ { value: "large", label: "Large", size: "20px" }
51
+ ]
52
+
53
+ // Reset to main menu when settings close
54
+ useEffect(() => {
55
+ if (!showSettings) {
56
+ setCurrentView("main")
57
+ }
58
+ }, [showSettings])
59
+
60
+ const handleSettingsClick = useCallback((e: React.MouseEvent) => {
61
+ e.stopPropagation()
62
+ onToggleSettings()
63
+ }, [onToggleSettings])
64
+
65
+ const handleDropdownClick = useCallback((e: React.MouseEvent) => {
66
+ e.stopPropagation()
67
+ }, [])
68
+
69
+ const handleBackClick = useCallback(() => {
70
+ setCurrentView("main")
71
+ }, [])
72
+
73
+ const handleSelectSpeed = useCallback((rate: number) => {
74
+ onChangePlaybackRate(rate)
75
+ setCurrentView("main")
76
+ }, [onChangePlaybackRate])
77
+
78
+ const handleSelectFontSize = useCallback((size: "small" | "medium" | "large") => {
79
+ onChangeSubtitleFontSize(size)
80
+ setCurrentView("main")
81
+ }, [onChangeSubtitleFontSize])
82
+
83
+ const handleSelectSubtitle = useCallback((trackId: string | null) => {
84
+ if (trackId === null) {
85
+ // Turn off subtitles
86
+ if (subtitlesEnabled) {
87
+ onToggleSubtitles()
88
+ }
89
+ } else {
90
+ // Select track and enable if needed
91
+ if (!subtitlesEnabled) {
92
+ onToggleSubtitles()
93
+ }
94
+ onSelectSubtitleTrack?.(trackId)
95
+ }
96
+ setCurrentView("main")
97
+ }, [subtitlesEnabled, onToggleSubtitles, onSelectSubtitleTrack])
98
+
99
+ const renderMainMenu = () => (
100
+ <>
101
+ <div className="border-gray-700 border-b px-3 py-2 font-semibold text-gray-400 text-xs uppercase tracking-wider">
102
+ Settings
103
+ </div>
104
+ <div className="py-1">
105
+ {/* Subtitles Option */}
106
+ <button
107
+ className={cn(
108
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
109
+ !hasSubtitles && "cursor-not-allowed opacity-50"
110
+ )}
111
+ onClick={() => hasSubtitles && setCurrentView("subtitles")}
112
+ disabled={!hasSubtitles}
113
+ >
114
+ <div className="flex items-center gap-2">
115
+ {defaultMinimalIcons.subtitle && <defaultMinimalIcons.subtitle size={16} />}
116
+ <span>Subtitles</span>
117
+ </div>
118
+ <div className="flex items-center gap-1 text-gray-400 text-xs">
119
+ {!hasSubtitles ? (
120
+ <span className="text-gray-500">Unavailable</span>
121
+ ) : subtitleLoadError ? (
122
+ <span className="text-red-400">Error</span>
123
+ ) : subtitlesEnabled ? (
124
+ <span className="text-cyan-400">On</span>
125
+ ) : (
126
+ <span>Off</span>
127
+ )}
128
+ {hasSubtitles && (
129
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
130
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
131
+ </svg>
132
+ )}
133
+ </div>
134
+ </button>
135
+
136
+ {/* Font Size Option - only show if subtitles are available */}
137
+ {hasSubtitles && (
138
+ <button
139
+ className="flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800"
140
+ onClick={() => setCurrentView("fontSize")}
141
+ >
142
+ <div className="flex items-center gap-2">
143
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
144
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
145
+ d="M4 7h16M4 7v10m0-10l4 10m8-10l4 10m-4-10v10" />
146
+ </svg>
147
+ <span>Font Size</span>
148
+ </div>
149
+ <div className="flex items-center gap-1 text-gray-400 text-xs">
150
+ <span className="capitalize">{subtitleFontSize}</span>
151
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
152
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
153
+ </svg>
154
+ </div>
155
+ </button>
156
+ )}
157
+
158
+ {/* Playback Speed Option */}
159
+ <button
160
+ className="flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800"
161
+ onClick={() => setCurrentView("speed")}
162
+ >
163
+ <div className="flex items-center gap-2">
164
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
165
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
166
+ d="M13 10V3L4 14h7v7l9-11h-7z" />
167
+ </svg>
168
+ <span>Playback Speed</span>
169
+ </div>
170
+ <div className="flex items-center gap-1 text-gray-400 text-xs">
171
+ <span>{playbackRate === 1 ? "Normal" : `${playbackRate}x`}</span>
172
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
174
+ </svg>
175
+ </div>
176
+ </button>
177
+ </div>
178
+ </>
179
+ )
180
+
181
+ const renderSubtitlesMenu = () => (
182
+ <>
183
+ <div className="flex items-center justify-between border-gray-700 border-b px-3 py-2">
184
+ <button
185
+ onClick={handleBackClick}
186
+ className="text-gray-400 transition-colors hover:text-white"
187
+ >
188
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
189
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
190
+ </svg>
191
+ </button>
192
+ <span className="font-semibold text-gray-300 text-sm">Subtitles</span>
193
+ <div className="w-4" />
194
+ </div>
195
+ <div className="py-1">
196
+ {/* Off option */}
197
+ <button
198
+ className={cn(
199
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
200
+ !subtitlesEnabled && "bg-cyan-700/20 text-cyan-400"
201
+ )}
202
+ onClick={() => handleSelectSubtitle(null)}
203
+ >
204
+ <span>Off</span>
205
+ {!subtitlesEnabled && (
206
+ <svg className="h-4 w-4 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
207
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
208
+ </svg>
209
+ )}
210
+ </button>
211
+
212
+ {/* Subtitle tracks */}
213
+ {subtitleTracks.length > 0 ? (
214
+ subtitleTracks.map((track) => (
215
+ <button
216
+ key={track.id}
217
+ className={cn(
218
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
219
+ subtitlesEnabled && currentSubtitleTrack === track.id && "bg-cyan-700/20 text-cyan-400"
220
+ )}
221
+ onClick={() => handleSelectSubtitle(track.id)}
222
+ >
223
+ <span>{track.label}</span>
224
+ {subtitlesEnabled && currentSubtitleTrack === track.id && (
225
+ <svg className="h-4 w-4 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
226
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
227
+ </svg>
228
+ )}
229
+ </button>
230
+ ))
231
+ ) : (
232
+ <button
233
+ className={cn(
234
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
235
+ subtitlesEnabled && "bg-cyan-700/20 text-cyan-400"
236
+ )}
237
+ onClick={() => {
238
+ if (!subtitlesEnabled) {
239
+ onToggleSubtitles()
240
+ }
241
+ setCurrentView("main")
242
+ }}
243
+ >
244
+ <span>Default</span>
245
+ {subtitlesEnabled && (
246
+ <svg className="h-4 w-4 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
247
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
248
+ </svg>
249
+ )}
250
+ </button>
251
+ )}
252
+ </div>
253
+ </>
254
+ )
255
+
256
+ const renderFontSizeMenu = () => (
257
+ <>
258
+ <div className="flex items-center justify-between border-gray-700 border-b px-3 py-2">
259
+ <button
260
+ onClick={handleBackClick}
261
+ className="text-gray-400 transition-colors hover:text-white"
262
+ >
263
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
264
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
265
+ </svg>
266
+ </button>
267
+ <span className="font-semibold text-gray-300 text-sm">Font Size</span>
268
+ <div className="w-4" />
269
+ </div>
270
+ <div className="py-1">
271
+ {fontSizes.map((size) => (
272
+ <button
273
+ key={size.value}
274
+ className={cn(
275
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
276
+ subtitleFontSize === size.value && "bg-cyan-700/20 text-cyan-400"
277
+ )}
278
+ onClick={() => handleSelectFontSize(size.value as "small" | "medium" | "large")}
279
+ >
280
+ <div className="flex items-center gap-3">
281
+ <span className="text-gray-400" style={{ fontSize: size.size }}>Aa</span>
282
+ <span>{size.label}</span>
283
+ </div>
284
+ {subtitleFontSize === size.value && (
285
+ <svg className="h-4 w-4 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
286
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
287
+ </svg>
288
+ )}
289
+ </button>
290
+ ))}
291
+ </div>
292
+ </>
293
+ )
294
+
295
+ const renderSpeedMenu = () => (
296
+ <>
297
+ <div className="flex items-center justify-between border-gray-700 border-b px-3 py-2">
298
+ <button
299
+ onClick={handleBackClick}
300
+ className="text-gray-400 transition-colors hover:text-white"
301
+ >
302
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
303
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
304
+ </svg>
305
+ </button>
306
+ <span className="font-semibold text-gray-300 text-sm">Playback Speed</span>
307
+ <div className="w-4" />
308
+ </div>
309
+ <div className="py-1">
310
+ {playbackRates.map((rate) => (
311
+ <button
312
+ key={rate}
313
+ className={cn(
314
+ "flex w-full items-center justify-between px-3 py-2 text-left text-sm transition-colors hover:bg-gray-800",
315
+ playbackRate === rate && "bg-cyan-700/20 text-cyan-400"
316
+ )}
317
+ onClick={() => handleSelectSpeed(rate)}
318
+ >
319
+ <span>{rate === 1 ? "Normal" : `${rate}x`}</span>
320
+ {playbackRate === rate && (
321
+ <svg className="h-4 w-4 text-cyan-400" fill="currentColor" viewBox="0 0 20 20">
322
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" />
323
+ </svg>
324
+ )}
325
+ </button>
326
+ ))}
327
+ </div>
328
+ </>
329
+ )
330
+
331
+ return (
332
+ <div className="relative" style={{ zIndex: showSettings ? 60 : "auto" }}>
333
+ <button
334
+ className={cn("minimal-btn h-9 w-9", showSettings && "text-cyan-500")}
335
+ onClick={handleSettingsClick}
336
+ aria-label="Settings"
337
+ title="Settings"
338
+ >
339
+ {defaultMinimalIcons.settings && <defaultMinimalIcons.settings size={18} />}
340
+ </button>
341
+
342
+ {showSettings && (
343
+ <div
344
+ className="absolute right-0 mb-2 min-w-[240px] overflow-hidden rounded-lg border border-gray-700 bg-gray-900 shadow-xl"
345
+ style={{
346
+ bottom: "100%",
347
+ zIndex: 1000,
348
+ maxHeight: "400px",
349
+ overflowY: "auto"
350
+ }}
351
+ onClick={handleDropdownClick}
352
+ >
353
+ {currentView === "main" && renderMainMenu()}
354
+ {currentView === "subtitles" && renderSubtitlesMenu()}
355
+ {currentView === "fontSize" && renderFontSizeMenu()}
356
+ {currentView === "speed" && renderSpeedMenu()}
357
+ </div>
358
+ )}
359
+ </div>
360
+ )
361
+ }
@@ -0,0 +1,51 @@
1
+ import React, { memo } from "react"
2
+ import { defaultMinimalIcons, MinimalBackward10Icon, MinimalForward10Icon } from "../../../icons"
3
+
4
+ interface PlaybackControlsProps {
5
+ isPlaying: boolean
6
+ onTogglePlay: () => void
7
+ onSeekBackward: () => void
8
+ onSeekForward: () => void
9
+ }
10
+
11
+ export const PlaybackControls = function PlaybackControls({
12
+ isPlaying,
13
+ onTogglePlay,
14
+ onSeekBackward,
15
+ onSeekForward
16
+ }: PlaybackControlsProps) {
17
+ const PlayIcon = defaultMinimalIcons.play
18
+ const PauseIcon = defaultMinimalIcons.pause
19
+
20
+ return (
21
+ <div className="flex items-center gap-2">
22
+ <button
23
+ className="minimal-btn size-15"
24
+ onClick={onTogglePlay}
25
+ aria-label={isPlaying ? "Pause" : "Play"}
26
+ >
27
+ {isPlaying
28
+ ? PauseIcon && <PauseIcon size={32} />
29
+ : PlayIcon && <PlayIcon size={32} />}
30
+ </button>
31
+
32
+ <button
33
+ className="minimal-btn h-9 w-9"
34
+ onClick={onSeekBackward}
35
+ aria-label="Backward 10 seconds"
36
+ title="Backward 10s (J)"
37
+ >
38
+ <MinimalBackward10Icon size={18} />
39
+ </button>
40
+
41
+ <button
42
+ className="minimal-btn h-9 w-9"
43
+ onClick={onSeekForward}
44
+ aria-label="Forward 10 seconds"
45
+ title="Forward 10s (L)"
46
+ >
47
+ <MinimalForward10Icon size={18} />
48
+ </button>
49
+ </div>
50
+ )
51
+ }
@@ -0,0 +1,173 @@
1
+ import React, { memo, useState, useRef, useEffect } from "react"
2
+ import { cn } from "../../../../lib/utils"
3
+ import {
4
+ MinimalSubtitleIcon,
5
+ MinimalScreenshotIcon,
6
+ defaultMinimalIcons
7
+ } from "../../../icons"
8
+ import { SettingsDropdown } from "./SettingsDropdown"
9
+
10
+ interface RightControlsProps {
11
+ subtitleSrc?: string
12
+ subtitlesEnabled: boolean
13
+ subtitleTracks?: Array<{
14
+ id: string
15
+ label: string
16
+ language?: string
17
+ src?: string
18
+ }>
19
+ currentTrackId?: string | null
20
+ hasSubtitles?: boolean
21
+ subtitleLoadError?: Error | null
22
+ isFullscreen: boolean
23
+ showSettings: boolean
24
+ playbackRate: number
25
+ onToggleSubtitles: () => void
26
+ onSelectSubtitleTrack?: (trackId: string | null) => void
27
+ onTakeScreenshot: (ctrlKey: boolean) => void
28
+ onToggleFullscreen: () => void
29
+ onToggleSettings: () => void
30
+ onChangePlaybackRate: (rate: number) => void
31
+ }
32
+
33
+ export const RightControls = function RightControls({
34
+ subtitleSrc,
35
+ subtitlesEnabled,
36
+ subtitleTracks,
37
+ currentTrackId,
38
+ hasSubtitles,
39
+ subtitleLoadError,
40
+ isFullscreen,
41
+ showSettings,
42
+ playbackRate,
43
+ onToggleSubtitles,
44
+ onSelectSubtitleTrack,
45
+ onTakeScreenshot,
46
+ onToggleFullscreen,
47
+ onToggleSettings,
48
+ onChangePlaybackRate
49
+ }: RightControlsProps) {
50
+ const [showSubtitleMenu, setShowSubtitleMenu] = useState(false)
51
+ const subtitleMenuRef = useRef<HTMLDivElement>(null)
52
+ const FullscreenIcon = defaultMinimalIcons.fullscreen
53
+ const ExitFullscreenIcon = defaultMinimalIcons.exitFullscreen
54
+
55
+ // Close subtitle menu when clicking outside
56
+ useEffect(() => {
57
+ const handleClickOutside = (event: MouseEvent) => {
58
+ if (subtitleMenuRef.current && !subtitleMenuRef.current.contains(event.target as Node)) {
59
+ setShowSubtitleMenu(false)
60
+ }
61
+ }
62
+
63
+ if (showSubtitleMenu) {
64
+ document.addEventListener("mousedown", handleClickOutside)
65
+ return () => document.removeEventListener("mousedown", handleClickOutside)
66
+ }
67
+ }, [showSubtitleMenu])
68
+
69
+ const handleSubtitleClick = () => {
70
+ if (subtitleTracks && subtitleTracks.length > 0) {
71
+ setShowSubtitleMenu(!showSubtitleMenu)
72
+ } else {
73
+ onToggleSubtitles()
74
+ }
75
+ }
76
+
77
+ return (
78
+ <div className="flex items-center gap-2">
79
+ {/* Subtitle Button with Dropdown */}
80
+ {hasSubtitles && (
81
+ <div className="relative" ref={subtitleMenuRef}>
82
+ <button
83
+ className={cn(
84
+ "minimal-btn h-9 w-9",
85
+ subtitlesEnabled && "text-cyan-500",
86
+ subtitleLoadError && "text-red-500"
87
+ )}
88
+ onClick={handleSubtitleClick}
89
+ aria-label="Subtitles"
90
+ title={subtitleLoadError ? "Subtitle Error" : "Subtitles (C)"}
91
+ >
92
+ <MinimalSubtitleIcon size={18} />
93
+ </button>
94
+
95
+ {/* Subtitle Track Selection Menu */}
96
+ {showSubtitleMenu && subtitleTracks && subtitleTracks.length > 0 && (
97
+ <div className="absolute right-0 bottom-full mb-2 w-48 rounded-md bg-gray-900/95 py-2 shadow-lg backdrop-blur-sm">
98
+ <div className="px-3 py-1 font-semibold text-gray-400 text-xs">Subtitles</div>
99
+
100
+ {/* Off Option */}
101
+ <button
102
+ className={cn(
103
+ "flex w-full items-center px-3 py-1.5 text-gray-300 text-sm hover:bg-gray-800",
104
+ !subtitlesEnabled && "bg-gray-800 text-cyan-500"
105
+ )}
106
+ onClick={() => {
107
+ onSelectSubtitleTrack?.(null)
108
+ setShowSubtitleMenu(false)
109
+ }}
110
+ >
111
+ Off
112
+ </button>
113
+
114
+ {/* Track Options */}
115
+ {subtitleTracks.map((track) => (
116
+ <button
117
+ key={track.id}
118
+ className={cn(
119
+ "flex w-full items-center px-3 py-1.5 text-gray-300 text-sm hover:bg-gray-800",
120
+ currentTrackId === track.id && "bg-gray-800 text-cyan-500"
121
+ )}
122
+ onClick={() => {
123
+ onSelectSubtitleTrack?.(track.id)
124
+ setShowSubtitleMenu(false)
125
+ }}
126
+ >
127
+ <span>{track.label}</span>
128
+ {track.language && (
129
+ <span className="ml-2 text-gray-500 text-xs">({track.language})</span>
130
+ )}
131
+ </button>
132
+ ))}
133
+
134
+ {/* Error Message */}
135
+ {subtitleLoadError && (
136
+ <div className="mt-2 border-gray-800 border-t px-3 py-2">
137
+ <p className="text-red-400 text-xs">Failed to load subtitles</p>
138
+ </div>
139
+ )}
140
+ </div>
141
+ )}
142
+ </div>
143
+ )}
144
+
145
+ <button
146
+ className="minimal-btn h-9 w-9"
147
+ onClick={(e) => onTakeScreenshot(e.ctrlKey || e.metaKey)}
148
+ aria-label="Take Screenshot"
149
+ title="Screenshot (S) - Ctrl+Click to copy"
150
+ >
151
+ <MinimalScreenshotIcon size={18} />
152
+ </button>
153
+
154
+ <SettingsDropdown
155
+ showSettings={showSettings}
156
+ playbackRate={playbackRate}
157
+ onToggleSettings={onToggleSettings}
158
+ onChangePlaybackRate={onChangePlaybackRate}
159
+ />
160
+
161
+ <button
162
+ className="minimal-btn h-9 w-9"
163
+ onClick={onToggleFullscreen}
164
+ aria-label={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
165
+ title="Fullscreen (F)"
166
+ >
167
+ {isFullscreen
168
+ ? ExitFullscreenIcon && <ExitFullscreenIcon size={18} />
169
+ : FullscreenIcon && <FullscreenIcon size={18} />}
170
+ </button>
171
+ </div>
172
+ )
173
+ }