@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,32 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { cn } from "../../../lib/utils"
5
+
6
+ export interface MinimalLoadingProps {
7
+ icon?: React.FC<{ size?: number; className?: string }>
8
+ className?: string
9
+ }
10
+
11
+ export function MinimalLoading({
12
+ icon: LoadingIcon,
13
+ className,
14
+ }: MinimalLoadingProps) {
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "absolute inset-0 z-40 flex items-center justify-center",
19
+ "bg-black/60 backdrop-blur-sm",
20
+ className
21
+ )}
22
+ >
23
+ <div className="text-white">
24
+ {LoadingIcon ? (
25
+ <LoadingIcon size={48} className="animate-spin" />
26
+ ) : (
27
+ <div className="minimal-spinner h-12 w-12 animate-spin rounded-full border-3 border-white/20 border-t-white" />
28
+ )}
29
+ </div>
30
+ </div>
31
+ )
32
+ }
@@ -0,0 +1,315 @@
1
+ "use client"
2
+
3
+ import React, { useCallback, useEffect, useRef, useState } from "react"
4
+ import { MediaPlayerRoot } from "../../media-player-root"
5
+ import { MediaPlayerVideo } from "../../media-player-video"
6
+ import { MinimalControls } from "./MinimalControls"
7
+ import { MinimalOverlay } from "./MinimalOverlay"
8
+ import { MinimalLoading } from "./MinimalLoading"
9
+ import { cn } from "../../../lib/utils"
10
+ import type { IconSet } from "../../icons"
11
+ import { defaultMinimalIcons } from "../../icons"
12
+
13
+ export interface MinimalMediaPlayerProps {
14
+ src: string | string[]
15
+ poster?: string
16
+ autoPlay?: boolean
17
+ muted?: boolean
18
+ loop?: boolean
19
+ autoHideControls?: boolean
20
+ hideDelay?: number
21
+ theme?: "dark" | "light" | "transparent"
22
+ customIcons?: IconSet
23
+ showCenterPlayButton?: boolean
24
+ showLoadingSpinner?: boolean
25
+ className?: string
26
+ onPlay?: () => void
27
+ onPause?: () => void
28
+ onEnded?: () => void
29
+ onTimeUpdate?: (currentTime: number) => void
30
+ onVolumeChange?: (volume: number) => void
31
+ onSeek?: (time: number) => void
32
+ onError?: (error: Error) => void
33
+ watermark?: {
34
+ src: string
35
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"
36
+ opacity?: number
37
+ size?: "small" | "medium" | "large"
38
+ }
39
+ qualities?: Array<{
40
+ src: string
41
+ quality: string
42
+ default?: boolean
43
+ }>
44
+ showQualitySelector?: boolean
45
+ showSpeedControl?: boolean
46
+ thumbnails?: string
47
+ }
48
+
49
+ export function MinimalMediaPlayer({
50
+ src,
51
+ poster,
52
+ autoPlay = false,
53
+ muted = false,
54
+ loop = false,
55
+ autoHideControls = true,
56
+ hideDelay = 2500,
57
+ theme = "dark",
58
+ customIcons,
59
+ showCenterPlayButton = true,
60
+ showLoadingSpinner = true,
61
+ className,
62
+ onPlay,
63
+ onPause,
64
+ onEnded,
65
+ onTimeUpdate,
66
+ onVolumeChange,
67
+ onSeek,
68
+ onError,
69
+ watermark,
70
+ qualities,
71
+ showQualitySelector = false,
72
+ showSpeedControl = false,
73
+ thumbnails,
74
+ }: MinimalMediaPlayerProps) {
75
+ const [controlsVisible, setControlsVisible] = useState(true)
76
+ const [isPlaying, setIsPlaying] = useState(false)
77
+ const [isLoading, setIsLoading] = useState(false)
78
+ const [error, setError] = useState<string | null>(null)
79
+ const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null)
80
+ const containerRef = useRef<HTMLDivElement | null>(null)
81
+ const [isHovered, setIsHovered] = useState(false)
82
+ const [userInteracting, setUserInteracting] = useState(false)
83
+
84
+ // Merge default icons with custom icons
85
+ const icons = { ...defaultMinimalIcons, ...customIcons }
86
+
87
+ // Auto-hide controls logic
88
+ const resetHideTimer = () => {
89
+ if (!autoHideControls) return
90
+
91
+ if (hideTimeoutRef.current) {
92
+ clearTimeout(hideTimeoutRef.current)
93
+ }
94
+ setControlsVisible(true)
95
+
96
+ if (!userInteracting && isPlaying) {
97
+ hideTimeoutRef.current = setTimeout(() => {
98
+ setControlsVisible(false)
99
+ }, hideDelay)
100
+ }
101
+ }
102
+
103
+ // Handle mouse movement
104
+ const handleMouseMove = () => {
105
+ resetHideTimer()
106
+ }
107
+
108
+ // Handle mouse enter/leave
109
+ const handleMouseEnter = () => {
110
+ setIsHovered(true)
111
+ setControlsVisible(true)
112
+ if (hideTimeoutRef.current) {
113
+ clearTimeout(hideTimeoutRef.current)
114
+ }
115
+ }
116
+
117
+ const handleMouseLeave = () => {
118
+ setIsHovered(false)
119
+ if (isPlaying && autoHideControls && !userInteracting) {
120
+ hideTimeoutRef.current = setTimeout(() => {
121
+ setControlsVisible(false)
122
+ }, 500)
123
+ }
124
+ }
125
+
126
+ // Handle user interaction
127
+ const handleUserInteractionStart = () => {
128
+ setUserInteracting(true)
129
+ setControlsVisible(true)
130
+ if (hideTimeoutRef.current) {
131
+ clearTimeout(hideTimeoutRef.current)
132
+ }
133
+ }
134
+
135
+ const handleUserInteractionEnd = () => {
136
+ setUserInteracting(false)
137
+ resetHideTimer()
138
+ }
139
+
140
+ // Handle play state changes
141
+ const handlePlayStateChange = (playing: boolean) => {
142
+ setIsPlaying(playing)
143
+ if (playing) {
144
+ onPlay?.()
145
+ resetHideTimer()
146
+ } else {
147
+ onPause?.()
148
+ setControlsVisible(true)
149
+ if (hideTimeoutRef.current) {
150
+ clearTimeout(hideTimeoutRef.current)
151
+ }
152
+ }
153
+ }
154
+
155
+ // Handle loading state
156
+ const handleLoadStart = () => {
157
+ setIsLoading(true)
158
+ setError(null)
159
+ }
160
+
161
+ const handleLoadedData = () => {
162
+ setIsLoading(false)
163
+ }
164
+
165
+ // Handle errors
166
+ const handleError = (e: Error) => {
167
+ setIsLoading(false)
168
+ setError(e.message || "An error occurred")
169
+ onError?.(e)
170
+ }
171
+
172
+ // Cleanup on unmount
173
+ useEffect(() => {
174
+ return () => {
175
+ if (hideTimeoutRef.current) {
176
+ clearTimeout(hideTimeoutRef.current)
177
+ }
178
+ }
179
+ }, [])
180
+
181
+ // Determine video source
182
+ const videoSrc = qualities && qualities.length > 0
183
+ ? qualities.find(q => q.default)?.src || qualities[0]?.src || (typeof src === 'string' ? src : src[0])
184
+ : (typeof src === 'string' ? src : src[0])
185
+
186
+ return (
187
+ <div
188
+ ref={containerRef}
189
+ className={cn(
190
+ "minimal-player theme-minimal relative overflow-hidden rounded-lg bg-black",
191
+ `theme-minimal-${theme}`,
192
+ className
193
+ )}
194
+ data-theme={theme}
195
+ onMouseMove={handleMouseMove}
196
+ onMouseEnter={handleMouseEnter}
197
+ onMouseLeave={handleMouseLeave}
198
+ >
199
+ <MediaPlayerRoot
200
+ className="relative h-full w-full"
201
+ autoHide={false}
202
+ enableGestures={true}
203
+ enableMobileOptimizations={true}
204
+ >
205
+ {/* Video Element */}
206
+ <MediaPlayerVideo
207
+ src={videoSrc}
208
+ poster={poster}
209
+ autoPlay={autoPlay}
210
+ muted={muted}
211
+ loop={loop}
212
+ playsInline
213
+ crossOrigin="anonymous"
214
+ onPlay={() => handlePlayStateChange(true)}
215
+ onPause={() => handlePlayStateChange(false)}
216
+ onEnded={onEnded}
217
+ onTimeUpdate={(e) => {
218
+ const video = e.currentTarget as HTMLVideoElement
219
+ onTimeUpdate?.(video.currentTime)
220
+ }}
221
+ onVolumeChange={(e) => {
222
+ const video = e.currentTarget as HTMLVideoElement
223
+ onVolumeChange?.(video.volume)
224
+ }}
225
+ onLoadStart={handleLoadStart}
226
+ onLoadedData={handleLoadedData}
227
+ onError={(e) => handleError(new Error("Failed to load video"))}
228
+ />
229
+
230
+ {/* Loading State */}
231
+ {showLoadingSpinner && isLoading && (
232
+ <MinimalLoading icon={icons.spinner} />
233
+ )}
234
+
235
+ {/* Error State */}
236
+ {error && (
237
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/80">
238
+ <div className="text-center">
239
+ <div className="mb-4 text-red-500">
240
+ <svg className="mx-auto h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
241
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
242
+ </svg>
243
+ </div>
244
+ <p className="mb-2 text-lg text-white">Playback Error</p>
245
+ <p className="text-gray-400 text-sm">{error}</p>
246
+ </div>
247
+ </div>
248
+ )}
249
+
250
+ {/* Watermark */}
251
+ {watermark && (
252
+ <div
253
+ className={cn(
254
+ "pointer-events-none absolute z-20",
255
+ {
256
+ "top-4 left-4": watermark.position === "top-left",
257
+ "top-4 right-4": watermark.position === "top-right",
258
+ "bottom-16 left-4": watermark.position === "bottom-left",
259
+ "right-4 bottom-16": watermark.position === "bottom-right",
260
+ }
261
+ )}
262
+ style={{ opacity: watermark.opacity || 0.7 }}
263
+ >
264
+ <img
265
+ src={watermark.src}
266
+ alt=""
267
+ className={cn(
268
+ "object-contain",
269
+ {
270
+ "h-8": watermark.size === "small",
271
+ "h-12": watermark.size === "medium" || !watermark.size,
272
+ "h-16": watermark.size === "large",
273
+ }
274
+ )}
275
+ />
276
+ </div>
277
+ )}
278
+
279
+ {/* Center Play Button */}
280
+ {showCenterPlayButton && !isPlaying && !isLoading && (
281
+ <MinimalOverlay
282
+ visible={true}
283
+ showCenterButton={true}
284
+ isPlaying={isPlaying}
285
+ icons={icons}
286
+ onPlayPause={() => {
287
+ const video = containerRef.current?.querySelector("video")
288
+ if (video) {
289
+ if (isPlaying) {
290
+ video.pause()
291
+ } else {
292
+ video.play()
293
+ }
294
+ }
295
+ }}
296
+ />
297
+ )}
298
+
299
+ {/* Controls */}
300
+ <MinimalControls
301
+ visible={controlsVisible || !isPlaying || isHovered}
302
+ icons={icons}
303
+ isPlaying={isPlaying}
304
+ showQualitySelector={showQualitySelector}
305
+ showSpeedControl={showSpeedControl}
306
+ qualities={qualities}
307
+ thumbnails={thumbnails}
308
+ onUserInteractionStart={handleUserInteractionStart}
309
+ onUserInteractionEnd={handleUserInteractionEnd}
310
+ onSeek={onSeek}
311
+ />
312
+ </MediaPlayerRoot>
313
+ </div>
314
+ )
315
+ }
@@ -0,0 +1,64 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { cn } from "../../../lib/utils"
5
+ import type { IconSet } from "../../icons"
6
+
7
+ export interface MinimalOverlayProps {
8
+ visible: boolean
9
+ showCenterButton?: boolean
10
+ isPlaying?: boolean
11
+ icons: IconSet
12
+ onPlayPause?: () => void
13
+ className?: string
14
+ }
15
+
16
+ export function MinimalOverlay({
17
+ visible,
18
+ showCenterButton = false,
19
+ isPlaying = false,
20
+ icons,
21
+ onPlayPause,
22
+ className,
23
+ }: MinimalOverlayProps) {
24
+ if (!visible || !showCenterButton) return null
25
+
26
+ const PlayIcon = icons.play
27
+ const PauseIcon = icons.pause
28
+
29
+ if (!PlayIcon || !PauseIcon) return null
30
+
31
+ return (
32
+ <div
33
+ className={cn(
34
+ "absolute inset-0 z-30 flex items-center justify-center",
35
+ "bg-black/20 transition-opacity duration-200",
36
+ className
37
+ )}
38
+ onClick={onPlayPause}
39
+ >
40
+ <button
41
+ className={cn(
42
+ "minimal-center-play",
43
+ "h-20 w-20 rounded-full",
44
+ "flex items-center justify-center",
45
+ "text-white",
46
+ "transition-all duration-200",
47
+ "hover:scale-110",
48
+ "active:scale-95"
49
+ )}
50
+ onClick={(e) => {
51
+ e.stopPropagation()
52
+ onPlayPause?.()
53
+ }}
54
+ aria-label={isPlaying ? "Pause" : "Play"}
55
+ >
56
+ {isPlaying ? (
57
+ <PauseIcon size={48} />
58
+ ) : (
59
+ <PlayIcon size={48} className="ml-1" />
60
+ )}
61
+ </button>
62
+ </div>
63
+ )
64
+ }
@@ -0,0 +1,65 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { useMediaContext, useMediaDispatch, MediaActionTypes } from "../../../context/media-context"
5
+ import { cn } from "../../../lib/utils"
6
+ import type { IconSet } from "../../icons"
7
+
8
+ export interface MinimalPlayButtonProps {
9
+ isPlaying: boolean
10
+ icons: IconSet
11
+ size?: "sm" | "md" | "lg" | "xl"
12
+ className?: string
13
+ }
14
+
15
+ export function MinimalPlayButton({
16
+ isPlaying,
17
+ icons,
18
+ size = "md",
19
+ className,
20
+ }: MinimalPlayButtonProps) {
21
+ const dispatch = useMediaDispatch()
22
+
23
+ const handleClick = () => {
24
+ dispatch({
25
+ type: isPlaying ? MediaActionTypes.PAUSE : MediaActionTypes.PLAY
26
+ })
27
+ }
28
+
29
+ const sizeClasses = {
30
+ sm: "h-10 w-10",
31
+ md: "h-12 w-12 minimal-btn",
32
+ lg: "h-16 w-16 minimal-btn-lg",
33
+ xl: "h-20 w-20 minimal-btn-lg",
34
+ }
35
+
36
+ const iconSizes = {
37
+ sm: 27,
38
+ md: 30,
39
+ lg: 36,
40
+ xl: 42,
41
+ }
42
+
43
+ const PlayIcon = icons.play
44
+ const PauseIcon = icons.pause
45
+
46
+ if (!PlayIcon || !PauseIcon) return null
47
+
48
+ return (
49
+ <button
50
+ className={cn(
51
+ "minimal-btn",
52
+ sizeClasses[size],
53
+ className
54
+ )}
55
+ onClick={handleClick}
56
+ aria-label={isPlaying ? "Pause" : "Play"}
57
+ >
58
+ {isPlaying ? (
59
+ <PauseIcon size={iconSizes[size]} />
60
+ ) : (
61
+ <PlayIcon size={iconSizes[size]} />
62
+ )}
63
+ </button>
64
+ )
65
+ }
@@ -0,0 +1,150 @@
1
+ "use client"
2
+
3
+ import React, { useRef, useState } from "react"
4
+ import { useMediaSelector, useMediaDispatch, MediaActionTypes } from "../../../context/media-context"
5
+ import { formatTime } from "../../../lib/format-time"
6
+ import { cn } from "../../../lib/utils"
7
+
8
+ export interface MinimalSeekBarProps {
9
+ thumbnails?: string
10
+ onSeek?: (time: number) => void
11
+ className?: string
12
+ }
13
+
14
+ export function MinimalSeekBar({
15
+ thumbnails,
16
+ onSeek,
17
+ className,
18
+ }: MinimalSeekBarProps) {
19
+ const dispatch = useMediaDispatch()
20
+ const currentTime = useMediaSelector((state) => state.currentTime)
21
+ const duration = useMediaSelector((state) => state.duration)
22
+ const buffered = useMediaSelector((state) => state.buffered)
23
+
24
+ const [isDragging, setIsDragging] = useState(false)
25
+ const [hoverTime, setHoverTime] = useState(0)
26
+ const [hoverPosition, setHoverPosition] = useState(0)
27
+ const seekBarRef = useRef<HTMLDivElement>(null)
28
+
29
+ const progress = duration > 0 ? (currentTime / duration) * 100 : 0
30
+ const bufferedProgress = buffered && buffered.length > 0 && duration > 0
31
+ ? (buffered.end(buffered.length - 1) / duration) * 100
32
+ : 0
33
+
34
+ const handleSeek = (clientX: number) => {
35
+ if (!seekBarRef.current || duration === 0) return
36
+
37
+ const rect = seekBarRef.current.getBoundingClientRect()
38
+ const position = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width))
39
+ const newTime = position * duration
40
+
41
+ dispatch({
42
+ type: MediaActionTypes.SEEK,
43
+ detail: newTime
44
+ })
45
+
46
+ onSeek?.(newTime)
47
+ }
48
+
49
+ const handleMouseDown = (e: React.MouseEvent) => {
50
+ e.preventDefault()
51
+ setIsDragging(true)
52
+ handleSeek(e.clientX)
53
+ }
54
+
55
+ const handleMouseMove = (e: React.MouseEvent) => {
56
+ if (!seekBarRef.current || duration === 0) return
57
+
58
+ const rect = seekBarRef.current.getBoundingClientRect()
59
+ const position = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
60
+ const time = position * duration
61
+
62
+ setHoverTime(time)
63
+ setHoverPosition((e.clientX - rect.left))
64
+
65
+ if (isDragging) {
66
+ handleSeek(e.clientX)
67
+ }
68
+ }
69
+
70
+ const handleMouseUp = () => {
71
+ setIsDragging(false)
72
+ }
73
+
74
+ const handleMouseLeave = () => {
75
+ setIsDragging(false)
76
+ setHoverTime(0)
77
+ }
78
+
79
+ React.useEffect(() => {
80
+ if (isDragging) {
81
+ const handleGlobalMouseMove = (e: MouseEvent) => {
82
+ handleSeek(e.clientX)
83
+ }
84
+
85
+ const handleGlobalMouseUp = () => {
86
+ setIsDragging(false)
87
+ }
88
+
89
+ document.addEventListener("mousemove", handleGlobalMouseMove)
90
+ document.addEventListener("mouseup", handleGlobalMouseUp)
91
+
92
+ return () => {
93
+ document.removeEventListener("mousemove", handleGlobalMouseMove)
94
+ document.removeEventListener("mouseup", handleGlobalMouseUp)
95
+ }
96
+ }
97
+ }, [isDragging])
98
+
99
+ return (
100
+ <div className={cn("group relative", className)}>
101
+ {/* Preview Tooltip */}
102
+ {hoverTime > 0 && (
103
+ <div
104
+ className="pointer-events-none absolute bottom-full z-50 mb-2"
105
+ style={{ left: `${hoverPosition}px`, transform: "translateX(-50%)" }}
106
+ >
107
+ {thumbnails && (
108
+ <div className="mb-1 rounded bg-black/90 p-1">
109
+ <div className="h-18 w-32 rounded bg-gray-800" />
110
+ </div>
111
+ )}
112
+ <div className="whitespace-nowrap rounded bg-black/90 px-2 py-1 text-white text-xs">
113
+ {formatTime(hoverTime)}
114
+ </div>
115
+ </div>
116
+ )}
117
+
118
+ {/* Seek Bar */}
119
+ <div
120
+ ref={seekBarRef}
121
+ className="minimal-seekbar"
122
+ onMouseDown={handleMouseDown}
123
+ onMouseMove={handleMouseMove}
124
+ onMouseUp={handleMouseUp}
125
+ onMouseLeave={handleMouseLeave}
126
+ >
127
+ {/* Buffered Progress */}
128
+ <div
129
+ className="minimal-seekbar-buffered"
130
+ style={{ width: `${bufferedProgress}%` }}
131
+ />
132
+
133
+ {/* Progress */}
134
+ <div
135
+ className="minimal-seekbar-progress"
136
+ style={{ width: `${progress}%` }}
137
+ />
138
+
139
+ {/* Thumb */}
140
+ <div
141
+ className={cn(
142
+ "minimal-seekbar-thumb",
143
+ isDragging && "active"
144
+ )}
145
+ style={{ left: `${progress}%` }}
146
+ />
147
+ </div>
148
+ </div>
149
+ )
150
+ }
@@ -0,0 +1,80 @@
1
+ "use client"
2
+
3
+ import React, { useState } from "react"
4
+ import { useMediaSelector, useMediaDispatch, MediaActionTypes } from "../../../context/media-context"
5
+ import { cn } from "../../../lib/utils"
6
+ import type { IconSet } from "../../icons"
7
+
8
+ export interface MinimalVolumeControlProps {
9
+ icons: IconSet
10
+ className?: string
11
+ }
12
+
13
+ export function MinimalVolumeControl({
14
+ icons,
15
+ className,
16
+ }: MinimalVolumeControlProps) {
17
+ const dispatch = useMediaDispatch()
18
+ const volume = useMediaSelector((state) => state.volume)
19
+ const isMuted = useMediaSelector((state) => state.isMuted)
20
+ const [isExpanded, setIsExpanded] = useState(false)
21
+
22
+ const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
23
+ const newVolume = parseFloat(e.target.value)
24
+ dispatch({
25
+ type: MediaActionTypes.SET_VOLUME,
26
+ detail: newVolume
27
+ })
28
+ }
29
+
30
+ const handleMuteToggle = () => {
31
+ dispatch({
32
+ type: MediaActionTypes.TOGGLE_MUTE
33
+ })
34
+ }
35
+
36
+ const VolumeIcon = isMuted || volume === 0 ? icons.volumeMuted : icons.volume
37
+
38
+ if (!VolumeIcon) return null
39
+
40
+ return (
41
+ <div
42
+ className={cn(
43
+ "minimal-volume-group flex items-center gap-2",
44
+ className
45
+ )}
46
+ onMouseEnter={() => setIsExpanded(true)}
47
+ onMouseLeave={() => setIsExpanded(false)}
48
+ >
49
+ {/* Volume Button */}
50
+ <button
51
+ className="minimal-btn inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/20 bg-white/10 text-white transition-all duration-150 hover:border-white/30 hover:bg-white/20"
52
+ onClick={handleMuteToggle}
53
+ aria-label={isMuted ? "Unmute" : "Mute"}
54
+ >
55
+ <VolumeIcon size={18} />
56
+ </button>
57
+
58
+ {/* Volume Slider */}
59
+ <div
60
+ className={cn(
61
+ "overflow-hidden transition-all duration-250",
62
+ isExpanded ? "w-20 opacity-100" : "w-0 opacity-0"
63
+ )}
64
+ >
65
+ <input
66
+ type="range"
67
+ min="0"
68
+ max="1"
69
+ step="0.01"
70
+ value={isMuted ? 0 : volume}
71
+ onChange={handleVolumeChange}
72
+ className="minimal-volume-slider h-1 w-full cursor-pointer appearance-none rounded-full bg-white/20"
73
+ style={{
74
+ background: `linear-gradient(to right, rgb(59 130 246) 0%, rgb(59 130 246) ${(isMuted ? 0 : volume) * 100}%, rgba(255,255,255,0.2) ${(isMuted ? 0 : volume) * 100}%, rgba(255,255,255,0.2) 100%)`
75
+ }}
76
+ />
77
+ </div>
78
+ </div>
79
+ )
80
+ }