@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,107 @@
1
+ "use client"
2
+
3
+ import { Pause, Play } from "lucide-react"
4
+ import { forwardRef } from "react"
5
+ import { cn } from "../../lib/utils"
6
+ import type { PlayButtonProps } from "../../types/index"
7
+
8
+ const sizeVariants = {
9
+ sm: { button: "size-8", icon: 16 },
10
+ md: { button: "size-10", icon: 20 },
11
+ lg: { button: "size-12", icon: 24 },
12
+ xl: { button: "size-16", icon: 32 }
13
+ } as const
14
+
15
+ export const PlayButton = forwardRef<HTMLButtonElement, PlayButtonProps>(
16
+ ({ isPlaying, onToggle, disabled = false, size = "lg", className }, ref) => {
17
+ const sizeConfig = sizeVariants[size] || sizeVariants.lg
18
+
19
+ return (
20
+ <button
21
+ ref={ref}
22
+ type="button"
23
+ onClick={onToggle}
24
+ disabled={disabled}
25
+ data-state={isPlaying ? "playing" : "paused"}
26
+ className={cn(
27
+ // Base styles with new design system
28
+ "group relative inline-flex items-center justify-center",
29
+ "glass-strong media-control rounded-full contain-layout",
30
+ "focus-ring touch-manipulation",
31
+ sizeConfig.button,
32
+
33
+ // Color transitions with OKLCH
34
+ "text-[color:var(--media-text)] transition-all duration-300",
35
+ "bg-[color:var(--media-control-bg)] backdrop-blur-sm",
36
+ "border border-[color:var(--media-border)]/30",
37
+
38
+ // Interactive states
39
+ "hover:bg-[color:var(--media-control-hover)]",
40
+ "hover:border-[color:var(--media-border)]/50",
41
+ "hover:shadow-[color:var(--media-bg)]/20 hover:shadow-lg",
42
+ "hover:-translate-y-0.5 hover:scale-105",
43
+
44
+ "active:bg-[color:var(--media-control-active)]",
45
+ "active:translate-y-0 active:scale-100",
46
+
47
+ // Focus state
48
+ "focus-visible:ring-2 focus-visible:ring-[color:var(--media-focus)]",
49
+ "focus-visible:ring-offset-2 focus-visible:ring-offset-[color:var(--media-bg)]",
50
+
51
+ // Disabled state
52
+ "disabled:cursor-not-allowed disabled:opacity-50",
53
+ "disabled:hover:translate-y-0 disabled:hover:scale-100",
54
+ "disabled:hover:bg-[color:var(--media-control-bg)]",
55
+
56
+ className
57
+ )}
58
+ aria-label={isPlaying ? "Pause media" : "Play media"}
59
+ aria-pressed={isPlaying}>
60
+ {/* Background glow effect */}
61
+ <div
62
+ className={cn(
63
+ "absolute inset-0 rounded-full opacity-0 transition-opacity duration-300",
64
+ "bg-gradient-to-br from-[color:var(--color-media-play)] to-[color:var(--color-media-pause)]",
65
+ "blur-sm group-hover:opacity-20",
66
+ isPlaying && "opacity-10"
67
+ )}
68
+ />
69
+
70
+ {/* Icon with enhanced styling */}
71
+ {isPlaying ? (
72
+ <Pause
73
+ size={sizeConfig.icon}
74
+ className={cn(
75
+ "relative z-10 fill-current transition-all duration-200",
76
+ "text-[color:var(--color-media-pause)]",
77
+ "group-hover:scale-110 group-active:scale-95",
78
+ "drop-shadow-sm"
79
+ )}
80
+ />
81
+ ) : (
82
+ <Play
83
+ size={sizeConfig.icon}
84
+ className={cn(
85
+ "relative z-10 fill-current transition-all duration-200",
86
+ "ml-0.5 text-[color:var(--color-media-play)]",
87
+ "group-hover:scale-110 group-active:scale-95",
88
+ "drop-shadow-sm"
89
+ )}
90
+ />
91
+ )}
92
+
93
+ {/* Ripple effect */}
94
+ <div
95
+ className={cn(
96
+ "absolute inset-0 rounded-full opacity-0",
97
+ "scale-75 bg-[color:var(--media-text)]/10",
98
+ "group-active:scale-100 group-active:opacity-100",
99
+ "transition-all duration-150 ease-out"
100
+ )}
101
+ />
102
+ </button>
103
+ )
104
+ }
105
+ )
106
+
107
+ PlayButton.displayName = "PlayButton"
@@ -0,0 +1,161 @@
1
+ "use client"
2
+
3
+ import * as Slider from "@radix-ui/react-slider"
4
+ import * as Tooltip from "@radix-ui/react-tooltip"
5
+ import { forwardRef, useCallback, useState, useMemo, useTransition, useDeferredValue, useRef } from "react"
6
+ import { formatTime } from "../../lib/format-time"
7
+ import { cn } from "../../lib/utils"
8
+ import type { SeekBarProps } from "../../types/index"
9
+
10
+ export const SeekBar = forwardRef<HTMLDivElement, SeekBarProps>(
11
+ ({ currentTime, duration, buffered, onSeek, size = "sm", className }, ref) => {
12
+ // React 19 concurrent features
13
+ const [isPending, startTransition] = useTransition()
14
+ const [isDragging, setIsDragging] = useState(false)
15
+ const [previewTime, setPreviewTime] = useState(0)
16
+ const [isHovering, setIsHovering] = useState(false)
17
+
18
+ // Defer non-critical updates for better performance
19
+ const deferredHovering = useDeferredValue(isHovering)
20
+ const deferredPreviewTime = useDeferredValue(previewTime)
21
+
22
+ // Memoize expensive calculations
23
+ const progress = useMemo(() => duration > 0 ? (currentTime / duration) * 100 : 0, [currentTime, duration])
24
+
25
+ // Calculate buffered ranges with memoization
26
+ const bufferedRanges = useMemo(() => {
27
+ const ranges = []
28
+ if (buffered && duration > 0) {
29
+ for (let i = 0; i < buffered.length; i++) {
30
+ const start = (buffered.start(i) / duration) * 100
31
+ const end = (buffered.end(i) / duration) * 100
32
+ ranges.push({ start, end })
33
+ }
34
+ }
35
+ return ranges
36
+ }, [buffered, duration])
37
+
38
+ // Throttled seek handler with React 19 transitions
39
+ const seekTimeoutRef = useRef<NodeJS.Timeout>()
40
+ const lastSeekTime = useRef<number>(0)
41
+
42
+ const handleValueChange = ([value]: number[]) => {
43
+ const time = ((value ?? 0) / 100) * duration
44
+ setPreviewTime(time)
45
+ if (isDragging) {
46
+ onSeek(time)
47
+ }
48
+ }
49
+
50
+ const handleValueCommit = ([value]: number[]) => {
51
+ const time = ((value ?? 0) / 100) * duration
52
+ onSeek(time)
53
+ setIsDragging(false)
54
+ }
55
+
56
+ const handlePointerDown = () => {
57
+ setIsDragging(true)
58
+ }
59
+
60
+ return (
61
+ <div
62
+ ref={ref}
63
+ className={cn(
64
+ "media-container relative w-full text-[color:var(--media-text)]",
65
+ "touch-manipulation contain-layout",
66
+ className
67
+ )}
68
+ onMouseEnter={() => setIsHovering(true)}
69
+ onMouseLeave={() => setIsHovering(false)}>
70
+ <Tooltip.Provider>
71
+ <Tooltip.Root>
72
+ <Tooltip.Trigger asChild>
73
+ <div className="relative animate-enter">
74
+ <Slider.Root
75
+ className={cn(
76
+ "group relative flex w-full touch-none select-none items-center",
77
+ "media-md:h-4 media-sm:h-3 media-xs:h-2",
78
+ "focus-ring cursor-pointer"
79
+ )}
80
+ value={[progress]}
81
+ max={100}
82
+ step={0.1}
83
+ onValueChange={handleValueChange}
84
+ onValueCommit={handleValueCommit}
85
+ onPointerDown={handlePointerDown}
86
+ aria-label="Seek to position">
87
+ <Slider.Track
88
+ className={cn(
89
+ "relative grow overflow-hidden rounded-full",
90
+ "bg-[color:var(--media-control-bg)]/40 backdrop-blur-sm",
91
+ "border border-[color:var(--media-border)]/20",
92
+ "shadow-inner transition-all duration-300 ease-out",
93
+ "h-1 group-focus-within:h-1.5 group-hover:h-1.5",
94
+ "group-hover:bg-[color:var(--media-control-bg)]/60",
95
+ "group-hover:border-[color:var(--media-border)]/40"
96
+ )}>
97
+ {/* Buffered ranges */}
98
+ {bufferedRanges.map((range, index) => (
99
+ <div
100
+ key={index}
101
+ className={cn(
102
+ "absolute h-full rounded-full transition-all duration-500",
103
+ "bg-[color:var(--color-media-buffered)] shadow-inner",
104
+ "opacity-60 group-hover:opacity-80"
105
+ )}
106
+ style={{
107
+ left: `${range.start}%`,
108
+ width: `${range.end - range.start}%`
109
+ }}
110
+ />
111
+ ))}
112
+
113
+ {/* Progress range with gradient */}
114
+ <Slider.Range
115
+ className={cn(
116
+ "absolute h-full rounded-full transition-all duration-300",
117
+ "bg-gradient-to-r from-[color:var(--color-media-progress)] to-[color:var(--color-brand-accent)]",
118
+ "shadow-[color:var(--color-brand-primary)]/20 shadow-lg",
119
+ "group-hover:shadow-xl"
120
+ )}
121
+ />
122
+ </Slider.Track>
123
+
124
+ <Slider.Thumb
125
+ className={cn(
126
+ "block size-4 rounded-full border-white/20 bg-white",
127
+ "shadow-lg hover:shadow-xl",
128
+ "opacity-0 group-hover:opacity-100",
129
+ "hover:scale-110 hover:bg-white/95 active:scale-95",
130
+ "focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-white/50",
131
+ "focus:ring-offset-2 focus:ring-offset-black/20",
132
+ "transition-all duration-300 ease-out",
133
+ isDragging && "opacity-100"
134
+ )}
135
+ aria-label="Seek"
136
+ />
137
+ </Slider.Root>
138
+ </div>
139
+ </Tooltip.Trigger>
140
+
141
+ {isDragging && (
142
+ <Tooltip.Portal>
143
+ <Tooltip.Content
144
+ className={cn(
145
+ "rounded bg-black/80 px-2 py-1 text-sm text-white",
146
+ "fade-in-0 zoom-in-95 animate-in"
147
+ )}
148
+ sideOffset={10}>
149
+ {formatTime(previewTime)}
150
+ <Tooltip.Arrow className="fill-black/80" />
151
+ </Tooltip.Content>
152
+ </Tooltip.Portal>
153
+ )}
154
+ </Tooltip.Root>
155
+ </Tooltip.Provider>
156
+ </div>
157
+ )
158
+ }
159
+ )
160
+
161
+ SeekBar.displayName = "SeekBar"
@@ -0,0 +1,40 @@
1
+ "use client"
2
+
3
+ import { forwardRef } from "react"
4
+ import { formatTime } from "../../lib/format-time"
5
+ import { cn } from "../../lib/utils"
6
+ import type { TimeDisplayProps } from "../../types/index"
7
+
8
+ export const TimeDisplay = forwardRef<HTMLDivElement, TimeDisplayProps>(
9
+ ({ currentTime, duration, showRemaining = false, size = "sm", className }, ref) => {
10
+ const current = formatTime(currentTime)
11
+ const total = formatTime(duration)
12
+ const remaining = formatTime(duration - currentTime)
13
+
14
+ const sizeClasses: Record<"sm" | "md" | "lg" | "xl", string> = {
15
+ sm: "text-xs",
16
+ md: "text-sm",
17
+ lg: "text-base",
18
+ xl: "text-lg"
19
+ }
20
+
21
+ return (
22
+ <div
23
+ ref={ref}
24
+ className={cn(
25
+ "flex items-center font-mono text-white text-xs",
26
+ "rounded-lg backdrop-blur-sm",
27
+ "px-3 py-1.5",
28
+ sizeClasses[size],
29
+ "select-none",
30
+ className
31
+ )}>
32
+ <span className="tabular-nums">{current}</span>
33
+ <span className="mx-1 text-white">/</span>
34
+ <span className="tabular-nums">{showRemaining ? `-${remaining}` : total}</span>
35
+ </div>
36
+ )
37
+ }
38
+ )
39
+
40
+ TimeDisplay.displayName = "TimeDisplay"
@@ -0,0 +1,143 @@
1
+ "use client"
2
+
3
+ import * as Slider from "@radix-ui/react-slider"
4
+ import * as Tooltip from "@radix-ui/react-tooltip"
5
+ import { Volume1, Volume2, VolumeX } from "lucide-react"
6
+ import { forwardRef } from "react"
7
+ import { cn } from "../../lib/utils"
8
+ import type { VolumeControlProps } from "../../types/index"
9
+
10
+ const sizeConfig = {
11
+ md: {
12
+ button: "size-10",
13
+ icon: 18,
14
+ slider: "h-6",
15
+ thumb: "size-3",
16
+ track: "h-1"
17
+ },
18
+ lg: {
19
+ button: "size-12",
20
+ icon: 22,
21
+ slider: "h-7",
22
+ thumb: "size-4",
23
+ track: "h-1.5"
24
+ },
25
+ xl: {
26
+ button: "size-14",
27
+ icon: 26,
28
+ slider: "h-8",
29
+ thumb: "size-5",
30
+ track: "h-2"
31
+ }
32
+ } as const
33
+
34
+ export const VolumeControl = forwardRef<HTMLDivElement, VolumeControlProps>(
35
+ ({ volume, isMuted, onVolumeChange, onToggleMute, size = "lg", className }, ref) => {
36
+ const getVolumeIcon = () => {
37
+ if (isMuted || volume === 0) {
38
+ return VolumeX
39
+ } else if (volume < 0.5) {
40
+ return Volume1
41
+ } else {
42
+ return Volume2
43
+ }
44
+ }
45
+
46
+ const VolumeIcon = getVolumeIcon()
47
+ const buttonSizes: Record<"md" | "lg" | "xl", string> = {
48
+ md: "h-11 w-11",
49
+ lg: "h-12 w-12",
50
+ xl: "h-14 w-14"
51
+ }
52
+ const iconSizes: Record<"md" | "lg" | "xl", string> = {
53
+ md: "h-5 w-5",
54
+ lg: "h-6 w-6",
55
+ xl: "h-7 w-7"
56
+ }
57
+ const sliderHeight: Record<"md" | "lg" | "xl", string> = {
58
+ md: "h-6",
59
+ lg: "h-7",
60
+ xl: "h-8"
61
+ }
62
+ const thumbSize: Record<"md" | "lg" | "xl", string> = {
63
+ md: "h-4 w-4",
64
+ lg: "h-4 w-4",
65
+ xl: "h-5 w-5"
66
+ }
67
+ const trackHeight: Record<"md" | "lg" | "xl", string> = {
68
+ md: "h-1.5",
69
+ lg: "h-2",
70
+ xl: "h-2.5"
71
+ }
72
+
73
+ return (
74
+ <div ref={ref} className={cn("flex items-center space-x-3 text-white", className)}>
75
+ <Tooltip.Provider>
76
+ <Tooltip.Root>
77
+ <Tooltip.Trigger asChild>
78
+ <button
79
+ type="button"
80
+ onClick={onToggleMute}
81
+ className={cn(
82
+ "inline-flex items-center justify-center rounded-lg bg-transparent p-0 text-white transition-all hover:text-cyan-700 active:scale-95",
83
+ "focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-black/20",
84
+ buttonSizes[size]
85
+ )}
86
+ aria-label={isMuted ? "Unmute" : "Mute"}>
87
+ <VolumeIcon className={cn(iconSizes[size])} />
88
+ </button>
89
+ </Tooltip.Trigger>
90
+ <Tooltip.Portal>
91
+ <Tooltip.Content
92
+ className={cn(
93
+ "rounded bg-black/80 px-2 py-1 text-sm text-white",
94
+ "fade-in-0 zoom-in-95 animate-in"
95
+ )}
96
+ sideOffset={5}>
97
+ {isMuted ? "Unmute" : "Mute"}
98
+ <Tooltip.Arrow className="fill-black/80" />
99
+ </Tooltip.Content>
100
+ </Tooltip.Portal>
101
+ </Tooltip.Root>
102
+ </Tooltip.Provider>
103
+
104
+ <div className="w-32">
105
+ <Slider.Root
106
+ className={cn(
107
+ "group relative flex w-full touch-none select-none items-center",
108
+ sliderHeight[size]
109
+ )}
110
+ value={[isMuted ? 0 : volume]}
111
+ max={1}
112
+ step={0.01}
113
+ onValueChange={([value]) => onVolumeChange(value ?? 0)}
114
+ aria-label="Volume">
115
+ <Slider.Track
116
+ className={cn(
117
+ "relative grow rounded-full bg-white/20 shadow-inner transition-all duration-300 group-hover:bg-white/30",
118
+ "border border-white/10",
119
+ trackHeight[size]
120
+ )}>
121
+ <Slider.Range className="absolute h-full rounded-full bg-white shadow-sm" />
122
+ </Slider.Track>
123
+ <Slider.Thumb
124
+ className={cn(
125
+ "block rounded-full border-2 border-white/20 bg-white shadow-lg",
126
+ "transition-all duration-300 ease-out hover:scale-110 hover:bg-white/95 active:scale-95",
127
+ "focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-black/20",
128
+ thumbSize[size]
129
+ )}
130
+ aria-label="Volume"
131
+ />
132
+ </Slider.Root>
133
+ </div>
134
+
135
+ <span className="w-10 text-right text-sm text-white">
136
+ {Math.round((isMuted ? 0 : volume) * 100)}
137
+ </span>
138
+ </div>
139
+ )
140
+ }
141
+ )
142
+
143
+ VolumeControl.displayName = "VolumeControl"
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Base Media Player - Core unified functionality for video and audio
3
+ * This consolidates the logic from MediaPlayer, VideoPlayer, and AudioPlayer
4
+ */
5
+
6
+ "use client"
7
+
8
+ import { forwardRef, useMemo, useTransition, startTransition } from "react"
9
+ import { useKeyboardControls } from "../../hooks/use-keyboard-controls"
10
+ import { useEnhancedMediaPlayer } from "../../hooks/use-enhanced-media-player"
11
+ import { useMemoryOptimization } from "../../hooks/use-memory-optimization"
12
+ import { EnhancedMediaWrapper } from "../media-suspense"
13
+ import { cn } from "../../lib/utils"
14
+ import type { MediaPlayerProps } from "../../types"
15
+
16
+ export interface BaseMediaPlayerProps extends Omit<MediaPlayerProps, 'poster' | 'aspectRatio' | 'width' | 'height'> {
17
+ // Media type detection
18
+ mediaType?: 'video' | 'audio' | 'auto'
19
+
20
+ // Video-specific props (only used for video content)
21
+ poster?: string
22
+ aspectRatio?: string
23
+ width?: string | number
24
+ height?: string | number
25
+
26
+ // Enhanced features
27
+ enablePerformanceMonitoring?: boolean
28
+ bufferingStrategy?: 'adaptive' | 'conservative' | 'aggressive'
29
+ preloadStrategy?: 'progressive' | 'eager' | 'lazy'
30
+ enableOptimizations?: boolean
31
+ }
32
+
33
+ export const BaseMediaPlayer = forwardRef<HTMLDivElement, BaseMediaPlayerProps>(
34
+ (
35
+ {
36
+ src,
37
+ mediaType = 'auto',
38
+ className,
39
+ children,
40
+ keyboardControls = true,
41
+ showControls = true,
42
+ customControls = false,
43
+ crossOrigin,
44
+ enablePerformanceMonitoring = true,
45
+ bufferingStrategy = "adaptive",
46
+ preloadStrategy = "progressive",
47
+ enableOptimizations = true,
48
+ // Video-specific props
49
+ poster,
50
+ aspectRatio = "16/9",
51
+ width = "100%",
52
+ height = "auto",
53
+ ...options
54
+ },
55
+ ref
56
+ ) => {
57
+ // React 19 concurrent features
58
+ const [isPending] = useTransition()
59
+
60
+ // Auto-detect media type if not specified
61
+ const detectedMediaType = useMemo(() => {
62
+ if (mediaType !== 'auto') return mediaType
63
+
64
+ // Simple detection based on file extension
65
+ if (typeof src === 'string') {
66
+ const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv']
67
+ const audioExtensions = ['.mp3', '.wav', '.ogg', '.aac', '.flac', '.m4a']
68
+
69
+ const extension = src.toLowerCase().split('.').pop()
70
+ if (extension && videoExtensions.some(ext => extension.endsWith(ext))) {
71
+ return 'video'
72
+ }
73
+ if (extension && audioExtensions.some(ext => extension.endsWith(ext))) {
74
+ return 'audio'
75
+ }
76
+ }
77
+
78
+ // Default to video for unknown types
79
+ return 'video'
80
+ }, [src, mediaType])
81
+
82
+ // Enhanced media player with optimizations
83
+ const { state, controls, mediaRef, containerRef, performanceMetrics, isEnhanced } = useEnhancedMediaPlayer({
84
+ src,
85
+ crossOrigin,
86
+ enablePerformanceMonitoring,
87
+ bufferingStrategy,
88
+ preloadStrategy,
89
+ enableTimeUpdateThrottling: true,
90
+ timeUpdateThrottleMs: 100,
91
+ enableSeekPreview: true,
92
+ ...options
93
+ })
94
+
95
+ // Memory optimization
96
+ const { memoryStats, addEventListener } = useMemoryOptimization({
97
+ maxMemoryUsage: 50, // 50MB for media player
98
+ cleanupInterval: 15000, // 15 seconds
99
+ enableGC: true,
100
+ monitorMemory: enableOptimizations
101
+ })
102
+
103
+ // Keyboard controls
104
+ useKeyboardControls({
105
+ enabled: keyboardControls,
106
+ state,
107
+ controls
108
+ })
109
+
110
+ // Determine the media element based on type
111
+ const MediaElement = useMemo(() => {
112
+ const commonProps = {
113
+ ref: mediaRef,
114
+ src,
115
+ className: cn(
116
+ "w-full h-full",
117
+ state.isLoading && "opacity-0",
118
+ !customControls && showControls && "cursor-pointer"
119
+ ),
120
+ onPlay: controls.play,
121
+ onPause: controls.pause,
122
+ onVolumeChange: (e: any) => controls.setVolume(e.target.volume),
123
+ onTimeUpdate: (e: any) => controls.setCurrentTime(e.target.currentTime),
124
+ onSeeked: (e: any) => controls.setCurrentTime(e.target.currentTime),
125
+ crossOrigin
126
+ }
127
+
128
+ if (detectedMediaType === 'audio') {
129
+ return (
130
+ <audio {...commonProps} controls={!customControls && showControls}>
131
+ {src && <source src={typeof src === 'string' ? src : src.src} />}
132
+ Your browser does not support the audio element.
133
+ </audio>
134
+ )
135
+ }
136
+
137
+ // Video (default)
138
+ return (
139
+ <video
140
+ {...commonProps}
141
+ poster={poster}
142
+ muted={state.isMuted}
143
+ playsInline
144
+ controls={!customControls && showControls}
145
+ >
146
+ {Array.isArray(src) ? (
147
+ src.map((source, index) => (
148
+ <source key={index} src={source.src} type={source.type} />
149
+ ))
150
+ ) : src ? (
151
+ <source src={typeof src === 'string' ? src : src.src} />
152
+ ) : null}
153
+ Your browser does not support the video element.
154
+ </video>
155
+ )
156
+ }, [detectedMediaType, src, mediaRef, state, controls, customControls, showControls, poster])
157
+
158
+ // Container styles based on media type
159
+ const containerStyles = useMemo(() => {
160
+ if (detectedMediaType === 'audio') {
161
+ return cn(
162
+ "relative bg-gradient-to-br from-gray-900 to-gray-800 rounded-lg overflow-hidden",
163
+ "flex items-center justify-center p-8",
164
+ className
165
+ )
166
+ }
167
+
168
+ // Video styles
169
+ return cn(
170
+ "relative bg-black rounded-lg overflow-hidden",
171
+ className
172
+ )
173
+ }, [detectedMediaType, className])
174
+
175
+ // Video-specific aspect ratio
176
+ const aspectRatioStyles = useMemo(() => {
177
+ if (detectedMediaType === 'video' && aspectRatio) {
178
+ return {
179
+ aspectRatio,
180
+ width: typeof width === 'number' ? `${width}px` : width,
181
+ height: typeof height === 'number' ? `${height}px` : height
182
+ }
183
+ }
184
+ return {}
185
+ }, [detectedMediaType, aspectRatio, width, height])
186
+
187
+ return (
188
+ <EnhancedMediaWrapper>
189
+ <div
190
+ ref={containerRef}
191
+ className={containerStyles}
192
+ style={aspectRatioStyles}
193
+ data-media-type={detectedMediaType}
194
+ data-enhanced={isEnhanced}
195
+ >
196
+ {MediaElement}
197
+
198
+ {/* Loading state */}
199
+ {state.isLoading && (
200
+ <div className="absolute inset-0 flex items-center justify-center bg-black/50 backdrop-blur-sm">
201
+ <div className="text-center">
202
+ <div className="mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-white"></div>
203
+ <p className="font-medium text-sm text-white">
204
+ {state.isBuffering ? "Buffering..." : "Loading..."}
205
+ </p>
206
+ </div>
207
+ </div>
208
+ )}
209
+
210
+ {/* Error state */}
211
+ {state.error && (
212
+ <div className="absolute inset-0 flex items-center justify-center bg-black/50 backdrop-blur-sm">
213
+ <div className="p-4 text-center text-white">
214
+ <div className="mb-2 text-lg text-red-400">⚠️</div>
215
+ <p className="text-sm">Failed to load media</p>
216
+ <p className="mt-1 text-xs opacity-75">{state.error.message}</p>
217
+ </div>
218
+ </div>
219
+ )}
220
+
221
+ {/* Custom controls or children */}
222
+ {children && (
223
+ <div className="pointer-events-none absolute inset-0">
224
+ {typeof children === "function"
225
+ ? children({ state, controls, mediaType: detectedMediaType })
226
+ : children
227
+ }
228
+ </div>
229
+ )}
230
+
231
+ {/* Performance indicator for development */}
232
+ {process.env.NODE_ENV === 'development' && enablePerformanceMonitoring && (
233
+ <div className="absolute bottom-2 left-2 rounded bg-black/50 px-2 py-1 text-white text-xs">
234
+ <div>FPS: {performanceMetrics.frameRate || 0}</div>
235
+ <div>Memory: {memoryStats.usage.toFixed(1)}MB</div>
236
+ <div>Type: {detectedMediaType}</div>
237
+ {isEnhanced && <div>Enhanced ✓</div>}
238
+ </div>
239
+ )}
240
+ </div>
241
+ </EnhancedMediaWrapper>
242
+ )
243
+ }
244
+ )
245
+
246
+ BaseMediaPlayer.displayName = "BaseMediaPlayer"