@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,358 @@
1
+ /**
2
+ * Intelligent Preloading Hook
3
+ * Combines user behavior analysis with network conditions for optimal preloading
4
+ */
5
+
6
+ import { useEffect, useRef, useCallback, useMemo } from 'react';
7
+ import { MediaSource } from '../types';
8
+
9
+ interface IntelligentPreloadingOptions {
10
+ /** Enable user behavior tracking */
11
+ trackUserBehavior?: boolean;
12
+ /** Adapt to network conditions */
13
+ adaptToNetwork?: boolean;
14
+ /** Preload strategy */
15
+ strategy?: 'conservative' | 'balanced' | 'aggressive';
16
+ /** Maximum number of items to preload */
17
+ maxPreloadItems?: number;
18
+ /** Preload radius for playlist items */
19
+ preloadRadius?: number;
20
+ }
21
+
22
+ interface UserBehaviorData {
23
+ mouseMovements: number;
24
+ scrollEvents: number;
25
+ keyPresses: number;
26
+ focusEvents: number;
27
+ timeOnPage: number;
28
+ engagementScore: number;
29
+ lastInteraction: number;
30
+ }
31
+
32
+ interface PreloadingDecision {
33
+ shouldPreload: boolean;
34
+ priority: 'high' | 'medium' | 'low';
35
+ reason: string;
36
+ }
37
+
38
+ export const useIntelligentPreloading = (
39
+ sources: MediaSource[],
40
+ options: IntelligentPreloadingOptions = {}
41
+ ) => {
42
+ const {
43
+ trackUserBehavior = true,
44
+ adaptToNetwork = true,
45
+ strategy = 'balanced',
46
+ maxPreloadItems = 3,
47
+ preloadRadius = 2,
48
+ } = options;
49
+
50
+ const userBehaviorRef = useRef<UserBehaviorData>({
51
+ mouseMovements: 0,
52
+ scrollEvents: 0,
53
+ keyPresses: 0,
54
+ focusEvents: 0,
55
+ timeOnPage: 0,
56
+ engagementScore: 0,
57
+ lastInteraction: Date.now(),
58
+ });
59
+
60
+ const networkInfoRef = useRef<NetworkInformation | null>(null);
61
+ const preloadedItemsRef = useRef<Set<string>>(new Set());
62
+ const preloadQueueRef = useRef<Array<{ source: MediaSource; priority: number }>>([]);
63
+
64
+ // Calculate user engagement score
65
+ const calculateEngagementScore = useCallback((): number => {
66
+ const behavior = userBehaviorRef.current;
67
+ const timeFactor = Math.min(behavior.timeOnPage / 60000, 1); // Cap at 1 minute
68
+ const interactionFactor = Math.min(
69
+ (behavior.mouseMovements + behavior.scrollEvents + behavior.keyPresses) / 100,
70
+ 1
71
+ );
72
+
73
+ return (timeFactor * 0.4 + interactionFactor * 0.6) * 100;
74
+ }, []);
75
+
76
+ // Update user behavior tracking
77
+ const updateUserBehavior = useCallback((type: keyof UserBehaviorData, increment = 1) => {
78
+ const behavior = userBehaviorRef.current;
79
+ behavior[type] = typeof behavior[type] === 'number' ? behavior[type] + increment : Date.now();
80
+ behavior.engagementScore = calculateEngagementScore();
81
+ behavior.lastInteraction = Date.now();
82
+ }, [calculateEngagementScore]);
83
+
84
+ // Get current network conditions
85
+ const getNetworkConditions = useCallback(() => {
86
+ if (!networkInfoRef.current || !adaptToNetwork) {
87
+ return { type: 'unknown', downlink: 10, effectiveType: '4g', saveData: false };
88
+ }
89
+
90
+ const network = networkInfoRef.current;
91
+ return {
92
+ type: network.type,
93
+ downlink: network.downlink || 10,
94
+ effectiveType: network.effectiveType || '4g',
95
+ saveData: network.saveData || false,
96
+ rtt: network.rtt || 100,
97
+ };
98
+ }, [adaptToNetwork]);
99
+
100
+ // Determine if a source should be preloaded
101
+ const shouldPreloadSource = useCallback((
102
+ source: MediaSource,
103
+ index: number,
104
+ currentIndex: number = 0
105
+ ): PreloadingDecision => {
106
+ // Skip if already preloaded
107
+ if (preloadedItemsRef.current.has(source.src)) {
108
+ return { shouldPreload: false, priority: 'low', reason: 'Already preloaded' };
109
+ }
110
+
111
+ // Check if within preload radius
112
+ const distance = Math.abs(index - currentIndex);
113
+ if (distance > preloadRadius) {
114
+ return { shouldPreload: false, priority: 'low', reason: 'Outside preload radius' };
115
+ }
116
+
117
+ const behavior = userBehaviorRef.current;
118
+ const network = getNetworkConditions();
119
+ const engagementScore = behavior.engagementScore;
120
+ const timeSinceInteraction = Date.now() - behavior.lastInteraction;
121
+
122
+ // Network-based decisions
123
+ if (network.saveData) {
124
+ return { shouldPreload: false, priority: 'low', reason: 'Data saver mode' };
125
+ }
126
+
127
+ // Strategy-based filtering
128
+ let shouldPreload = false;
129
+ let priority: 'high' | 'medium' | 'low' = 'medium';
130
+ let reason = '';
131
+
132
+ switch (strategy) {
133
+ case 'conservative':
134
+ shouldPreload = engagementScore > 70 && network.effectiveType !== '2g' && distance === 1;
135
+ priority = shouldPreload ? 'medium' : 'low';
136
+ reason = shouldPreload ? 'High engagement and optimal network' : 'Conservative strategy';
137
+ break;
138
+
139
+ case 'aggressive':
140
+ shouldPreload = engagementScore > 30 && network.effectiveType !== 'slow-2g';
141
+ priority = engagementScore > 70 ? 'high' : engagementScore > 50 ? 'medium' : 'low';
142
+ reason = shouldPreload ? 'Aggressive preloading' : 'Low engagement';
143
+ break;
144
+
145
+ case 'balanced':
146
+ default:
147
+ const networkMultiplier = network.effectiveType === '4g' ? 1 :
148
+ network.effectiveType === '3g' ? 0.7 : 0.3;
149
+ const adjustedScore = engagementScore * networkMultiplier;
150
+
151
+ shouldPreload = adjustedScore > 40 && distance <= 2;
152
+ priority = adjustedScore > 70 ? 'high' : adjustedScore > 55 ? 'medium' : 'low';
153
+ reason = shouldPreload ? `Balanced: score ${adjustedScore.toFixed(1)}` : 'Low adjusted score';
154
+ break;
155
+ }
156
+
157
+ // Additional heuristics
158
+ if (shouldPreload) {
159
+ // Prioritize HLS and adaptive streaming
160
+ if (source.type === 'hls' || source.type === 'm3u8') {
161
+ priority = priority === 'low' ? 'medium' : 'high';
162
+ }
163
+
164
+ // Boost priority for recent user interaction
165
+ if (timeSinceInteraction < 5000) { // Within 5 seconds
166
+ priority = 'high';
167
+ reason += ' + Recent interaction';
168
+ }
169
+
170
+ // Consider content type (video gets higher priority than audio)
171
+ if (source.type === 'mp4' || source.type === 'webm') {
172
+ priority = priority === 'low' ? 'medium' : priority;
173
+ }
174
+ }
175
+
176
+ return { shouldPreload, priority, reason };
177
+ }, [strategy, preloadRadius, getNetworkConditions, calculateEngagementScore]);
178
+
179
+ // Preload a specific source
180
+ const preloadSource = useCallback(async (source: MediaSource, priority: number) => {
181
+ try {
182
+ // Create a preload link element
183
+ const link = document.createElement('link');
184
+ link.rel = 'preload';
185
+ link.href = source.src;
186
+
187
+ // Determine the correct 'as' attribute
188
+ switch (source.type) {
189
+ case 'hls':
190
+ case 'm3u8':
191
+ link.as = 'fetch';
192
+ break;
193
+ case 'mp4':
194
+ case 'webm':
195
+ link.as = 'video';
196
+ break;
197
+ case 'mp3':
198
+ case 'wav':
199
+ case 'ogg':
200
+ link.as = 'audio';
201
+ break;
202
+ default:
203
+ link.as = 'fetch';
204
+ }
205
+
206
+ link.crossOrigin = 'anonymous';
207
+
208
+ // Add to document head
209
+ document.head.appendChild(link);
210
+
211
+ // Mark as preloaded
212
+ preloadedItemsRef.current.add(source.src);
213
+
214
+ console.debug(`Preloaded: ${source.src} (${priority})`);
215
+
216
+ // Cleanup after 30 seconds to prevent memory issues
217
+ setTimeout(() => {
218
+ if (document.head.contains(link)) {
219
+ document.head.removeChild(link);
220
+ }
221
+ }, 30000);
222
+
223
+ } catch (error) {
224
+ console.warn('Failed to preload source:', source.src, error);
225
+ }
226
+ }, []);
227
+
228
+ // Process preload queue
229
+ const processPreloadQueue = useCallback(() => {
230
+ if (preloadQueueRef.current.length === 0) return;
231
+
232
+ // Sort by priority (higher number = higher priority)
233
+ const sortedQueue = [...preloadQueueRef.current].sort((a, b) => b.priority - a.priority);
234
+
235
+ // Take top items based on maxPreloadItems
236
+ const itemsToPreload = sortedQueue.slice(0, maxPreloadItems);
237
+
238
+ // Clear queue
239
+ preloadQueueRef.current = [];
240
+
241
+ // Preload items
242
+ itemsToPreload.forEach(({ source, priority }) => {
243
+ preloadSource(source, priority);
244
+ });
245
+ }, [maxPreloadItems, preloadSource]);
246
+
247
+ // Update preloading decisions
248
+ const updatePreloading = useCallback((currentIndex: number = 0) => {
249
+ preloadQueueRef.current = [];
250
+
251
+ sources.forEach((source, index) => {
252
+ const decision = shouldPreloadSource(source, index, currentIndex);
253
+
254
+ if (decision.shouldPreload) {
255
+ const priority = decision.priority === 'high' ? 3 :
256
+ decision.priority === 'medium' ? 2 : 1;
257
+
258
+ preloadQueueRef.current.push({ source, priority });
259
+ }
260
+ });
261
+
262
+ processPreloadQueue();
263
+ }, [sources, shouldPreloadSource, processPreloadQueue]);
264
+
265
+ // Set up user behavior tracking
266
+ useEffect(() => {
267
+ if (!trackUserBehavior) return;
268
+
269
+ const handleMouseMove = () => updateUserBehavior('mouseMovements');
270
+ const handleScroll = () => updateUserBehavior('scrollEvents');
271
+ const handleKeyPress = () => updateUserBehavior('keyPresses');
272
+ const handleFocus = () => updateUserBehavior('focusEvents');
273
+
274
+ // Add event listeners
275
+ document.addEventListener('mousemove', handleMouseMove, { passive: true });
276
+ document.addEventListener('scroll', handleScroll, { passive: true });
277
+ document.addEventListener('keydown', handleKeyPress, { passive: true });
278
+ document.addEventListener('focusin', handleFocus, { passive: true });
279
+
280
+ // Update time on page every second
281
+ const timeInterval = setInterval(() => {
282
+ updateUserBehavior('timeOnPage', 1);
283
+ }, 1000);
284
+
285
+ return () => {
286
+ document.removeEventListener('mousemove', handleMouseMove);
287
+ document.removeEventListener('scroll', handleScroll);
288
+ document.removeEventListener('keydown', handleKeyPress);
289
+ document.removeEventListener('focusin', handleFocus);
290
+ clearInterval(timeInterval);
291
+ };
292
+ }, [trackUserBehavior, updateUserBehavior]);
293
+
294
+ // Set up network monitoring
295
+ useEffect(() => {
296
+ if (!adaptToNetwork || !('connection' in navigator)) return;
297
+
298
+ const network = (navigator as any).connection;
299
+ networkInfoRef.current = network;
300
+
301
+ const handleNetworkChange = () => {
302
+ // Re-evaluate preloading when network conditions change
303
+ updatePreloading();
304
+ };
305
+
306
+ network.addEventListener('change', handleNetworkChange);
307
+
308
+ return () => {
309
+ network.removeEventListener('change', handleNetworkChange);
310
+ };
311
+ }, [adaptToNetwork, updatePreloading]);
312
+
313
+ // Initial preloading
314
+ useEffect(() => {
315
+ updatePreloading();
316
+ }, [updatePreloading]);
317
+
318
+ // Memoize return values
319
+ const controls = useMemo(() => ({
320
+ /**
321
+ * Manually trigger preloading for specific index
322
+ */
323
+ preloadAtIndex: (index: number) => {
324
+ updatePreloading(index);
325
+ },
326
+
327
+ /**
328
+ * Get current user behavior data
329
+ */
330
+ getUserBehavior: () => ({ ...userBehaviorRef.current }),
331
+
332
+ /**
333
+ * Get current network conditions
334
+ */
335
+ getNetworkConditions: getNetworkConditions,
336
+
337
+ /**
338
+ * Clear preloaded items cache
339
+ */
340
+ clearCache: () => {
341
+ preloadedItemsRef.current.clear();
342
+ },
343
+
344
+ /**
345
+ * Get preloading statistics
346
+ */
347
+ getStats: () => ({
348
+ preloadedItems: preloadedItemsRef.current.size,
349
+ engagementScore: userBehaviorRef.current.engagementScore,
350
+ queueLength: preloadQueueRef.current.length,
351
+ maxPreloadItems,
352
+ }),
353
+ }), [updatePreloading, getNetworkConditions, maxPreloadItems]);
354
+
355
+ return controls;
356
+ };
357
+
358
+ export default useIntelligentPreloading;
@@ -0,0 +1,115 @@
1
+ "use client"
2
+
3
+ import { useCallback, useEffect } from "react"
4
+ import type { MediaPlayerControls, MediaPlayerState } from "../types/index"
5
+
6
+ interface UseKeyboardControlsOptions {
7
+ enabled?: boolean
8
+ state: MediaPlayerState
9
+ controls: MediaPlayerControls
10
+ seekStep?: number // seconds to seek forward/backward
11
+ volumeStep?: number // volume step (0-1)
12
+ }
13
+
14
+ export function useKeyboardControls({
15
+ enabled = true,
16
+ state,
17
+ controls,
18
+ seekStep = 10,
19
+ volumeStep = 0.1
20
+ }: UseKeyboardControlsOptions) {
21
+ const handleKeyDown = (event: KeyboardEvent) => {
22
+ if (!enabled) return
23
+
24
+ // Don't handle keyboard events if user is typing in an input
25
+ const target = event.target as HTMLElement
26
+ if (
27
+ target.tagName === "INPUT" ||
28
+ target.tagName === "TEXTAREA" ||
29
+ target.contentEditable === "true"
30
+ ) {
31
+ return
32
+ }
33
+
34
+ switch (event.code) {
35
+ case "Space":
36
+ case "KeyK":
37
+ event.preventDefault()
38
+ controls.toggle()
39
+ break
40
+
41
+ case "KeyM":
42
+ event.preventDefault()
43
+ controls.toggleMute()
44
+ break
45
+
46
+ case "KeyF":
47
+ event.preventDefault()
48
+ controls.toggleFullscreen()
49
+ break
50
+
51
+ case "ArrowLeft":
52
+ event.preventDefault()
53
+ controls.seek(Math.max(0, state.currentTime - seekStep))
54
+ break
55
+
56
+ case "ArrowRight":
57
+ event.preventDefault()
58
+ controls.seek(Math.min(state.duration, state.currentTime + seekStep))
59
+ break
60
+
61
+ case "ArrowUp":
62
+ event.preventDefault()
63
+ controls.setVolume(Math.min(1, state.volume + volumeStep))
64
+ break
65
+
66
+ case "ArrowDown":
67
+ event.preventDefault()
68
+ controls.setVolume(Math.max(0, state.volume - volumeStep))
69
+ break
70
+
71
+ case "Digit0":
72
+ case "Digit1":
73
+ case "Digit2":
74
+ case "Digit3":
75
+ case "Digit4":
76
+ case "Digit5":
77
+ case "Digit6":
78
+ case "Digit7":
79
+ case "Digit8":
80
+ case "Digit9":
81
+ event.preventDefault()
82
+ const digit = parseInt(event.code.replace("Digit", ""), 10)
83
+ const percentage = digit / 10
84
+ controls.seek(state.duration * percentage)
85
+ break
86
+
87
+ case "Home":
88
+ event.preventDefault()
89
+ controls.seek(0)
90
+ break
91
+
92
+ case "End":
93
+ event.preventDefault()
94
+ controls.seek(state.duration)
95
+ break
96
+
97
+ default:
98
+ // Don't prevent default for unhandled keys
99
+ break
100
+ }
101
+ }
102
+
103
+ useEffect(() => {
104
+ if (!enabled) return
105
+
106
+ document.addEventListener("keydown", handleKeyDown)
107
+ return () => {
108
+ document.removeEventListener("keydown", handleKeyDown)
109
+ }
110
+ }, [enabled, handleKeyDown])
111
+
112
+ return {
113
+ handleKeyDown
114
+ }
115
+ }
@@ -0,0 +1,175 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import type { MediaActionType, MediaState } from "../context/media-context"
5
+ import type { OptimisticMediaAction, MediaActionState, EnhancedMediaStore } from "../context/media-store"
6
+ import { MediaActionTypes } from "../context/media-context"
7
+
8
+ // Action payload types
9
+ export interface MediaActionPayloads {
10
+ [MediaActionTypes.PLAY]: void
11
+ [MediaActionTypes.PAUSE]: void
12
+ [MediaActionTypes.SEEK]: number
13
+ [MediaActionTypes.SET_VOLUME]: number
14
+ [MediaActionTypes.MUTE]: void
15
+ [MediaActionTypes.UNMUTE]: void
16
+ [MediaActionTypes.TOGGLE_MUTE]: void
17
+ [MediaActionTypes.SET_DURATION]: number
18
+ [MediaActionTypes.SET_CURRENT_TIME]: number
19
+ [MediaActionTypes.SET_BUFFERED]: TimeRanges | null
20
+ [MediaActionTypes.SET_LOADING]: boolean
21
+ [MediaActionTypes.SET_ERROR]: string | null
22
+ [MediaActionTypes.ENTER_FULLSCREEN]: void
23
+ [MediaActionTypes.EXIT_FULLSCREEN]: void
24
+ [MediaActionTypes.ENTER_PIP]: void
25
+ [MediaActionTypes.EXIT_PIP]: void
26
+ [MediaActionTypes.SET_PLAYBACK_RATE]: number
27
+ [MediaActionTypes.SET_SUBTITLE_TRACK]: TextTrack | null
28
+ [MediaActionTypes.SET_RENDITION]: string | null
29
+ [MediaActionTypes.TOGGLE_SUBTITLES]: void
30
+ [MediaActionTypes.PREVIEW_TIME]: number | null
31
+ [MediaActionTypes.CLEAR_PREVIEW]: void
32
+ }
33
+
34
+ // Enhanced action creator that returns a Promise
35
+ type OptimisticActionCreator<T extends keyof MediaActionPayloads> = MediaActionPayloads[T] extends void
36
+ ? (optimistic?: boolean) => Promise<void>
37
+ : (payload: MediaActionPayloads[T], optimistic?: boolean) => Promise<void>
38
+
39
+ // Action creator factory
40
+ function createOptimisticAction<T extends keyof MediaActionPayloads>(
41
+ type: T,
42
+ store: EnhancedMediaStore
43
+ ): OptimisticActionCreator<T> {
44
+ return ((payload?: any, optimistic = false) => {
45
+ const action: OptimisticMediaAction = {
46
+ type,
47
+ detail: payload,
48
+ optimistic,
49
+ timestamp: Date.now()
50
+ }
51
+
52
+ if (optimistic) {
53
+ return store.dispatchOptimistic(action)
54
+ } else {
55
+ store.dispatch(action)
56
+ return Promise.resolve()
57
+ }
58
+ }) as OptimisticActionCreator<T>
59
+ }
60
+
61
+ // React 19 useActionState integration
62
+ export function useMediaActionState(store: EnhancedMediaStore) {
63
+ const [actionState, setActionState] = React.useState<MediaActionState>(store.getActionState())
64
+
65
+ // Subscribe to action state changes
66
+ React.useEffect(() => {
67
+ const unsubscribe = store.subscribe(() => {
68
+ const newActionState = store.getActionState()
69
+ setActionState(current =>
70
+ JSON.stringify(current) !== JSON.stringify(newActionState)
71
+ ? newActionState
72
+ : current
73
+ )
74
+ })
75
+ return unsubscribe
76
+ }, [store])
77
+
78
+ return actionState
79
+ }
80
+
81
+ // Media actions hook with React 19 optimistic updates
82
+ export function useMediaActions(store: EnhancedMediaStore) {
83
+ // Create action creators with optimistic support
84
+ const actions = React.useMemo(() => ({
85
+ // Playback actions
86
+ play: createOptimisticAction(MediaActionTypes.PLAY, store),
87
+ pause: createOptimisticAction(MediaActionTypes.PAUSE, store),
88
+ seek: createOptimisticAction(MediaActionTypes.SEEK, store),
89
+
90
+ // Volume actions
91
+ setVolume: createOptimisticAction(MediaActionTypes.SET_VOLUME, store),
92
+ mute: createOptimisticAction(MediaActionTypes.MUTE, store),
93
+ unmute: createOptimisticAction(MediaActionTypes.UNMUTE, store),
94
+ toggleMute: createOptimisticAction(MediaActionTypes.TOGGLE_MUTE, store),
95
+
96
+ // Media state actions
97
+ setDuration: createOptimisticAction(MediaActionTypes.SET_DURATION, store),
98
+ setCurrentTime: createOptimisticAction(MediaActionTypes.SET_CURRENT_TIME, store),
99
+ setBuffered: createOptimisticAction(MediaActionTypes.SET_BUFFERED, store),
100
+ setLoading: createOptimisticAction(MediaActionTypes.SET_LOADING, store),
101
+ setError: createOptimisticAction(MediaActionTypes.SET_ERROR, store),
102
+
103
+ // Display actions
104
+ enterFullscreen: createOptimisticAction(MediaActionTypes.ENTER_FULLSCREEN, store),
105
+ exitFullscreen: createOptimisticAction(MediaActionTypes.EXIT_FULLSCREEN, store),
106
+ enterPiP: createOptimisticAction(MediaActionTypes.ENTER_PIP, store),
107
+ exitPiP: createOptimisticAction(MediaActionTypes.EXIT_PIP, store),
108
+
109
+ // Advanced actions
110
+ setPlaybackRate: createOptimisticAction(MediaActionTypes.SET_PLAYBACK_RATE, store),
111
+ setSubtitleTrack: createOptimisticAction(MediaActionTypes.SET_SUBTITLE_TRACK, store),
112
+ setRendition: createOptimisticAction(MediaActionTypes.SET_RENDITION, store),
113
+ toggleSubtitles: createOptimisticAction(MediaActionTypes.TOGGLE_SUBTITLES, store),
114
+
115
+ // Preview actions
116
+ previewTime: createOptimisticAction(MediaActionTypes.PREVIEW_TIME, store),
117
+ clearPreview: createOptimisticAction(MediaActionTypes.CLEAR_PREVIEW, store)
118
+ }), [store])
119
+
120
+ // Get action state for loading/error handling
121
+ const actionState = useMediaActionState(store)
122
+
123
+ // Convenience methods that commonly use optimistic updates
124
+ const optimisticActions = React.useMemo(() => ({
125
+ // These actions benefit from immediate UI feedback
126
+ togglePlayPause: async (isPlaying: boolean) => {
127
+ if (isPlaying) {
128
+ await actions.pause(true) // optimistic
129
+ } else {
130
+ await actions.play(true) // optimistic
131
+ }
132
+ },
133
+
134
+ seekWithPreview: async (time: number) => {
135
+ // Immediate preview update, then actual seek
136
+ await actions.previewTime(time, true)
137
+ await actions.seek(time, true)
138
+ },
139
+
140
+ adjustVolume: async (delta: number, currentVolume: number) => {
141
+ const newVolume = Math.max(0, Math.min(1, currentVolume + delta))
142
+ await actions.setVolume(newVolume, true)
143
+ },
144
+
145
+ toggleFullscreenOptimistic: async (isFullscreen: boolean) => {
146
+ if (isFullscreen) {
147
+ await actions.exitFullscreen(true)
148
+ } else {
149
+ await actions.enterFullscreen(true)
150
+ }
151
+ }
152
+ }), [actions])
153
+
154
+ // Retry failed action
155
+ const retryLastAction = React.useCallback(async () => {
156
+ const { lastAction } = actionState
157
+ if (lastAction && actionState.error) {
158
+ await store.dispatchOptimistic({
159
+ ...lastAction,
160
+ retryCount: (lastAction.retryCount || 0) + 1
161
+ })
162
+ }
163
+ }, [store, actionState])
164
+
165
+ return {
166
+ ...actions,
167
+ ...optimisticActions,
168
+ actionState,
169
+ retryLastAction,
170
+ isLoading: actionState.pending,
171
+ hasError: !!actionState.error,
172
+ errorMessage: actionState.error,
173
+ retryCount: actionState.retryCount
174
+ }
175
+ }