@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,600 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { useMediaSelector, useMediaDispatch, MediaActionTypes } from "../../context/media-context"
5
+ import { cn } from "../../lib/utils"
6
+ import { formatTime } from "../../lib/format-time"
7
+
8
+ // Enhanced play button with better animations
9
+ export const EnhancedPlayButton = function EnhancedPlayButton({
10
+ className,
11
+ size = "md"
12
+ }: {
13
+ className?: string
14
+ size?: "sm" | "md" | "lg" | "xl"
15
+ }) {
16
+ const dispatch = useMediaDispatch()
17
+ const isPlaying = useMediaSelector(state => state.isPlaying)
18
+ const isLoading = useMediaSelector(state => state.isLoading)
19
+
20
+ const sizeClasses = {
21
+ sm: "h-8 w-8",
22
+ md: "h-10 w-10",
23
+ lg: "h-12 w-12",
24
+ xl: "h-16 w-16"
25
+ }
26
+
27
+ const iconSizes = {
28
+ sm: "h-4 w-4",
29
+ md: "h-5 w-5",
30
+ lg: "h-6 w-6",
31
+ xl: "h-8 w-8"
32
+ }
33
+
34
+ const handleClick = React.useCallback(() => {
35
+ dispatch({
36
+ type: isPlaying ? MediaActionTypes.PAUSE : MediaActionTypes.PLAY
37
+ })
38
+ }, [dispatch, isPlaying])
39
+
40
+ return (
41
+ <button
42
+ type="button"
43
+ onClick={handleClick}
44
+ disabled={isLoading}
45
+ className={cn(
46
+ "group relative flex items-center justify-center rounded-full bg-white/10 backdrop-blur-sm",
47
+ "border border-white/20 text-white transition-all duration-200",
48
+ "hover:scale-105 hover:bg-white/20 active:scale-95",
49
+ "focus:outline-none focus:ring-2 focus:ring-white/50",
50
+ "disabled:cursor-not-allowed disabled:opacity-50",
51
+ sizeClasses[size],
52
+ className
53
+ )}
54
+ aria-label={isPlaying ? "Pause" : "Play"}
55
+ >
56
+ {isLoading ? (
57
+ <div className={cn("animate-spin rounded-full border-2 border-white/30 border-t-white", iconSizes[size])} />
58
+ ) : (
59
+ <div className={cn("transition-transform duration-200", iconSizes[size])}>
60
+ {isPlaying ? (
61
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-full w-full">
62
+ <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
63
+ </svg>
64
+ ) : (
65
+ <svg viewBox="0 0 24 24" fill="currentColor" className="ml-0.5 h-full w-full">
66
+ <path d="M8 5v14l11-7z" />
67
+ </svg>
68
+ )}
69
+ </div>
70
+ )}
71
+ </button>
72
+ )
73
+ }
74
+
75
+ // Enhanced seek bar with preview and better touch support
76
+ export const EnhancedSeekBar = function EnhancedSeekBar({
77
+ className
78
+ }: {
79
+ className?: string
80
+ }) {
81
+ const dispatch = useMediaDispatch()
82
+ const currentTime = useMediaSelector(state => state.currentTime)
83
+ const duration = useMediaSelector(state => state.duration)
84
+ const buffered = useMediaSelector(state => state.buffered)
85
+ const previewTime = useMediaSelector(state => state.previewTime)
86
+
87
+ const [isDragging, setIsDragging] = React.useState(false)
88
+ const [hoverTime, setHoverTime] = React.useState<number | null>(null)
89
+ const progressRef = React.useRef<HTMLDivElement>(null)
90
+
91
+ const progress = duration > 0 ? (currentTime / duration) * 100 : 0
92
+ const previewProgress = duration > 0 && previewTime !== null ? (previewTime / duration) * 100 : null
93
+
94
+ // Calculate buffered ranges
95
+ const bufferedRanges = React.useMemo(() => {
96
+ if (!buffered || duration === 0) return []
97
+ const ranges = []
98
+ for (let i = 0; i < buffered.length; i++) {
99
+ ranges.push({
100
+ start: (buffered.start(i) / duration) * 100,
101
+ end: (buffered.end(i) / duration) * 100
102
+ })
103
+ }
104
+ return ranges
105
+ }, [buffered, duration])
106
+
107
+ const getTimeFromPosition = React.useCallback((clientX: number) => {
108
+ if (!progressRef.current) return 0
109
+ const rect = progressRef.current.getBoundingClientRect()
110
+ const position = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
111
+ return position * duration
112
+ }, [duration])
113
+
114
+ const handleMouseMove = React.useCallback((e: React.MouseEvent) => {
115
+ if (!isDragging) {
116
+ const time = getTimeFromPosition(e.clientX)
117
+ setHoverTime(time)
118
+ dispatch({ type: MediaActionTypes.PREVIEW_TIME, detail: time })
119
+ }
120
+ }, [isDragging, getTimeFromPosition, dispatch])
121
+
122
+ const handleMouseLeave = React.useCallback(() => {
123
+ if (!isDragging) {
124
+ setHoverTime(null)
125
+ dispatch({ type: MediaActionTypes.CLEAR_PREVIEW })
126
+ }
127
+ }, [isDragging, dispatch])
128
+
129
+ const handleMouseDown = React.useCallback((e: React.MouseEvent) => {
130
+ setIsDragging(true)
131
+ const time = getTimeFromPosition(e.clientX)
132
+ dispatch({ type: MediaActionTypes.SEEK, detail: time })
133
+ }, [getTimeFromPosition, dispatch])
134
+
135
+ const handleTouchStart = React.useCallback((e: React.TouchEvent) => {
136
+ setIsDragging(true)
137
+ const time = getTimeFromPosition(e.touches[0]?.clientX || 0)
138
+ dispatch({ type: MediaActionTypes.SEEK, detail: time })
139
+ }, [getTimeFromPosition, dispatch])
140
+
141
+ React.useEffect(() => {
142
+ const handleMouseMove = (e: MouseEvent) => {
143
+ if (isDragging) {
144
+ const time = getTimeFromPosition(e.clientX)
145
+ dispatch({ type: MediaActionTypes.SEEK, detail: time })
146
+ }
147
+ }
148
+
149
+ const handleMouseUp = () => {
150
+ setIsDragging(false)
151
+ setHoverTime(null)
152
+ dispatch({ type: MediaActionTypes.CLEAR_PREVIEW })
153
+ }
154
+
155
+ const handleTouchMove = (e: TouchEvent) => {
156
+ if (isDragging) {
157
+ const time = getTimeFromPosition(e.touches[0]?.clientX || 0)
158
+ dispatch({ type: MediaActionTypes.SEEK, detail: time })
159
+ }
160
+ }
161
+
162
+ if (isDragging) {
163
+ document.addEventListener('mousemove', handleMouseMove)
164
+ document.addEventListener('mouseup', handleMouseUp)
165
+ document.addEventListener('touchmove', handleTouchMove)
166
+ document.addEventListener('touchend', handleMouseUp)
167
+ }
168
+
169
+ return () => {
170
+ document.removeEventListener('mousemove', handleMouseMove)
171
+ document.removeEventListener('mouseup', handleMouseUp)
172
+ document.removeEventListener('touchmove', handleTouchMove)
173
+ document.removeEventListener('touchend', handleMouseUp)
174
+ }
175
+ }, [isDragging, getTimeFromPosition, dispatch])
176
+
177
+ return (
178
+ <div className={cn("group relative flex items-center", className)}>
179
+ {/* Time preview tooltip */}
180
+ {(hoverTime !== null || previewTime !== null) && (
181
+ <div
182
+ className="pointer-events-none absolute bottom-full mb-2 rounded bg-black/80 px-2 py-1 text-white text-xs"
183
+ style={{
184
+ left: `${previewProgress || ((hoverTime || 0) / duration) * 100}%`,
185
+ transform: 'translateX(-50%)'
186
+ }}
187
+ >
188
+ {formatTime(hoverTime || previewTime || 0)}
189
+ </div>
190
+ )}
191
+
192
+ <div
193
+ ref={progressRef}
194
+ className="relative h-2 w-full cursor-pointer rounded-full bg-white/20 transition-all duration-200 group-hover:h-3"
195
+ onMouseMove={handleMouseMove}
196
+ onMouseLeave={handleMouseLeave}
197
+ onMouseDown={handleMouseDown}
198
+ onTouchStart={handleTouchStart}
199
+ >
200
+ {/* Buffered ranges */}
201
+ {bufferedRanges.map((range, index) => (
202
+ <div
203
+ key={index}
204
+ className="absolute top-0 h-full rounded-full bg-white/30"
205
+ style={{
206
+ left: `${range.start}%`,
207
+ width: `${range.end - range.start}%`
208
+ }}
209
+ />
210
+ ))}
211
+
212
+ {/* Progress bar */}
213
+ <div
214
+ className="absolute top-0 h-full rounded-full bg-primary transition-all duration-100"
215
+ style={{ width: `${progress}%` }}
216
+ />
217
+
218
+ {/* Preview indicator */}
219
+ {previewProgress !== null && (
220
+ <div
221
+ className="absolute top-0 h-full w-1 rounded-full bg-white/80"
222
+ style={{ left: `${previewProgress}%` }}
223
+ />
224
+ )}
225
+
226
+ {/* Thumb */}
227
+ <div
228
+ className="-translate-y-1/2 absolute top-1/2 h-4 w-4 rounded-full border-2 border-white bg-primary opacity-0 shadow-lg transition-all duration-200 group-hover:opacity-100"
229
+ style={{ left: `${progress}%`, transform: 'translateX(-50%) translateY(-50%)' }}
230
+ />
231
+ </div>
232
+ </div>
233
+ )
234
+ }
235
+
236
+ // Enhanced volume control with better UX
237
+ export const EnhancedVolumeControl = function EnhancedVolumeControl({
238
+ className
239
+ }: {
240
+ className?: string
241
+ }) {
242
+ const dispatch = useMediaDispatch()
243
+ const volume = useMediaSelector(state => state.volume)
244
+ const isMuted = useMediaSelector(state => state.isMuted)
245
+
246
+ const [showSlider, setShowSlider] = React.useState(false)
247
+
248
+ const handleToggleMute = React.useCallback(() => {
249
+ dispatch({ type: MediaActionTypes.TOGGLE_MUTE })
250
+ }, [dispatch])
251
+
252
+ const handleVolumeChange = React.useCallback((newVolume: number) => {
253
+ dispatch({ type: MediaActionTypes.SET_VOLUME, detail: newVolume })
254
+ }, [dispatch])
255
+
256
+ const VolumeIcon = React.useMemo(() => {
257
+ if (isMuted || volume === 0) {
258
+ return (
259
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5">
260
+ <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
261
+ </svg>
262
+ )
263
+ } else if (volume < 0.5) {
264
+ return (
265
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5">
266
+ <path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>
267
+ </svg>
268
+ )
269
+ } else {
270
+ return (
271
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-5 w-5">
272
+ <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
273
+ </svg>
274
+ )
275
+ }
276
+ }, [isMuted, volume])
277
+
278
+ return (
279
+ <div
280
+ className={cn("relative flex items-center", className)}
281
+ onMouseEnter={() => setShowSlider(true)}
282
+ onMouseLeave={() => setShowSlider(false)}
283
+ >
284
+ <button
285
+ type="button"
286
+ onClick={handleToggleMute}
287
+ className="flex h-10 w-10 items-center justify-center rounded-full text-white transition-colors hover:bg-white/10"
288
+ aria-label={isMuted ? "Unmute" : "Mute"}
289
+ >
290
+ {VolumeIcon}
291
+ </button>
292
+
293
+ {/* Volume slider */}
294
+ <div className={cn(
295
+ "absolute left-full ml-2 flex items-center transition-all duration-200",
296
+ showSlider ? "translate-x-0 opacity-100" : "-translate-x-2 pointer-events-none opacity-0"
297
+ )}>
298
+ <input
299
+ type="range"
300
+ min="0"
301
+ max="1"
302
+ step="0.01"
303
+ value={isMuted ? 0 : volume}
304
+ onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
305
+ className="slider h-1 w-20 cursor-pointer appearance-none rounded-lg bg-white/20"
306
+ />
307
+ </div>
308
+ </div>
309
+ )
310
+ }
311
+
312
+ // Enhanced backward skip button
313
+ export const EnhancedBackwardButton = function EnhancedBackwardButton({
314
+ className,
315
+ size = "md",
316
+ skipTime = 10
317
+ }: {
318
+ className?: string
319
+ size?: "sm" | "md" | "lg"
320
+ skipTime?: number
321
+ }) {
322
+ const dispatch = useMediaDispatch()
323
+ const currentTime = useMediaSelector(state => state.currentTime)
324
+
325
+ const sizeClasses = {
326
+ sm: "h-8 w-8",
327
+ md: "h-10 w-10",
328
+ lg: "h-12 w-12"
329
+ }
330
+
331
+ const iconSizes = {
332
+ sm: "h-4 w-4",
333
+ md: "h-5 w-5",
334
+ lg: "h-6 w-6"
335
+ }
336
+
337
+ const handleClick = React.useCallback(() => {
338
+ const newTime = Math.max(0, currentTime - skipTime)
339
+ dispatch({ type: MediaActionTypes.SEEK, detail: newTime })
340
+ }, [dispatch, currentTime, skipTime])
341
+
342
+ return (
343
+ <button
344
+ type="button"
345
+ onClick={handleClick}
346
+ className={cn(
347
+ "group relative flex items-center justify-center rounded-full bg-white/10 backdrop-blur-sm",
348
+ "border border-white/20 text-white transition-all duration-200",
349
+ "hover:scale-105 hover:bg-white/20 active:scale-95",
350
+ "focus:outline-none focus:ring-2 focus:ring-white/50",
351
+ sizeClasses[size],
352
+ className
353
+ )}
354
+ aria-label={`Backward ${skipTime} seconds`}
355
+ title={`Backward ${skipTime}s`}
356
+ >
357
+ <div className={cn("flex items-center transition-transform duration-200", iconSizes[size])}>
358
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-full w-full">
359
+ <path d="M7 6c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1s-1-.45-1-1V7c0-.55.45-1 1-1zM15.5 7.5c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v9c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-9z"/>
360
+ <path d="M5.5 12l7-4.5v9l-7-4.5z" opacity="0.8"/>
361
+ </svg>
362
+ {skipTime && (
363
+ <span className="-bottom-1 absolute font-bold text-[10px]">
364
+ {skipTime}
365
+ </span>
366
+ )}
367
+ </div>
368
+ </button>
369
+ )
370
+ }
371
+
372
+ // Enhanced forward skip button
373
+ export const EnhancedForwardButton = function EnhancedForwardButton({
374
+ className,
375
+ size = "md",
376
+ skipTime = 10
377
+ }: {
378
+ className?: string
379
+ size?: "sm" | "md" | "lg"
380
+ skipTime?: number
381
+ }) {
382
+ const dispatch = useMediaDispatch()
383
+ const currentTime = useMediaSelector(state => state.currentTime)
384
+ const duration = useMediaSelector(state => state.duration)
385
+
386
+ const sizeClasses = {
387
+ sm: "h-8 w-8",
388
+ md: "h-10 w-10",
389
+ lg: "h-12 w-12"
390
+ }
391
+
392
+ const iconSizes = {
393
+ sm: "h-4 w-4",
394
+ md: "h-5 w-5",
395
+ lg: "h-6 w-6"
396
+ }
397
+
398
+ const handleClick = React.useCallback(() => {
399
+ const newTime = Math.min(duration, currentTime + skipTime)
400
+ dispatch({ type: MediaActionTypes.SEEK, detail: newTime })
401
+ }, [dispatch, currentTime, duration, skipTime])
402
+
403
+ return (
404
+ <button
405
+ type="button"
406
+ onClick={handleClick}
407
+ className={cn(
408
+ "group relative flex items-center justify-center rounded-full bg-white/10 backdrop-blur-sm",
409
+ "border border-white/20 text-white transition-all duration-200",
410
+ "hover:scale-105 hover:bg-white/20 active:scale-95",
411
+ "focus:outline-none focus:ring-2 focus:ring-white/50",
412
+ sizeClasses[size],
413
+ className
414
+ )}
415
+ aria-label={`Forward ${skipTime} seconds`}
416
+ title={`Forward ${skipTime}s`}
417
+ >
418
+ <div className={cn("flex items-center transition-transform duration-200", iconSizes[size])}>
419
+ <svg viewBox="0 0 24 24" fill="currentColor" className="h-full w-full">
420
+ <path d="M17 6c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1s-1-.45-1-1V7c0-.55.45-1 1-1zM8.5 7.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5v9c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5v-9z"/>
421
+ <path d="M18.5 12l-7-4.5v9l7-4.5z" opacity="0.8"/>
422
+ </svg>
423
+ {skipTime && (
424
+ <span className="-bottom-1 absolute font-bold text-[10px]">
425
+ {skipTime}
426
+ </span>
427
+ )}
428
+ </div>
429
+ </button>
430
+ )
431
+ }
432
+
433
+ // Enhanced time display
434
+ export const EnhancedTimeDisplay = function EnhancedTimeDisplay({
435
+ variant = "current",
436
+ className
437
+ }: {
438
+ variant?: "current" | "duration" | "remaining"
439
+ className?: string
440
+ }) {
441
+ const currentTime = useMediaSelector(state => state.currentTime)
442
+ const duration = useMediaSelector(state => state.duration)
443
+
444
+ const displayTime = React.useMemo(() => {
445
+ switch (variant) {
446
+ case "current":
447
+ return formatTime(currentTime)
448
+ case "duration":
449
+ return formatTime(duration)
450
+ case "remaining":
451
+ return `-${formatTime(duration - currentTime)}`
452
+ default:
453
+ return formatTime(currentTime)
454
+ }
455
+ }, [variant, currentTime, duration])
456
+
457
+ return (
458
+ <div className={cn("font-mono text-sm text-white tabular-nums", className)}>
459
+ {displayTime}
460
+ </div>
461
+ )
462
+ }
463
+
464
+ // Performance indicator component
465
+ export const PerformanceIndicator = function PerformanceIndicator({
466
+ metrics,
467
+ className
468
+ }: {
469
+ metrics?: {
470
+ fps?: number
471
+ bufferHealth?: number
472
+ quality?: number
473
+ bandwidth?: number
474
+ }
475
+ className?: string
476
+ }) {
477
+ if (!metrics || process.env.NODE_ENV !== 'development') {
478
+ return null
479
+ }
480
+
481
+ const getPerformanceColor = (fps: number) => {
482
+ if (fps >= 50) return 'text-green-400'
483
+ if (fps >= 30) return 'text-yellow-400'
484
+ return 'text-red-400'
485
+ }
486
+
487
+ const getBufferColor = (health: number) => {
488
+ if (health >= 5) return 'text-green-400'
489
+ if (health >= 2) return 'text-yellow-400'
490
+ return 'text-red-400'
491
+ }
492
+
493
+ return (
494
+ <div className={cn("flex items-center gap-3 font-mono text-xs", className)}>
495
+ {metrics.fps !== undefined && (
496
+ <div className={cn("flex items-center gap-1", getPerformanceColor(metrics.fps))}>
497
+ <div className="h-2 w-2 rounded-full bg-current" />
498
+ <span>{metrics.fps} FPS</span>
499
+ </div>
500
+ )}
501
+ {metrics.bufferHealth !== undefined && (
502
+ <div className={cn("flex items-center gap-1", getBufferColor(metrics.bufferHealth))}>
503
+ <div className="h-2 w-2 rounded-full bg-current" />
504
+ <span>{metrics.bufferHealth.toFixed(1)}s</span>
505
+ </div>
506
+ )}
507
+ {metrics.quality !== undefined && (
508
+ <div className="flex items-center gap-1 text-blue-400">
509
+ <div className="h-2 w-2 rounded-full bg-current" />
510
+ <span>{metrics.quality}p</span>
511
+ </div>
512
+ )}
513
+ </div>
514
+ )
515
+ }
516
+
517
+ // Enhanced control bar that combines all controls
518
+ export const EnhancedControlBar = function EnhancedControlBar({
519
+ className,
520
+ showSkipButtons = true,
521
+ showVolumeControl = true,
522
+ showTimeDisplay = true,
523
+ playButtonSize = "xl",
524
+ skipButtonSize = "md",
525
+ skipTime = 10,
526
+ showPerformanceIndicators = process.env.NODE_ENV === 'development',
527
+ performanceMetrics
528
+ }: {
529
+ className?: string
530
+ showSkipButtons?: boolean
531
+ showVolumeControl?: boolean
532
+ showTimeDisplay?: boolean
533
+ playButtonSize?: "sm" | "md" | "lg" | "xl"
534
+ skipButtonSize?: "sm" | "md" | "lg"
535
+ skipTime?: number
536
+ showPerformanceIndicators?: boolean
537
+ performanceMetrics?: {
538
+ fps?: number
539
+ bufferHealth?: number
540
+ quality?: number
541
+ bandwidth?: number
542
+ }
543
+ }) {
544
+ return (
545
+ <div className={cn("flex flex-col gap-4 bg-gradient-to-t from-black/80 to-transparent p-4", className)}>
546
+ {/* Seek Bar */}
547
+ <EnhancedSeekBar className="w-full" />
548
+
549
+ {/* Control Buttons */}
550
+ <div className="flex items-center justify-between">
551
+ {/* Left side - Media controls */}
552
+ <div className="flex items-center gap-3">
553
+ {showSkipButtons && (
554
+ <EnhancedBackwardButton
555
+ size={skipButtonSize}
556
+ skipTime={skipTime}
557
+ className="shrink-0"
558
+ />
559
+ )}
560
+
561
+ <EnhancedPlayButton
562
+ size={playButtonSize}
563
+ className="shrink-0"
564
+ />
565
+
566
+ {showSkipButtons && (
567
+ <EnhancedForwardButton
568
+ size={skipButtonSize}
569
+ skipTime={skipTime}
570
+ className="shrink-0"
571
+ />
572
+ )}
573
+
574
+ {showVolumeControl && (
575
+ <EnhancedVolumeControl className="shrink-0" />
576
+ )}
577
+
578
+ {showTimeDisplay && (
579
+ <div className="flex items-center gap-1 font-mono text-sm">
580
+ <EnhancedTimeDisplay variant="current" />
581
+ <span className="text-white/60">/</span>
582
+ <EnhancedTimeDisplay variant="duration" />
583
+ </div>
584
+ )}
585
+ </div>
586
+
587
+ {/* Right side - Performance indicators and additional controls */}
588
+ <div className="flex items-center gap-2">
589
+ {showPerformanceIndicators && (
590
+ <PerformanceIndicator
591
+ metrics={performanceMetrics}
592
+ className="text-white/80"
593
+ />
594
+ )}
595
+ {/* Future: Settings, Fullscreen, etc. */}
596
+ </div>
597
+ </div>
598
+ </div>
599
+ )
600
+ }