@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,235 @@
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 EnhancedSeekBar = 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
+ // Throttling refs
23
+ const seekTimeoutRef = useRef<NodeJS.Timeout>()
24
+ const lastSeekTime = useRef<number>(0)
25
+ const rafId = useRef<number>()
26
+
27
+ // Memoize expensive calculations
28
+ const progress = useMemo(() => duration > 0 ? (currentTime / duration) * 100 : 0, [currentTime, duration])
29
+
30
+ // Calculate buffered ranges with memoization
31
+ const bufferedRanges = useMemo(() => {
32
+ const ranges = []
33
+ if (buffered && duration > 0) {
34
+ for (let i = 0; i < buffered.length; i++) {
35
+ const start = (buffered.start(i) / duration) * 100
36
+ const end = (buffered.end(i) / duration) * 100
37
+ ranges.push({ start, end })
38
+ }
39
+ }
40
+ return ranges
41
+ }, [buffered, duration])
42
+
43
+ // Optimized value change handler with throttling and RAF
44
+ const handleValueChange = useCallback(([value]: number[]) => {
45
+ const time = ((value ?? 0) / 100) * duration
46
+
47
+ setPreviewTime(time)
48
+
49
+ // Cancel any pending RAF
50
+ if (rafId.current) {
51
+ cancelAnimationFrame(rafId.current)
52
+ }
53
+
54
+ if (isDragging) {
55
+ // Clear previous timeout
56
+ if (seekTimeoutRef.current) {
57
+ clearTimeout(seekTimeoutRef.current)
58
+ }
59
+
60
+ // Use RAF for smooth updates during dragging
61
+ rafId.current = requestAnimationFrame(() => {
62
+ const now = performance.now()
63
+ const timeDiff = now - lastSeekTime.current
64
+ const timeJump = Math.abs(time - currentTime)
65
+
66
+ // Only seek if enough time has passed or if this is a large jump
67
+ if (timeDiff > 16 || timeJump > 0.5) { // 60fps = 16ms per frame
68
+ startTransition(() => {
69
+ onSeek(time)
70
+ })
71
+ lastSeekTime.current = now
72
+ } else {
73
+ // Defer the seek
74
+ seekTimeoutRef.current = setTimeout(() => {
75
+ startTransition(() => {
76
+ onSeek(time)
77
+ })
78
+ lastSeekTime.current = performance.now()
79
+ }, 16)
80
+ }
81
+ })
82
+ }
83
+ }, [duration, isDragging, onSeek, currentTime, startTransition])
84
+
85
+ const handleValueCommit = useCallback(([value]: number[]) => {
86
+ const time = ((value ?? 0) / 100) * duration
87
+
88
+ // Cancel any pending RAF
89
+ if (rafId.current) {
90
+ cancelAnimationFrame(rafId.current)
91
+ }
92
+
93
+ // Clear any pending seeks
94
+ if (seekTimeoutRef.current) {
95
+ clearTimeout(seekTimeoutRef.current)
96
+ }
97
+
98
+ startTransition(() => {
99
+ onSeek(time)
100
+ })
101
+ setIsDragging(false)
102
+ }, [duration, onSeek, startTransition])
103
+
104
+ const handlePointerDown = useCallback(() => {
105
+ setIsDragging(true)
106
+ lastSeekTime.current = performance.now()
107
+ }, [])
108
+
109
+ // Cleanup on unmount
110
+ useMemo(() => {
111
+ return () => {
112
+ if (rafId.current) {
113
+ cancelAnimationFrame(rafId.current)
114
+ }
115
+ if (seekTimeoutRef.current) {
116
+ clearTimeout(seekTimeoutRef.current)
117
+ }
118
+ }
119
+ }, [])
120
+
121
+ // Memoize tooltip content to prevent re-renders
122
+ const tooltipContent = useMemo(() => {
123
+ return (
124
+ <div className={cn(
125
+ "rounded bg-black/80 px-2 py-1 text-sm text-white",
126
+ "fade-in-0 zoom-in-95 animate-in"
127
+ )}>
128
+ {formatTime(deferredPreviewTime)}
129
+ </div>
130
+ )
131
+ }, [deferredPreviewTime])
132
+
133
+ return (
134
+ <div
135
+ ref={ref}
136
+ className={cn(
137
+ "media-container relative w-full text-[color:var(--media-text)]",
138
+ "touch-manipulation contain-layout",
139
+ className
140
+ )}
141
+ onMouseEnter={() => setIsHovering(true)}
142
+ onMouseLeave={() => setIsHovering(false)}>
143
+ <Tooltip.Provider>
144
+ <Tooltip.Root>
145
+ <Tooltip.Trigger asChild>
146
+ <div className="relative animate-enter">
147
+ <Slider.Root
148
+ className={cn(
149
+ "group relative flex w-full touch-none select-none items-center",
150
+ "media-md:h-4 media-sm:h-3 media-xs:h-2",
151
+ "focus-ring cursor-pointer",
152
+ isPending && "opacity-75"
153
+ )}
154
+ value={[progress]}
155
+ max={100}
156
+ step={0.1}
157
+ onValueChange={handleValueChange}
158
+ onValueCommit={handleValueCommit}
159
+ onPointerDown={handlePointerDown}
160
+ aria-label="Seek to position">
161
+ <Slider.Track
162
+ className={cn(
163
+ "relative grow overflow-hidden rounded-full",
164
+ "bg-[color:var(--media-control-bg)]/40 backdrop-blur-sm",
165
+ "border border-[color:var(--media-border)]/20",
166
+ "shadow-inner transition-all duration-300 ease-out",
167
+ "h-1 group-focus-within:h-1.5 group-hover:h-1.5",
168
+ "group-hover:bg-[color:var(--media-control-bg)]/60",
169
+ "group-hover:border-[color:var(--media-border)]/40"
170
+ )}>
171
+ {/* Buffered ranges - optimized rendering */}
172
+ {bufferedRanges.map((range, index) => (
173
+ <div
174
+ key={`${range.start}-${range.end}-${index}`}
175
+ className={cn(
176
+ "absolute h-full rounded-full transition-all duration-500",
177
+ "bg-[color:var(--color-media-buffered)] shadow-inner",
178
+ "opacity-60 group-hover:opacity-80"
179
+ )}
180
+ style={{
181
+ left: `${range.start}%`,
182
+ width: `${range.end - range.start}%`,
183
+ willChange: 'width, left'
184
+ }}
185
+ />
186
+ ))}
187
+
188
+ {/* Progress range with gradient */}
189
+ <Slider.Range
190
+ className={cn(
191
+ "absolute h-full rounded-full transition-all duration-300",
192
+ "bg-gradient-to-r from-[color:var(--color-media-progress)] to-[color:var(--color-brand-accent)]",
193
+ "shadow-[color:var(--color-brand-primary)]/20 shadow-lg",
194
+ "group-hover:shadow-xl",
195
+ isDragging && "transition-none"
196
+ )}
197
+ />
198
+ </Slider.Track>
199
+
200
+ <Slider.Thumb
201
+ className={cn(
202
+ "block size-4 rounded-full border-white/20 bg-white",
203
+ "shadow-lg hover:shadow-xl",
204
+ "opacity-0 group-hover:opacity-100",
205
+ "hover:scale-110 hover:bg-white/95 active:scale-95",
206
+ "focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-white/50",
207
+ "focus:ring-offset-2 focus:ring-offset-black/20",
208
+ "transition-all duration-300 ease-out",
209
+ isDragging && "scale-110 opacity-100",
210
+ isPending && "animate-pulse"
211
+ )}
212
+ aria-label="Seek"
213
+ />
214
+ </Slider.Root>
215
+ </div>
216
+ </Tooltip.Trigger>
217
+
218
+ {(isDragging || deferredHovering) && (
219
+ <Tooltip.Portal>
220
+ <Tooltip.Content
221
+ sideOffset={10}
222
+ className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
223
+ {tooltipContent}
224
+ <Tooltip.Arrow className="fill-black/80" />
225
+ </Tooltip.Content>
226
+ </Tooltip.Portal>
227
+ )}
228
+ </Tooltip.Root>
229
+ </Tooltip.Provider>
230
+ </div>
231
+ )
232
+ }
233
+ )
234
+
235
+ EnhancedSeekBar.displayName = "EnhancedSeekBar"
@@ -0,0 +1,60 @@
1
+ "use client"
2
+
3
+ import * as Tooltip from "@radix-ui/react-tooltip"
4
+ import { Maximize, Minimize } from "lucide-react"
5
+ import { forwardRef } from "react"
6
+ import { cn } from "../../lib/utils"
7
+ import type { FullscreenButtonProps } from "../../types/index"
8
+
9
+ export const FullscreenButton = forwardRef<HTMLButtonElement, FullscreenButtonProps>(
10
+ ({ isFullscreen, onToggle, disabled = false, size = "lg", className }, ref) => {
11
+ const Icon = isFullscreen ? Minimize : Maximize
12
+ const buttonSizes: Record<"md" | "lg" | "xl", string> = {
13
+ md: "h-11 w-11",
14
+ lg: "h-12 w-12",
15
+ xl: "h-14 w-14"
16
+ }
17
+ const iconSizes: Record<"md" | "lg" | "xl", string> = {
18
+ md: "h-5 w-5",
19
+ lg: "h-6 w-6",
20
+ xl: "h-7 w-7"
21
+ }
22
+
23
+ return (
24
+ <Tooltip.Provider>
25
+ <Tooltip.Root>
26
+ <Tooltip.Trigger asChild>
27
+ <button
28
+ ref={ref}
29
+ type="button"
30
+ onClick={onToggle}
31
+ disabled={disabled}
32
+ className={cn(
33
+ "inline-flex items-center justify-center rounded-lg bg-transparent p-0 text-white transition-all hover:text-cyan-700 active:scale-95",
34
+ "focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-black/20",
35
+ "disabled:cursor-not-allowed disabled:opacity-50",
36
+ buttonSizes[size],
37
+ className
38
+ )}
39
+ aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}>
40
+ <Icon className={cn(iconSizes[size])} />
41
+ </button>
42
+ </Tooltip.Trigger>
43
+ <Tooltip.Portal>
44
+ <Tooltip.Content
45
+ className={cn(
46
+ "rounded bg-black/80 px-2 py-1 text-sm text-white",
47
+ "fade-in-0 zoom-in-95 animate-in"
48
+ )}
49
+ sideOffset={5}>
50
+ {isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
51
+ <Tooltip.Arrow className="fill-black/80" />
52
+ </Tooltip.Content>
53
+ </Tooltip.Portal>
54
+ </Tooltip.Root>
55
+ </Tooltip.Provider>
56
+ )
57
+ }
58
+ )
59
+
60
+ FullscreenButton.displayName = "FullscreenButton"
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Media Resource Preloader using React 19's resource preloading APIs
3
+ * Implements intelligent preloading strategies based on user behavior and network conditions
4
+ */
5
+
6
+ import React, { useEffect, useRef, useMemo } from 'react';
7
+ import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';
8
+ import { MediaSource } from '../../types';
9
+
10
+ interface MediaResourcePreloaderProps {
11
+ /** Media sources to preload */
12
+ sources: MediaSource[];
13
+ /** Enable intelligent preloading based on user behavior */
14
+ enableIntelligentPreloading?: boolean;
15
+ /** Network-aware preloading */
16
+ adaptToNetworkConditions?: boolean;
17
+ /** Preload priority level */
18
+ priority?: 'high' | 'low' | 'auto';
19
+ /** Custom preloading strategy */
20
+ strategy?: 'conservative' | 'aggressive' | 'adaptive';
21
+ }
22
+
23
+ interface PreloadingConfig {
24
+ dnsPrefetch: string[];
25
+ preconnect: string[];
26
+ preload: Array<{ url: string; type: string }>;
27
+ preinit: Array<{ url: string; type: string }>;
28
+ }
29
+
30
+ export const MediaResourcePreloader: React.FC<MediaResourcePreloaderProps> = ({
31
+ sources,
32
+ enableIntelligentPreloading = true,
33
+ adaptToNetworkConditions = true,
34
+ priority = 'auto',
35
+ strategy = 'adaptive',
36
+ }) => {
37
+ const networkInfoRef = useRef<NetworkInformation | null>(null);
38
+ const userBehaviorRef = useRef<UserBehaviorTracker | null>(null);
39
+ const preloadingHistoryRef = useRef<Map<string, number>>(new Map());
40
+
41
+ // Analyze sources and determine preloading configuration
42
+ const preloadingConfig = useMemo((): PreloadingConfig => {
43
+ const config: PreloadingConfig = {
44
+ dnsPrefetch: [],
45
+ preconnect: [],
46
+ preload: [],
47
+ preinit: [],
48
+ };
49
+
50
+ sources.forEach((source) => {
51
+ const url = new URL(source.src);
52
+ const domain = url.origin;
53
+
54
+ // DNS prefetch for CDN domains
55
+ if (isCDNDomain(domain)) {
56
+ config.dnsPrefetch.push(domain);
57
+ }
58
+
59
+ // Preconnect to media domains
60
+ if (shouldPreconnect(source.type, strategy)) {
61
+ config.preconnect.push(domain);
62
+ }
63
+
64
+ // Preload media files based on strategy
65
+ if (shouldPreload(source, priority, strategy)) {
66
+ config.preload.push({
67
+ url: source.src,
68
+ type: getPreloadType(source.type),
69
+ });
70
+ }
71
+
72
+ // Preinit critical resources
73
+ if (shouldPreinit(source, strategy)) {
74
+ config.preinit.push({
75
+ url: source.src,
76
+ type: 'script',
77
+ });
78
+ }
79
+ });
80
+
81
+ return config;
82
+ }, [sources, priority, strategy]);
83
+
84
+ // Initialize network monitoring
85
+ useEffect(() => {
86
+ if (adaptToNetworkConditions && 'connection' in navigator) {
87
+ networkInfoRef.current = (navigator as any).connection;
88
+
89
+ const handleNetworkChange = () => {
90
+ adjustPreloadingStrategy();
91
+ };
92
+
93
+ networkInfoRef.current.addEventListener('change', handleNetworkChange);
94
+
95
+ return () => {
96
+ networkInfoRef.current?.removeEventListener('change', handleNetworkChange);
97
+ };
98
+ }
99
+ }, [adaptToNetworkConditions]);
100
+
101
+ // Initialize user behavior tracking
102
+ useEffect(() => {
103
+ if (enableIntelligentPreloading) {
104
+ userBehaviorRef.current = new UserBehaviorTracker();
105
+ userBehaviorRef.current.startTracking();
106
+
107
+ return () => {
108
+ userBehaviorRef.current?.stopTracking();
109
+ };
110
+ }
111
+ }, [enableIntelligentPreloading]);
112
+
113
+ // Apply DNS prefetching
114
+ useEffect(() => {
115
+ preloadingConfig.dnsPrefetch.forEach((domain) => {
116
+ prefetchDNS(domain);
117
+ });
118
+ }, [preloadingConfig.dnsPrefetch]);
119
+
120
+ // Apply preconnections
121
+ useEffect(() => {
122
+ preloadingConfig.preconnect.forEach((origin) => {
123
+ preconnect(origin, { crossOrigin: 'anonymous' });
124
+ });
125
+ }, [preloadingConfig.preconnect]);
126
+
127
+ // Apply preloading
128
+ useEffect(() => {
129
+ preloadingConfig.preload.forEach(({ url, type }) => {
130
+ const crossOrigin = shouldUseCrossOrigin(url) ? 'anonymous' : undefined;
131
+
132
+ preload(url, {
133
+ as: type,
134
+ crossOrigin,
135
+ integrity: getAssetIntegrity(url),
136
+ priority,
137
+ });
138
+ });
139
+ }, [preloadingConfig.preload, priority]);
140
+
141
+ // Apply preinit for critical resources
142
+ useEffect(() => {
143
+ preloadingConfig.preinit.forEach(({ url, type }) => {
144
+ const crossOrigin = shouldUseCrossOrigin(url) ? 'anonymous' : undefined;
145
+
146
+ preinit(url, {
147
+ as: type,
148
+ crossOrigin,
149
+ });
150
+ });
151
+ }, [preloadingConfig.preinit]);
152
+
153
+ // Intelligent preloading based on user behavior
154
+ useEffect(() => {
155
+ if (!enableIntelligentPreloading || !userBehaviorRef.current) return;
156
+
157
+ const handleUserBehavior = (behavior: UserBehaviorData) => {
158
+ if (behavior.isLikelyToInteract) {
159
+ preloadLikelyContent(behavior.predictedSources);
160
+ }
161
+ };
162
+
163
+ userBehaviorRef.current.onBehaviorChange(handleUserBehavior);
164
+ }, [enableIntelligentPreloading]);
165
+
166
+ // Helper functions
167
+ function isCDNDomain(domain: string): boolean {
168
+ const cdnDomains = [
169
+ 'cdn.jsdelivr.net',
170
+ 'cdnjs.cloudflare.com',
171
+ 'unpkg.com',
172
+ 'cloudflare.com',
173
+ 'fastly.net',
174
+ 'akamaized.net',
175
+ ];
176
+
177
+ return cdnDomains.some((cdn) => domain.includes(cdn));
178
+ }
179
+
180
+ function shouldPreconnect(mediaType: string, strategy: string): boolean {
181
+ if (strategy === 'aggressive') return true;
182
+ if (strategy === 'conservative') return ['hls', 'm3u8'].includes(mediaType);
183
+ return ['hls', 'm3u8', 'mp4'].includes(mediaType);
184
+ }
185
+
186
+ function shouldPreload(source: MediaSource, priority: string, strategy: string): boolean {
187
+ // Check if already preloaded recently
188
+ const lastPreload = preloadingHistoryRef.current.get(source.src);
189
+ if (lastPreload && Date.now() - lastPreload < 300000) return false; // 5 minutes
190
+
191
+ // Strategy-based decision
192
+ if (strategy === 'conservative') {
193
+ return priority === 'high' && source.type === 'hls';
194
+ }
195
+
196
+ if (strategy === 'aggressive') {
197
+ return true;
198
+ }
199
+
200
+ // Adaptive strategy
201
+ return shouldAdaptivePreload(source, priority);
202
+ }
203
+
204
+ function shouldAdaptivePreload(source: MediaSource, priority: string): boolean {
205
+ // Consider network conditions
206
+ if (networkInfoRef.current) {
207
+ const { effectiveType, saveData } = networkInfoRef.current;
208
+
209
+ if (saveData) return false;
210
+ if (effectiveType === 'slow-2g' || effectiveType === '2g') {
211
+ return priority === 'high' && source.type === 'hls';
212
+ }
213
+ if (effectiveType === '3g') {
214
+ return ['hls', 'm3u8'].includes(source.type);
215
+ }
216
+ }
217
+
218
+ // Consider user behavior
219
+ if (userBehaviorRef.current) {
220
+ const behavior = userBehaviorRef.current.getCurrentBehavior();
221
+ if (behavior.engagementScore > 0.7) {
222
+ return true;
223
+ }
224
+ }
225
+
226
+ return priority !== 'low';
227
+ }
228
+
229
+ function shouldPreinit(source: MediaSource, strategy: string): boolean {
230
+ return strategy === 'aggressive' && source.type === 'hls';
231
+ }
232
+
233
+ function getPreloadType(mediaType: string): string {
234
+ switch (mediaType) {
235
+ case 'hls':
236
+ case 'm3u8':
237
+ return 'fetch';
238
+ case 'mp4':
239
+ case 'webm':
240
+ return 'video';
241
+ case 'mp3':
242
+ case 'wav':
243
+ return 'audio';
244
+ default:
245
+ return 'fetch';
246
+ }
247
+ }
248
+
249
+ function shouldUseCrossOrigin(url: string): boolean {
250
+ return !url.includes(window.location.origin);
251
+ }
252
+
253
+ function getAssetIntegrity(url: string): string | undefined {
254
+ // Return integrity hash if available for the asset
255
+ // This would typically come from a build process or asset manifest
256
+ return undefined;
257
+ }
258
+
259
+ function adjustPreloadingStrategy(): void {
260
+ // Adjust preloading based on network conditions
261
+ if (!networkInfoRef.current) return;
262
+
263
+ const { effectiveType, downlink } = networkInfoRef.current;
264
+
265
+ // This would update the preloading configuration
266
+ // Implementation depends on specific requirements
267
+ }
268
+
269
+ function preloadLikelyContent(sources: string[]): void {
270
+ sources.forEach((url) => {
271
+ // Mark as preloaded
272
+ preloadingHistoryRef.current.set(url, Date.now());
273
+
274
+ // Preload the content
275
+ preload(url, {
276
+ as: 'fetch',
277
+ crossOrigin: 'anonymous',
278
+ priority: 'high',
279
+ });
280
+ });
281
+ }
282
+
283
+ // This component doesn't render anything visible
284
+ return null;
285
+ };
286
+
287
+ /**
288
+ * User behavior tracker for intelligent preloading
289
+ */
290
+ class UserBehaviorTracker {
291
+ private behaviorData: UserBehaviorData = {
292
+ isLikelyToInteract: false,
293
+ engagementScore: 0,
294
+ predictedSources: [],
295
+ interactionHistory: [],
296
+ };
297
+
298
+ private listeners: ((behavior: UserBehaviorData) => void)[] = [];
299
+ private mouseMoveTimer: number | null = null;
300
+
301
+ public startTracking(): void {
302
+ // Track mouse movements as interaction indicators
303
+ document.addEventListener('mousemove', this.handleMouseMove);
304
+ document.addEventListener('scroll', this.handleScroll);
305
+ document.addEventListener('keydown', this.handleKeydown);
306
+
307
+ // Track focus events
308
+ document.addEventListener('focusin', this.handleFocus);
309
+ document.addEventListener('visibilitychange', this.handleVisibilityChange);
310
+ }
311
+
312
+ public stopTracking(): void {
313
+ document.removeEventListener('mousemove', this.handleMouseMove);
314
+ document.removeEventListener('scroll', this.handleScroll);
315
+ document.removeEventListener('keydown', this.handleKeydown);
316
+ document.removeEventListener('focusin', this.handleFocus);
317
+ document.removeEventListener('visibilitychange', this.handleVisibilityChange);
318
+
319
+ if (this.mouseMoveTimer) {
320
+ clearTimeout(this.mouseMoveTimer);
321
+ }
322
+ }
323
+
324
+ public onBehaviorChange(listener: (behavior: UserBehaviorData) => void): void {
325
+ this.listeners.push(listener);
326
+ }
327
+
328
+ public getCurrentBehavior(): UserBehaviorData {
329
+ return this.behaviorData;
330
+ }
331
+
332
+ private handleMouseMove = (): void => {
333
+ if (this.mouseMoveTimer) return;
334
+
335
+ this.mouseMoveTimer = window.setTimeout(() => {
336
+ this.updateEngagementScore(0.1);
337
+ this.mouseMoveTimer = null;
338
+ }, 100);
339
+ };
340
+
341
+ private handleScroll = (): void => {
342
+ this.updateEngagementScore(0.2);
343
+ };
344
+
345
+ private handleKeydown = (): void => {
346
+ this.updateEngagementScore(0.3);
347
+ this.behaviorData.isLikelyToInteract = true;
348
+ };
349
+
350
+ private handleFocus = (): void => {
351
+ this.updateEngagementScore(0.4);
352
+ this.behaviorData.isLikelyToInteract = true;
353
+ };
354
+
355
+ private handleVisibilityChange = (): void => {
356
+ if (!document.hidden) {
357
+ this.updateEngagementScore(0.5);
358
+ this.behaviorData.isLikelyToInteract = true;
359
+ }
360
+ };
361
+
362
+ private updateEngagementScore(increment: number): void {
363
+ this.behaviorData.engagementScore = Math.min(
364
+ 1,
365
+ this.behaviorData.engagementScore + increment
366
+ );
367
+
368
+ // Decay engagement score over time
369
+ setTimeout(() => {
370
+ this.behaviorData.engagementScore = Math.max(
371
+ 0,
372
+ this.behaviorData.engagementScore - increment * 0.5
373
+ );
374
+ this.notifyListeners();
375
+ }, 30000); // 30 seconds decay
376
+
377
+ this.notifyListeners();
378
+ }
379
+
380
+ private notifyListeners(): void {
381
+ this.listeners.forEach((listener) => listener(this.behaviorData));
382
+ }
383
+ }
384
+
385
+ interface UserBehaviorData {
386
+ isLikelyToInteract: boolean;
387
+ engagementScore: number;
388
+ predictedSources: string[];
389
+ interactionHistory: Array<{
390
+ type: string;
391
+ timestamp: number;
392
+ }>;
393
+ }
394
+
395
+ export default MediaResourcePreloader;