@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,394 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import type { MediaActionType } from "./media-context"
5
+ import type { OptimisticMediaAction } from "./media-store"
6
+
7
+ // Error types specific to media operations
8
+ export enum MediaErrorType {
9
+ NETWORK_ERROR = "NETWORK_ERROR",
10
+ PLAYBACK_ERROR = "PLAYBACK_ERROR",
11
+ PERMISSION_ERROR = "PERMISSION_ERROR",
12
+ NOT_SUPPORTED_ERROR = "NOT_SUPPORTED_ERROR",
13
+ TIMEOUT_ERROR = "TIMEOUT_ERROR",
14
+ UNKNOWN_ERROR = "UNKNOWN_ERROR"
15
+ }
16
+
17
+ // Enhanced error interface
18
+ export interface MediaError {
19
+ type: MediaErrorType
20
+ message: string
21
+ action?: OptimisticMediaAction
22
+ timestamp: number
23
+ retryable: boolean
24
+ retryCount: number
25
+ originalError?: Error
26
+ }
27
+
28
+ // Retry configuration
29
+ export interface RetryConfig {
30
+ maxRetries: number
31
+ baseDelay: number // milliseconds
32
+ maxDelay: number // milliseconds
33
+ exponentialBackoff: boolean
34
+ retryableErrors: MediaErrorType[]
35
+ retryableActions: MediaActionType[]
36
+ }
37
+
38
+ // Default retry configuration
39
+ const DEFAULT_RETRY_CONFIG: RetryConfig = {
40
+ maxRetries: 3,
41
+ baseDelay: 1000,
42
+ maxDelay: 10000,
43
+ exponentialBackoff: true,
44
+ retryableErrors: [
45
+ MediaErrorType.NETWORK_ERROR,
46
+ MediaErrorType.TIMEOUT_ERROR
47
+ ],
48
+ retryableActions: [
49
+ "PLAY",
50
+ "PAUSE",
51
+ "SEEK",
52
+ "SET_VOLUME",
53
+ "ENTER_FULLSCREEN",
54
+ "EXIT_FULLSCREEN"
55
+ ]
56
+ }
57
+
58
+ // Error recovery strategies
59
+ export interface ErrorRecoveryStrategy {
60
+ canRecover(error: MediaError): boolean
61
+ recover(error: MediaError): Promise<void>
62
+ }
63
+
64
+ // Network error recovery
65
+ class NetworkErrorRecovery implements ErrorRecoveryStrategy {
66
+ canRecover(error: MediaError): boolean {
67
+ return error.type === MediaErrorType.NETWORK_ERROR && error.retryCount < 3
68
+ }
69
+
70
+ async recover(error: MediaError): Promise<void> {
71
+ // Wait for network to become available
72
+ if (navigator.onLine === false) {
73
+ await new Promise(resolve => {
74
+ const handleOnline = () => {
75
+ window.removeEventListener('online', handleOnline)
76
+ resolve(void 0)
77
+ }
78
+ window.addEventListener('online', handleOnline)
79
+ })
80
+ }
81
+
82
+ // Additional delay for network stability
83
+ await new Promise(resolve => setTimeout(resolve, 2000))
84
+ }
85
+ }
86
+
87
+ // Playback error recovery
88
+ class PlaybackErrorRecovery implements ErrorRecoveryStrategy {
89
+ canRecover(error: MediaError): boolean {
90
+ return error.type === MediaErrorType.PLAYBACK_ERROR && error.retryCount < 2
91
+ }
92
+
93
+ async recover(error: MediaError): Promise<void> {
94
+ // Try to reload the media element
95
+ const mediaElement = document.querySelector('video, audio') as HTMLMediaElement
96
+ if (mediaElement) {
97
+ mediaElement.load()
98
+ await new Promise(resolve => {
99
+ const handleCanPlay = () => {
100
+ mediaElement.removeEventListener('canplay', handleCanPlay)
101
+ resolve(void 0)
102
+ }
103
+ mediaElement.addEventListener('canplay', handleCanPlay)
104
+ })
105
+ }
106
+ }
107
+ }
108
+
109
+ // Permission error recovery (for fullscreen, etc.)
110
+ class PermissionErrorRecovery implements ErrorRecoveryStrategy {
111
+ canRecover(error: MediaError): boolean {
112
+ return error.type === MediaErrorType.PERMISSION_ERROR &&
113
+ error.action?.type === 'ENTER_FULLSCREEN'
114
+ }
115
+
116
+ async recover(error: MediaError): Promise<void> {
117
+ // Request user interaction for fullscreen
118
+ console.warn('Fullscreen requires user interaction')
119
+ // In a real implementation, you might show a message to the user
120
+ }
121
+ }
122
+
123
+ // Error handler class
124
+ export class MediaErrorHandler {
125
+ private config: RetryConfig
126
+ private recoveryStrategies: ErrorRecoveryStrategy[]
127
+ private errorQueue: Map<string, MediaError> = new Map()
128
+ private listeners: Set<(error: MediaError) => void> = new Set()
129
+
130
+ constructor(
131
+ config: Partial<RetryConfig> = {},
132
+ recoveryStrategies: ErrorRecoveryStrategy[] = []
133
+ ) {
134
+ this.config = { ...DEFAULT_RETRY_CONFIG, ...config }
135
+ this.recoveryStrategies = [
136
+ new NetworkErrorRecovery(),
137
+ new PlaybackErrorRecovery(),
138
+ new PermissionErrorRecovery(),
139
+ ...recoveryStrategies
140
+ ]
141
+ }
142
+
143
+ // Create a media error from a generic error
144
+ createMediaError(
145
+ error: unknown,
146
+ action?: OptimisticMediaAction,
147
+ retryCount = 0
148
+ ): MediaError {
149
+ const timestamp = Date.now()
150
+
151
+ if (error instanceof Error) {
152
+ const errorType = this.categorizeError(error, action)
153
+ return {
154
+ type: errorType,
155
+ message: error.message,
156
+ action,
157
+ timestamp,
158
+ retryable: this.isRetryable(errorType, action),
159
+ retryCount,
160
+ originalError: error
161
+ }
162
+ }
163
+
164
+ return {
165
+ type: MediaErrorType.UNKNOWN_ERROR,
166
+ message: typeof error === 'string' ? error : 'Unknown error occurred',
167
+ action,
168
+ timestamp,
169
+ retryable: this.isRetryable(MediaErrorType.UNKNOWN_ERROR, action),
170
+ retryCount
171
+ }
172
+ }
173
+
174
+ // Categorize errors based on error properties and action context
175
+ private categorizeError(error: Error, action?: OptimisticMediaAction): MediaErrorType {
176
+ const message = error.message.toLowerCase()
177
+
178
+ // Network-related errors
179
+ if (message.includes('network') ||
180
+ message.includes('fetch') ||
181
+ message.includes('connection') ||
182
+ error.name === 'NetworkError') {
183
+ return MediaErrorType.NETWORK_ERROR
184
+ }
185
+
186
+ // Permission errors
187
+ if (message.includes('permission') ||
188
+ message.includes('denied') ||
189
+ message.includes('fullscreen') ||
190
+ error.name === 'NotAllowedError') {
191
+ return MediaErrorType.PERMISSION_ERROR
192
+ }
193
+
194
+ // Playback errors
195
+ if (message.includes('decode') ||
196
+ message.includes('format') ||
197
+ message.includes('codec') ||
198
+ action?.type === 'PLAY') {
199
+ return MediaErrorType.PLAYBACK_ERROR
200
+ }
201
+
202
+ // Timeout errors
203
+ if (message.includes('timeout') ||
204
+ error.name === 'TimeoutError') {
205
+ return MediaErrorType.TIMEOUT_ERROR
206
+ }
207
+
208
+ // Not supported errors
209
+ if (message.includes('not supported') ||
210
+ message.includes('unsupported') ||
211
+ error.name === 'NotSupportedError') {
212
+ return MediaErrorType.NOT_SUPPORTED_ERROR
213
+ }
214
+
215
+ return MediaErrorType.UNKNOWN_ERROR
216
+ }
217
+
218
+ // Check if an error/action combination is retryable
219
+ private isRetryable(errorType: MediaErrorType, action?: OptimisticMediaAction): boolean {
220
+ const errorRetryable = this.config.retryableErrors.includes(errorType)
221
+ const actionRetryable = !action || this.config.retryableActions.includes(action.type)
222
+ return errorRetryable && actionRetryable
223
+ }
224
+
225
+ // Calculate retry delay with exponential backoff
226
+ private calculateRetryDelay(retryCount: number): number {
227
+ if (!this.config.exponentialBackoff) {
228
+ return this.config.baseDelay
229
+ }
230
+
231
+ const delay = this.config.baseDelay * Math.pow(2, retryCount)
232
+ return Math.min(delay, this.config.maxDelay)
233
+ }
234
+
235
+ // Handle an error with automatic retry logic
236
+ async handleError(
237
+ error: unknown,
238
+ action?: OptimisticMediaAction,
239
+ retryCallback?: (action: OptimisticMediaAction) => Promise<void>
240
+ ): Promise<MediaError> {
241
+ const mediaError = this.createMediaError(error, action, action?.retryCount || 0)
242
+ const errorId = `${action?.type}-${mediaError.timestamp}`
243
+
244
+ // Store error for tracking
245
+ this.errorQueue.set(errorId, mediaError)
246
+
247
+ // Notify listeners
248
+ this.listeners.forEach(listener => listener(mediaError))
249
+
250
+ // Try recovery strategies
251
+ for (const strategy of this.recoveryStrategies) {
252
+ if (strategy.canRecover(mediaError)) {
253
+ try {
254
+ await strategy.recover(mediaError)
255
+ break
256
+ } catch (recoveryError) {
257
+ console.warn('Recovery strategy failed:', recoveryError)
258
+ }
259
+ }
260
+ }
261
+
262
+ // Retry if possible
263
+ if (mediaError.retryable &&
264
+ mediaError.retryCount < this.config.maxRetries &&
265
+ retryCallback &&
266
+ action) {
267
+
268
+ const delay = this.calculateRetryDelay(mediaError.retryCount)
269
+ console.log(`Retrying action ${action.type} in ${delay}ms (attempt ${mediaError.retryCount + 1}/${this.config.maxRetries})`)
270
+
271
+ setTimeout(async () => {
272
+ try {
273
+ await retryCallback({
274
+ ...action,
275
+ retryCount: mediaError.retryCount + 1
276
+ })
277
+
278
+ // Remove from error queue on successful retry
279
+ this.errorQueue.delete(errorId)
280
+ } catch (retryError) {
281
+ // Handle retry failure
282
+ await this.handleError(retryError, {
283
+ ...action,
284
+ retryCount: mediaError.retryCount + 1
285
+ }, retryCallback)
286
+ }
287
+ }, delay)
288
+ }
289
+
290
+ return mediaError
291
+ }
292
+
293
+ // Subscribe to error events
294
+ subscribe(listener: (error: MediaError) => void): () => void {
295
+ this.listeners.add(listener)
296
+ return () => this.listeners.delete(listener)
297
+ }
298
+
299
+ // Get current errors
300
+ getErrors(): MediaError[] {
301
+ return Array.from(this.errorQueue.values())
302
+ }
303
+
304
+ // Clear specific error
305
+ clearError(errorId: string): void {
306
+ this.errorQueue.delete(errorId)
307
+ }
308
+
309
+ // Clear all errors
310
+ clearAllErrors(): void {
311
+ this.errorQueue.clear()
312
+ }
313
+
314
+ // Get error statistics
315
+ getErrorStats(): {
316
+ totalErrors: number
317
+ errorsByType: Record<MediaErrorType, number>
318
+ retryableErrors: number
319
+ recentErrors: MediaError[]
320
+ } {
321
+ const errors = this.getErrors()
322
+ const now = Date.now()
323
+ const recentThreshold = 5 * 60 * 1000 // 5 minutes
324
+
325
+ const errorsByType = errors.reduce((acc, error) => {
326
+ acc[error.type] = (acc[error.type] || 0) + 1
327
+ return acc
328
+ }, {} as Record<MediaErrorType, number>)
329
+
330
+ const retryableErrors = errors.filter(e => e.retryable).length
331
+ const recentErrors = errors.filter(e => now - e.timestamp < recentThreshold)
332
+
333
+ return {
334
+ totalErrors: errors.length,
335
+ errorsByType,
336
+ retryableErrors,
337
+ recentErrors
338
+ }
339
+ }
340
+ }
341
+
342
+ // Hook for using error handling in components
343
+ export function useMediaErrorHandler(config?: Partial<RetryConfig>) {
344
+ const [handler] = React.useState(() => new MediaErrorHandler(config))
345
+ const [errors, setErrors] = React.useState<MediaError[]>([])
346
+
347
+ React.useEffect(() => {
348
+ const unsubscribe = handler.subscribe((error) => {
349
+ setErrors(current => [...current, error])
350
+ })
351
+
352
+ return unsubscribe
353
+ }, [handler])
354
+
355
+ const clearError = React.useCallback((errorId: string) => {
356
+ handler.clearError(errorId)
357
+ setErrors(current => current.filter(e => `${e.action?.type}-${e.timestamp}` !== errorId))
358
+ }, [handler])
359
+
360
+ const clearAllErrors = React.useCallback(() => {
361
+ handler.clearAllErrors()
362
+ setErrors([])
363
+ }, [handler])
364
+
365
+ return {
366
+ handler,
367
+ errors,
368
+ clearError,
369
+ clearAllErrors,
370
+ errorStats: handler.getErrorStats()
371
+ }
372
+ }
373
+
374
+ // Hook for error boundaries
375
+ export function useMediaErrorBoundary() {
376
+ const [error, setError] = React.useState<MediaError | null>(null)
377
+
378
+ const resetError = React.useCallback(() => {
379
+ setError(null)
380
+ }, [])
381
+
382
+ const captureError = React.useCallback((error: unknown, action?: OptimisticMediaAction) => {
383
+ const handler = new MediaErrorHandler()
384
+ const mediaError = handler.createMediaError(error, action)
385
+ setError(mediaError)
386
+ }, [])
387
+
388
+ return {
389
+ error,
390
+ hasError: !!error,
391
+ resetError,
392
+ captureError
393
+ }
394
+ }
@@ -0,0 +1,208 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+
5
+ // Media action types
6
+ export const MediaActionTypes = {
7
+ PLAY: "PLAY",
8
+ PAUSE: "PAUSE",
9
+ SEEK: "SEEK",
10
+ SET_VOLUME: "SET_VOLUME",
11
+ MUTE: "MUTE",
12
+ UNMUTE: "UNMUTE",
13
+ TOGGLE_MUTE: "TOGGLE_MUTE",
14
+ SET_DURATION: "SET_DURATION",
15
+ SET_CURRENT_TIME: "SET_CURRENT_TIME",
16
+ SET_BUFFERED: "SET_BUFFERED",
17
+ SET_LOADING: "SET_LOADING",
18
+ SET_ERROR: "SET_ERROR",
19
+ ENTER_FULLSCREEN: "ENTER_FULLSCREEN",
20
+ EXIT_FULLSCREEN: "EXIT_FULLSCREEN",
21
+ ENTER_PIP: "ENTER_PIP",
22
+ EXIT_PIP: "EXIT_PIP",
23
+ SET_PLAYBACK_RATE: "SET_PLAYBACK_RATE",
24
+ SET_SUBTITLE_TRACK: "SET_SUBTITLE_TRACK",
25
+ SET_RENDITION: "SET_RENDITION",
26
+ TOGGLE_SUBTITLES: "TOGGLE_SUBTITLES",
27
+ PREVIEW_TIME: "PREVIEW_TIME",
28
+ CLEAR_PREVIEW: "CLEAR_PREVIEW",
29
+ // New actions for Video.js adapter
30
+ SET_BUFFERING: "SET_BUFFERING",
31
+ SET_QUALITY_LEVEL: "SET_QUALITY_LEVEL",
32
+ SET_BANDWIDTH_ESTIMATE: "SET_BANDWIDTH_ESTIMATE",
33
+ SET_ACTIVE_TEXT_TRACKS: "SET_ACTIVE_TEXT_TRACKS"
34
+ } as const
35
+
36
+ export type MediaActionType = (typeof MediaActionTypes)[keyof typeof MediaActionTypes]
37
+
38
+ // Media state interface
39
+ export interface MediaState {
40
+ // Playback state
41
+ isPlaying: boolean
42
+ currentTime: number
43
+ duration: number
44
+ buffered: TimeRanges | null
45
+ isLoading: boolean
46
+ isBuffering: boolean
47
+ error: string | null
48
+
49
+ // Audio state
50
+ volume: number
51
+ isMuted: boolean
52
+
53
+ // Video state
54
+ isFullscreen: boolean
55
+ isPictureInPicture: boolean
56
+
57
+ // Advanced features
58
+ playbackRate: number
59
+ subtitlesShowing: boolean
60
+ selectedSubtitleTrack: TextTrack | null
61
+ selectedRendition: string | null
62
+ qualityLevel: number
63
+ bandwidthEstimate: number
64
+ activeTextTracks: any[]
65
+
66
+ // Preview state (for seek preview)
67
+ previewTime: number | null
68
+ previewImage: string | null
69
+ previewCoords: { x: number; y: number; width: number; height: number } | null
70
+
71
+ // Media lists
72
+ subtitlesList: TextTrack[]
73
+ renditionList: Array<{ id: string; label: string; width?: number; height?: number }>
74
+ }
75
+
76
+ // Media action interface
77
+ export interface MediaAction {
78
+ type: MediaActionType
79
+ detail?: any
80
+ }
81
+
82
+ // Media context interface
83
+ export interface MediaContextValue {
84
+ mediaId: string
85
+ labelId: string
86
+ descriptionId: string
87
+ dir?: "ltr" | "rtl"
88
+ rootRef: React.RefObject<HTMLDivElement | null>
89
+ mediaRef: React.RefObject<HTMLVideoElement | HTMLAudioElement | null>
90
+ portalContainer?: HTMLElement
91
+ tooltipDelayDuration: number
92
+ tooltipSideOffset: number
93
+ disabled: boolean
94
+ isVideo: boolean
95
+ withoutTooltip: boolean
96
+ }
97
+
98
+ // Store interface for internal state management
99
+ export interface MediaStore {
100
+ subscribe: (callback: () => void) => () => void
101
+ getState: () => MediaState
102
+ setState: (updates: Partial<MediaState>) => void
103
+ dispatch: (action: MediaAction) => void
104
+ }
105
+
106
+ // Create the media context
107
+ export const MediaContext = React.createContext<MediaContextValue | null>(null)
108
+
109
+ // Hook to use media context
110
+ export function useMediaContext() {
111
+ const context = React.useContext(MediaContext)
112
+ if (!context) {
113
+ throw new Error("useMediaContext must be used within a MediaPlayer")
114
+ }
115
+ return context
116
+ }
117
+
118
+ // Store context for state management
119
+ export const MediaStoreContext = React.createContext<MediaStore | null>(null)
120
+
121
+ // Hook to use media store
122
+ export function useMediaStore() {
123
+ const store = React.useContext(MediaStoreContext)
124
+ if (!store) {
125
+ throw new Error("useMediaStore must be used within a MediaPlayer")
126
+ }
127
+ return store
128
+ }
129
+
130
+ // Hook to select specific state values - React 19 optimized
131
+ export function useMediaSelector<T>(selector: (state: MediaState) => T): T {
132
+ const store = useMediaStore()
133
+
134
+ // React 19 compiler will optimize this callback
135
+ const getSnapshot = () => selector(store.getState())
136
+
137
+ return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot)
138
+ }
139
+
140
+ // Hook to get dispatch function
141
+ export function useMediaDispatch() {
142
+ const store = useMediaStore()
143
+ return store.dispatch
144
+ }
145
+
146
+ // React 19 enhanced context selector with optimistic updates
147
+ export function useOptimisticMediaSelector<T>(selector: (state: MediaState) => T): T {
148
+ const store = useMediaStore()
149
+
150
+ // Check if enhanced store is available
151
+ const isEnhanced = 'getOptimisticState' in store
152
+
153
+ const getSnapshot = () => {
154
+ if (isEnhanced) {
155
+ // Use optimistic state for immediate UI feedback
156
+ return selector((store as any).getOptimisticState())
157
+ } else {
158
+ // Fall back to regular state
159
+ return selector(store.getState())
160
+ }
161
+ }
162
+
163
+ return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot)
164
+ }
165
+
166
+ // Hook for accessing React 19 enhanced features
167
+ export function useMediaEnhancements() {
168
+ const store = useMediaStore()
169
+ const context = useMediaContext()
170
+
171
+ const isEnhanced = 'getOptimisticState' in store
172
+ const errorHandler = 'errorHandler' in context ? (context as any).errorHandler : null
173
+ const isRehydrated = 'isRehydrated' in context ? (context as any).isRehydrated : true
174
+
175
+ return {
176
+ isEnhanced,
177
+ errorHandler,
178
+ isRehydrated,
179
+ actionsState: isEnhanced ? (store as any).getActionState() : null
180
+ }
181
+ }
182
+
183
+ // Initial media state
184
+ export const initialMediaState: MediaState = {
185
+ isPlaying: false,
186
+ currentTime: 0,
187
+ duration: 0,
188
+ buffered: null,
189
+ isLoading: false,
190
+ isBuffering: false,
191
+ error: null,
192
+ volume: 1,
193
+ isMuted: false,
194
+ isFullscreen: false,
195
+ isPictureInPicture: false,
196
+ playbackRate: 1,
197
+ subtitlesShowing: false,
198
+ selectedSubtitleTrack: null,
199
+ selectedRendition: null,
200
+ qualityLevel: -1,
201
+ bandwidthEstimate: 0,
202
+ activeTextTracks: [],
203
+ previewTime: null,
204
+ previewImage: null,
205
+ previewCoords: null,
206
+ subtitlesList: [],
207
+ renditionList: []
208
+ }