@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,226 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { useMediaSelector } from "../context/media-context"
5
+ import { useDeviceInfo, useContainerQuery } from "@rp/ui/hooks/use-mobile"
6
+ import { cn } from "../lib/utils"
7
+
8
+ export interface MediaPlayerControlsProps {
9
+ children: React.ReactNode
10
+ className?: string
11
+ asChild?: boolean
12
+ variant?: 'default' | 'mobile' | 'compact'
13
+ adaptiveLayout?: boolean
14
+ }
15
+
16
+ export function MediaPlayerControls({
17
+ children,
18
+ className,
19
+ asChild = false,
20
+ variant,
21
+ adaptiveLayout = true,
22
+ ...props
23
+ }: MediaPlayerControlsProps) {
24
+ const isFullscreen = useMediaSelector((state) => state.isFullscreen)
25
+ const disabled = useMediaSelector((state) => state.error !== null)
26
+ const { isMobile, isTablet, isTouchDevice, orientation } = useDeviceInfo()
27
+
28
+ // Container ref for responsive queries
29
+ const containerRef = React.useRef<HTMLDivElement>(null)
30
+ const isCompactContainer = useContainerQuery(containerRef as React.RefObject<HTMLElement>, '(max-width: 480px)')
31
+
32
+ // Determine the effective variant
33
+ const effectiveVariant = React.useMemo(() => {
34
+ if (variant) return variant
35
+ if (!adaptiveLayout) return 'default'
36
+
37
+ // Auto-detect based on device and container size
38
+ if (isMobile || isCompactContainer) return 'mobile'
39
+ if (isTablet) return 'compact'
40
+ return 'default'
41
+ }, [variant, adaptiveLayout, isMobile, isTablet, isCompactContainer])
42
+
43
+ // Get mobile-optimized classes
44
+ const getMobileClasses = () => {
45
+ const baseClasses = [
46
+ // Base mobile-first classes
47
+ "pointer-events-none absolute right-0 bottom-0 left-0 z-50",
48
+ "opacity-0 transition-opacity duration-200",
49
+ "data-[visible]:pointer-events-auto data-[visible]:opacity-100"
50
+ ]
51
+
52
+ switch (effectiveVariant) {
53
+ case 'mobile':
54
+ return [
55
+ ...baseClasses,
56
+ // Mobile-first layout with larger touch targets
57
+ "flex flex-col gap-3 px-4 py-4",
58
+ // Larger padding for better thumb reach
59
+ "[@container(min-width:360px)]:px-6 [@container(min-width:360px)]:py-5",
60
+ // Portrait orientation adjustments
61
+ orientation === 'portrait' && "pb-6",
62
+ // Fullscreen mobile adjustments
63
+ "[:fullscreen_&]:px-8 [:fullscreen_&]:py-6",
64
+ // Ensure minimum touch target sizes (44px)
65
+ "[&_button]:min-h-[44px] [&_button]:min-w-[44px]",
66
+ "[&_[role=slider]]:min-h-[44px]",
67
+ // Better spacing for touch
68
+ "[&>*]:gap-4",
69
+ ]
70
+
71
+ case 'compact':
72
+ return [
73
+ ...baseClasses,
74
+ // Tablet-optimized compact layout
75
+ "flex items-center gap-2 px-4 py-3",
76
+ "[:fullscreen_&]:px-6 [:fullscreen_&]:py-4",
77
+ // Medium-sized touch targets (40px)
78
+ "[&_button]:min-h-[40px] [&_button]:min-w-[40px]",
79
+ "[&_[role=slider]]:min-h-[40px]",
80
+ ]
81
+
82
+ default:
83
+ return [
84
+ ...baseClasses,
85
+ // Desktop default layout
86
+ "flex items-center gap-2 px-4 py-3",
87
+ "[:fullscreen_&]:px-6 [:fullscreen_&]:py-4",
88
+ // Standard button sizes for desktop
89
+ "[&_button]:min-h-[36px] [&_button]:min-w-[36px]",
90
+ ]
91
+ }
92
+ }
93
+
94
+ if (asChild) {
95
+ const child = React.Children.only(children) as React.ReactElement
96
+ return React.cloneElement(child, {
97
+ ...props,
98
+ ref: containerRef,
99
+ "data-slot": "media-player-controls",
100
+ "data-state": isFullscreen ? "fullscreen" : "inline",
101
+ "data-disabled": disabled ? "" : undefined,
102
+ "data-variant": effectiveVariant,
103
+ "data-touch": isTouchDevice ? "" : undefined,
104
+ className: cn(
105
+ getMobileClasses(),
106
+ // Add container query support
107
+ "@container",
108
+ className
109
+ )
110
+ } as any)
111
+ }
112
+
113
+ return (
114
+ <div
115
+ ref={containerRef}
116
+ data-slot="media-player-controls"
117
+ data-state={isFullscreen ? "fullscreen" : "inline"}
118
+ data-disabled={disabled ? "" : undefined}
119
+ data-variant={effectiveVariant}
120
+ data-touch={isTouchDevice ? "" : undefined}
121
+ className={cn(
122
+ "dark",
123
+ getMobileClasses(),
124
+ // Add container query support
125
+ "@container",
126
+ className
127
+ )}
128
+ {...props}>
129
+ {children}
130
+ </div>
131
+ )
132
+ }
133
+
134
+ MediaPlayerControls.displayName = "MediaPlayerControls"
135
+
136
+ // Controls overlay for background gradient
137
+ export interface MediaPlayerControlsOverlayProps {
138
+ className?: string
139
+ asChild?: boolean
140
+ children?: React.ReactNode
141
+ variant?: 'default' | 'mobile' | 'compact'
142
+ }
143
+
144
+ export function MediaPlayerControlsOverlay({
145
+ className,
146
+ asChild = false,
147
+ children,
148
+ variant,
149
+ ...props
150
+ }: MediaPlayerControlsOverlayProps) {
151
+ const isFullscreen = useMediaSelector((state) => state.isFullscreen)
152
+ const { isMobile, isTablet, isTouchDevice } = useDeviceInfo()
153
+
154
+ // Determine the effective variant
155
+ const effectiveVariant = React.useMemo(() => {
156
+ if (variant) return variant
157
+ if (isMobile) return 'mobile'
158
+ if (isTablet) return 'compact'
159
+ return 'default'
160
+ }, [variant, isMobile, isTablet])
161
+
162
+ // Get mobile-optimized gradient classes
163
+ const getOverlayClasses = () => {
164
+ const baseClasses = [
165
+ "-z-10 pointer-events-none absolute inset-0",
166
+ "opacity-0 transition-opacity duration-200 data-[visible]:opacity-100"
167
+ ]
168
+
169
+ switch (effectiveVariant) {
170
+ case 'mobile':
171
+ return [
172
+ ...baseClasses,
173
+ // Enhanced gradient for better readability on mobile
174
+ "bg-gradient-to-t from-black/90 via-black/60 to-transparent",
175
+ // Larger gradient area for mobile controls
176
+ "from-0% via-40% to-70%"
177
+ ]
178
+
179
+ case 'compact':
180
+ return [
181
+ ...baseClasses,
182
+ // Medium gradient for tablet
183
+ "bg-gradient-to-t from-black/85 via-black/40 to-transparent",
184
+ "from-0% via-30% to-60%"
185
+ ]
186
+
187
+ default:
188
+ return [
189
+ ...baseClasses,
190
+ // Standard desktop gradient
191
+ "bg-gradient-to-t from-black/80 to-transparent"
192
+ ]
193
+ }
194
+ }
195
+
196
+ if (asChild) {
197
+ const child = React.Children.only(children) as React.ReactElement
198
+ return React.cloneElement(child, {
199
+ ...props,
200
+ "data-slot": "media-player-controls-overlay",
201
+ "data-state": isFullscreen ? "fullscreen" : "inline",
202
+ "data-variant": effectiveVariant,
203
+ "data-touch": isTouchDevice ? "" : undefined,
204
+ className: cn(
205
+ getOverlayClasses(),
206
+ className
207
+ )
208
+ } as any)
209
+ }
210
+
211
+ return (
212
+ <div
213
+ data-slot="media-player-controls-overlay"
214
+ data-state={isFullscreen ? "fullscreen" : "inline"}
215
+ data-variant={effectiveVariant}
216
+ data-touch={isTouchDevice ? "" : undefined}
217
+ className={cn(
218
+ getOverlayClasses(),
219
+ className
220
+ )}
221
+ {...props}
222
+ />
223
+ )
224
+ }
225
+
226
+ MediaPlayerControlsOverlay.displayName = "MediaPlayerControlsOverlay"
@@ -0,0 +1,163 @@
1
+ "use client"
2
+
3
+ import { Button } from "@rp/ui/components/button"
4
+ import { AlertTriangleIcon, RefreshCcwIcon, RotateCcwIcon } from "lucide-react"
5
+ import React from "react"
6
+ import { useMediaContext, useMediaSelector } from "../context/media-context"
7
+ import { cn } from "../lib/utils"
8
+
9
+ export interface MediaPlayerErrorProps {
10
+ error?: Error | string | null
11
+ label?: string
12
+ description?: string
13
+ onRetry?: () => void
14
+ onReload?: () => void
15
+ className?: string
16
+ asChild?: boolean
17
+ children?: React.ReactNode
18
+ }
19
+
20
+ export function MediaPlayerError({
21
+ error: errorProp,
22
+ label,
23
+ description,
24
+ onRetry: onRetryProp,
25
+ onReload: onReloadProp,
26
+ className,
27
+ asChild = false,
28
+ children,
29
+ ...props
30
+ }: MediaPlayerErrorProps) {
31
+ const context = useMediaContext()
32
+ const stateError = useMediaSelector((state) => state.error)
33
+
34
+ const error = errorProp || stateError
35
+ const [actionState, setActionState] = React.useState({
36
+ retryPending: false,
37
+ reloadPending: false
38
+ })
39
+
40
+ const handleRetry = React.useCallback(() => {
41
+ setActionState((prev) => ({ ...prev, retryPending: true }))
42
+
43
+ requestAnimationFrame(() => {
44
+ const mediaElement = context.mediaRef.current
45
+ if (!mediaElement) {
46
+ setActionState((prev) => ({ ...prev, retryPending: false }))
47
+ return
48
+ }
49
+
50
+ if (onRetryProp) {
51
+ onRetryProp()
52
+ } else {
53
+ const currentSrc = mediaElement.src
54
+ if (currentSrc) {
55
+ mediaElement.load()
56
+ }
57
+ }
58
+
59
+ setActionState((prev) => ({ ...prev, retryPending: false }))
60
+ })
61
+ }, [context.mediaRef, onRetryProp])
62
+
63
+ const handleReload = React.useCallback(() => {
64
+ setActionState((prev) => ({ ...prev, reloadPending: true }))
65
+
66
+ requestAnimationFrame(() => {
67
+ if (onReloadProp) {
68
+ onReloadProp()
69
+ } else {
70
+ window.location.reload()
71
+ }
72
+ })
73
+ }, [onReloadProp])
74
+
75
+ const errorLabel = React.useMemo(() => {
76
+ if (label) return label
77
+
78
+ if (error instanceof Error) {
79
+ return "Media Error"
80
+ }
81
+
82
+ return "Playback Error"
83
+ }, [label, error])
84
+
85
+ const errorDescription = React.useMemo(() => {
86
+ if (description) return description
87
+
88
+ if (error instanceof Error) {
89
+ return error.message
90
+ }
91
+
92
+ if (typeof error === "string") {
93
+ return error
94
+ }
95
+
96
+ return "An error occurred while loading the media"
97
+ }, [description, error])
98
+
99
+ if (!error) return null
100
+
101
+ const errorProps = {
102
+ role: "alert",
103
+ "aria-describedby": `${context.mediaId}-error-description`,
104
+ "aria-labelledby": `${context.mediaId}-error-label`,
105
+ "aria-live": "assertive" as const,
106
+ "data-slot": "media-player-error",
107
+ "data-state": "error",
108
+ className: cn(
109
+ "pointer-events-auto absolute inset-0 z-50 flex flex-col items-center justify-center bg-black/80 text-white backdrop-blur-sm",
110
+ className
111
+ ),
112
+ ...props
113
+ }
114
+
115
+ const content = (
116
+ <div className="flex flex-col items-center gap-4 p-6 text-center">
117
+ <AlertTriangleIcon className="size-12 text-red-400" />
118
+ <div className="space-y-2">
119
+ <h3 id={`${context.mediaId}-error-label`} className="font-semibold text-lg">
120
+ {errorLabel}
121
+ </h3>
122
+ <p id={`${context.mediaId}-error-description`} className="text-sm text-white/80">
123
+ {errorDescription}
124
+ </p>
125
+ </div>
126
+ <div className="flex items-center gap-2">
127
+ <Button
128
+ variant="outline"
129
+ size="sm"
130
+ onClick={handleRetry}
131
+ disabled={actionState.retryPending}>
132
+ {actionState.retryPending ? (
133
+ <RefreshCcwIcon className="mr-2 size-4 animate-spin" />
134
+ ) : (
135
+ <RefreshCcwIcon className="mr-2 size-4" />
136
+ )}
137
+ Try again
138
+ </Button>
139
+ <Button
140
+ variant="outline"
141
+ size="sm"
142
+ onClick={handleReload}
143
+ disabled={actionState.reloadPending}>
144
+ {actionState.reloadPending ? (
145
+ <RotateCcwIcon className="mr-2 size-4 animate-spin" />
146
+ ) : (
147
+ <RotateCcwIcon className="mr-2 size-4" />
148
+ )}
149
+ Reload page
150
+ </Button>
151
+ </div>
152
+ </div>
153
+ )
154
+
155
+ if (asChild) {
156
+ const child = React.Children.only(children) as React.ReactElement
157
+ return React.cloneElement(child, errorProps as any, content)
158
+ }
159
+
160
+ return <div {...errorProps}>{content}</div>
161
+ }
162
+
163
+ MediaPlayerError.displayName = "MediaPlayerError"
@@ -0,0 +1,109 @@
1
+ "use client"
2
+
3
+ import { Button } from "@rp/ui/components/button"
4
+ import { Maximize2Icon, Minimize2Icon } from "lucide-react"
5
+ import React from "react"
6
+ import {
7
+ MediaActionTypes,
8
+ useMediaContext,
9
+ useMediaDispatch,
10
+ useMediaSelector
11
+ } from "../context/media-context"
12
+ import { MediaPlayerTooltip } from "./media-player-tooltip"
13
+
14
+ export interface MediaPlayerFullscreenProps {
15
+ className?: string
16
+ disabled?: boolean
17
+ asChild?: boolean
18
+ children?: React.ReactNode
19
+ }
20
+
21
+ export function MediaPlayerFullscreen({
22
+ className,
23
+ disabled = false,
24
+ asChild = false,
25
+ children,
26
+ ...props
27
+ }: MediaPlayerFullscreenProps) {
28
+ const context = useMediaContext()
29
+ const dispatch = useMediaDispatch()
30
+ const isFullscreen = useMediaSelector((state) => state.isFullscreen)
31
+ const isDisabled = disabled || useMediaSelector((state) => state.error !== null)
32
+
33
+ const handleClick = React.useCallback(
34
+ (event: React.MouseEvent) => {
35
+ event.preventDefault()
36
+
37
+ if (isDisabled) return
38
+
39
+ dispatch({
40
+ type: isFullscreen ? MediaActionTypes.EXIT_FULLSCREEN : MediaActionTypes.ENTER_FULLSCREEN
41
+ })
42
+ },
43
+ [dispatch, isFullscreen, isDisabled]
44
+ )
45
+
46
+ // Set up fullscreen API event listeners
47
+ React.useEffect(() => {
48
+ const handleFullscreenChange = () => {
49
+ const isCurrentlyFullscreen = !!document.fullscreenElement
50
+ dispatch({
51
+ type: isCurrentlyFullscreen
52
+ ? MediaActionTypes.ENTER_FULLSCREEN
53
+ : MediaActionTypes.EXIT_FULLSCREEN
54
+ })
55
+ }
56
+
57
+ document.addEventListener("fullscreenchange", handleFullscreenChange)
58
+ return () => {
59
+ document.removeEventListener("fullscreenchange", handleFullscreenChange)
60
+ }
61
+ }, [dispatch])
62
+
63
+ // Handle actual fullscreen API calls
64
+ React.useEffect(() => {
65
+ const rootElement = context.rootRef.current
66
+ if (!rootElement) return
67
+
68
+ if (isFullscreen && !document.fullscreenElement) {
69
+ rootElement.requestFullscreen?.().catch(console.error)
70
+ } else if (!isFullscreen && document.fullscreenElement) {
71
+ document.exitFullscreen?.().catch(console.error)
72
+ }
73
+ }, [isFullscreen, context.rootRef])
74
+
75
+ const buttonProps = {
76
+ type: "button" as const,
77
+ "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
78
+ "data-disabled": isDisabled ? "" : undefined,
79
+ "data-slot": "media-player-fullscreen",
80
+ "data-state": isFullscreen ? "fullscreen" : "inline",
81
+ disabled: isDisabled,
82
+ variant: "ghost" as const,
83
+ size: "icon" as const,
84
+ className: "size-8",
85
+ onClick: handleClick,
86
+ ...props
87
+ }
88
+
89
+ const content = isFullscreen ? (
90
+ <Minimize2Icon className="size-4" />
91
+ ) : (
92
+ <Maximize2Icon className="size-4" />
93
+ )
94
+
95
+ if (asChild) {
96
+ const child = React.Children.only(children) as React.ReactElement
97
+ return React.cloneElement(child, buttonProps as any, content)
98
+ }
99
+
100
+ return (
101
+ <MediaPlayerTooltip
102
+ tooltip={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
103
+ shortcut="F">
104
+ <Button {...buttonProps}>{content}</Button>
105
+ </MediaPlayerTooltip>
106
+ )
107
+ }
108
+
109
+ MediaPlayerFullscreen.displayName = "MediaPlayerFullscreen"
@@ -0,0 +1,111 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { MediaPlayerRoot, MediaPlayerRootProps } from "./media-player-root"
5
+ import { VideoJSAdapter, VideoSource, TextTrack } from "../adapters/videojs-adapter"
6
+ import { MediaPlayerControls } from "./media-player-controls"
7
+ import { MediaPlayerLoading } from "./media-player-loading"
8
+ import { MediaPlayerError } from "./media-player-error"
9
+ import { useMediaSelector } from "../context/media-context"
10
+ import { cn } from "../lib/utils"
11
+ import "../styles/videojs.css" // Import Video.js styles
12
+
13
+ export interface MediaPlayerHLSProps extends Omit<MediaPlayerRootProps, "children"> {
14
+ sources: VideoSource[]
15
+ poster?: string
16
+ textTracks?: TextTrack[]
17
+ showControls?: boolean
18
+ customControls?: React.ReactNode
19
+ hlsConfig?: any
20
+ enableQualitySelector?: boolean
21
+ enableSubtitles?: boolean
22
+ containerClassName?: string
23
+ videoClassName?: string
24
+ onPlayerReady?: (player: any) => void
25
+ onPlayerError?: (error: any) => void
26
+ }
27
+
28
+ function MediaPlayerHLSContent({
29
+ sources,
30
+ poster,
31
+ textTracks = [],
32
+ showControls = true,
33
+ customControls,
34
+ hlsConfig,
35
+ enableQualitySelector = true,
36
+ enableSubtitles = true,
37
+ containerClassName,
38
+ videoClassName,
39
+ onPlayerReady,
40
+ onPlayerError
41
+ }: MediaPlayerHLSProps) {
42
+ const isLoading = useMediaSelector(state => state.isLoading)
43
+ const isBuffering = useMediaSelector(state => state.isBuffering)
44
+ const error = useMediaSelector(state => state.error)
45
+
46
+ return (
47
+ <div className={cn("relative h-full w-full", containerClassName)}>
48
+ {/* Video.js Adapter */}
49
+ <VideoJSAdapter
50
+ sources={sources}
51
+ poster={poster}
52
+ textTracks={textTracks}
53
+ controls={true} // Enable Video.js controls
54
+ autoplay={false}
55
+ muted={true} // Muted for autoplay policy
56
+ preload="auto"
57
+ className={videoClassName}
58
+ hlsConfig={hlsConfig}
59
+ enableQualitySelector={enableQualitySelector}
60
+ enableSubtitles={enableSubtitles}
61
+ onReady={onPlayerReady}
62
+ onError={onPlayerError}
63
+ />
64
+
65
+ {/* Loading state */}
66
+ {(isLoading || isBuffering) && <MediaPlayerLoading />}
67
+
68
+ {/* Error state */}
69
+ {error && <MediaPlayerError>{error}</MediaPlayerError>}
70
+ </div>
71
+ )
72
+ }
73
+
74
+ export function MediaPlayerHLS(props: MediaPlayerHLSProps) {
75
+ const {
76
+ sources,
77
+ poster,
78
+ textTracks,
79
+ showControls,
80
+ customControls,
81
+ hlsConfig,
82
+ enableQualitySelector,
83
+ enableSubtitles,
84
+ containerClassName,
85
+ videoClassName,
86
+ onPlayerReady,
87
+ onPlayerError,
88
+ ...rootProps
89
+ } = props
90
+
91
+ return (
92
+ <MediaPlayerRoot {...rootProps}>
93
+ <MediaPlayerHLSContent
94
+ sources={sources}
95
+ poster={poster}
96
+ textTracks={textTracks}
97
+ showControls={showControls}
98
+ customControls={customControls}
99
+ hlsConfig={hlsConfig}
100
+ enableQualitySelector={enableQualitySelector}
101
+ enableSubtitles={enableSubtitles}
102
+ containerClassName={containerClassName}
103
+ videoClassName={videoClassName}
104
+ onPlayerReady={onPlayerReady}
105
+ onPlayerError={onPlayerError}
106
+ />
107
+ </MediaPlayerRoot>
108
+ )
109
+ }
110
+
111
+ MediaPlayerHLS.displayName = "MediaPlayerHLS"
@@ -0,0 +1,76 @@
1
+ "use client"
2
+
3
+ import { Loader2Icon } from "lucide-react"
4
+ import React from "react"
5
+ import { useMediaSelector } from "../context/media-context"
6
+ import { cn } from "../lib/utils"
7
+
8
+ export interface MediaPlayerLoadingProps {
9
+ className?: string
10
+ delayMs?: number
11
+ asChild?: boolean
12
+ children?: React.ReactNode
13
+ }
14
+
15
+ export function MediaPlayerLoading({
16
+ className,
17
+ delayMs = 300,
18
+ asChild = false,
19
+ children,
20
+ ...props
21
+ }: MediaPlayerLoadingProps) {
22
+ const isLoading = useMediaSelector((state) => state.isLoading)
23
+ const [shouldRender, setShouldRender] = React.useState(false)
24
+ const timeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined)
25
+
26
+ React.useEffect(() => {
27
+ if (timeoutRef.current) {
28
+ clearTimeout(timeoutRef.current)
29
+ timeoutRef.current = undefined
30
+ }
31
+
32
+ if (isLoading) {
33
+ if (delayMs > 0) {
34
+ timeoutRef.current = setTimeout(() => {
35
+ setShouldRender(true)
36
+ timeoutRef.current = undefined
37
+ }, delayMs)
38
+ } else {
39
+ setShouldRender(true)
40
+ }
41
+ } else {
42
+ setShouldRender(false)
43
+ }
44
+
45
+ return () => {
46
+ if (timeoutRef.current) {
47
+ clearTimeout(timeoutRef.current)
48
+ timeoutRef.current = undefined
49
+ }
50
+ }
51
+ }, [isLoading, delayMs])
52
+
53
+ if (!shouldRender) return null
54
+
55
+ const loadingProps = {
56
+ role: "status",
57
+ "aria-live": "polite" as const,
58
+ "data-slot": "media-player-loading",
59
+ className: cn(
60
+ "fade-in-0 zoom-in-95 pointer-events-none absolute inset-0 z-50 flex animate-in items-center justify-center duration-200",
61
+ className
62
+ ),
63
+ ...props
64
+ }
65
+
66
+ const content = <Loader2Icon className="size-8 animate-spin text-white" />
67
+
68
+ if (asChild) {
69
+ const child = React.Children.only(children) as React.ReactElement
70
+ return React.cloneElement(child, loadingProps as any, content)
71
+ }
72
+
73
+ return <div {...loadingProps}>{content}</div>
74
+ }
75
+
76
+ MediaPlayerLoading.displayName = "MediaPlayerLoading"