@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,89 @@
1
+ import { use, cache } from 'react'
2
+
3
+ export interface MediaMetadata {
4
+ title?: string
5
+ artist?: string
6
+ duration: number
7
+ format: string
8
+ width?: number
9
+ height?: number
10
+ }
11
+
12
+ // Cache metadata fetches to prevent duplicate requests
13
+ const fetchMetadata = cache(async (src: string): Promise<MediaMetadata> => {
14
+ try {
15
+ // Create a video element to get metadata
16
+ const video = document.createElement('video')
17
+ video.crossOrigin = 'anonymous'
18
+ video.preload = 'metadata'
19
+
20
+ return new Promise((resolve, reject) => {
21
+ const timeout = setTimeout(() => {
22
+ video.removeEventListener('loadedmetadata', onMetadata)
23
+ video.removeEventListener('error', onError)
24
+ reject(new Error('Metadata loading timeout'))
25
+ }, 10000) // 10 second timeout
26
+
27
+ const onMetadata = () => {
28
+ clearTimeout(timeout)
29
+ video.removeEventListener('loadedmetadata', onMetadata)
30
+ video.removeEventListener('error', onError)
31
+
32
+ const metadata: MediaMetadata = {
33
+ duration: video.duration || 0,
34
+ format: getFormatFromSrc(src),
35
+ width: video.videoWidth || undefined,
36
+ height: video.videoHeight || undefined,
37
+ }
38
+
39
+ resolve(metadata)
40
+ }
41
+
42
+ const onError = (error: Event) => {
43
+ clearTimeout(timeout)
44
+ video.removeEventListener('loadedmetadata', onMetadata)
45
+ video.removeEventListener('error', onError)
46
+ reject(new Error('Failed to load media metadata'))
47
+ }
48
+
49
+ video.addEventListener('loadedmetadata', onMetadata)
50
+ video.addEventListener('error', onError)
51
+ video.src = src
52
+ })
53
+ } catch (error) {
54
+ throw new Error(`Failed to fetch metadata: ${error instanceof Error ? error.message : 'Unknown error'}`)
55
+ }
56
+ })
57
+
58
+ function getFormatFromSrc(src: string): string {
59
+ const url = new URL(src, window.location.href)
60
+ const pathname = url.pathname.toLowerCase()
61
+
62
+ if (pathname.endsWith('.mp4')) return 'MP4'
63
+ if (pathname.endsWith('.webm')) return 'WebM'
64
+ if (pathname.endsWith('.ogg')) return 'OGG'
65
+ if (pathname.endsWith('.m3u8')) return 'HLS'
66
+ if (pathname.endsWith('.mpd')) return 'DASH'
67
+
68
+ return 'Unknown'
69
+ }
70
+
71
+ export function useMediaMetadata(src: string): MediaMetadata {
72
+ // Use the React 19 use() hook for async data
73
+ return use(fetchMetadata(src))
74
+ }
75
+
76
+ // Utility function to format time
77
+ export function formatTime(seconds: number): string {
78
+ if (!isFinite(seconds) || isNaN(seconds)) return '0:00'
79
+
80
+ const hours = Math.floor(seconds / 3600)
81
+ const minutes = Math.floor((seconds % 3600) / 60)
82
+ const secs = Math.floor(seconds % 60)
83
+
84
+ if (hours > 0) {
85
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
86
+ } else {
87
+ return `${minutes}:${secs.toString().padStart(2, '0')}`
88
+ }
89
+ }
@@ -0,0 +1,270 @@
1
+ "use client"
2
+
3
+ import React, { useOptimistic } from "react"
4
+ import type { MediaState } from "../context/media-context"
5
+ import type { OptimisticMediaAction, EnhancedMediaStore } from "../context/media-store"
6
+
7
+ // Optimistic update reducer function
8
+ function optimisticMediaReducer(
9
+ state: MediaState,
10
+ action: OptimisticMediaAction
11
+ ): MediaState {
12
+ if (!action.optimistic) return state
13
+
14
+ // Apply immediate optimistic updates for better UX
15
+ switch (action.type) {
16
+ case "PLAY":
17
+ return { ...state, isPlaying: true, error: null }
18
+
19
+ case "PAUSE":
20
+ return { ...state, isPlaying: false }
21
+
22
+ case "SEEK":
23
+ return { ...state, currentTime: action.detail }
24
+
25
+ case "SET_VOLUME":
26
+ return { ...state, volume: Math.max(0, Math.min(1, action.detail)) }
27
+
28
+ case "MUTE":
29
+ return { ...state, isMuted: true }
30
+
31
+ case "UNMUTE":
32
+ return { ...state, isMuted: false }
33
+
34
+ case "TOGGLE_MUTE":
35
+ return { ...state, isMuted: !state.isMuted }
36
+
37
+ case "ENTER_FULLSCREEN":
38
+ return { ...state, isFullscreen: true }
39
+
40
+ case "EXIT_FULLSCREEN":
41
+ return { ...state, isFullscreen: false }
42
+
43
+ case "SET_PLAYBACK_RATE":
44
+ return { ...state, playbackRate: action.detail }
45
+
46
+ case "TOGGLE_SUBTITLES":
47
+ return { ...state, subtitlesShowing: !state.subtitlesShowing }
48
+
49
+ case "PREVIEW_TIME":
50
+ return { ...state, previewTime: action.detail }
51
+
52
+ case "CLEAR_PREVIEW":
53
+ return {
54
+ ...state,
55
+ previewTime: null,
56
+ previewImage: null,
57
+ previewCoords: null
58
+ }
59
+
60
+ default:
61
+ return state
62
+ }
63
+ }
64
+
65
+ // Media optimistic state hook using React 19's useOptimistic
66
+ export function useMediaOptimistic(store: EnhancedMediaStore) {
67
+ const actualState = React.useSyncExternalStore(
68
+ store.subscribe,
69
+ store.getState,
70
+ store.getState
71
+ )
72
+
73
+ const [optimisticState, addOptimistic] = useOptimistic(
74
+ actualState,
75
+ optimisticMediaReducer
76
+ )
77
+
78
+ // Enhanced dispatch that uses optimistic updates
79
+ const dispatchOptimistic = React.useCallback(async (action: OptimisticMediaAction) => {
80
+ // Add optimistic update immediately
81
+ if (action.optimistic) {
82
+ addOptimistic(action)
83
+ }
84
+
85
+ // Dispatch the actual action
86
+ await store.dispatchOptimistic(action)
87
+ }, [store, addOptimistic])
88
+
89
+ // Quick actions with optimistic updates
90
+ const optimisticActions = React.useMemo(() => ({
91
+ // Play/Pause with immediate feedback
92
+ togglePlayPause: async () => {
93
+ const action: OptimisticMediaAction = {
94
+ type: optimisticState.isPlaying ? "PAUSE" : "PLAY",
95
+ optimistic: true,
96
+ timestamp: Date.now()
97
+ }
98
+ await dispatchOptimistic(action)
99
+ },
100
+
101
+ // Volume control with immediate slider feedback
102
+ setVolumeOptimistic: async (volume: number) => {
103
+ const action: OptimisticMediaAction = {
104
+ type: "SET_VOLUME",
105
+ detail: Math.max(0, Math.min(1, volume)),
106
+ optimistic: true,
107
+ timestamp: Date.now()
108
+ }
109
+ await dispatchOptimistic(action)
110
+ },
111
+
112
+ // Seek with immediate scrub feedback
113
+ seekOptimistic: async (time: number) => {
114
+ const action: OptimisticMediaAction = {
115
+ type: "SEEK",
116
+ detail: time,
117
+ optimistic: true,
118
+ timestamp: Date.now()
119
+ }
120
+ await dispatchOptimistic(action)
121
+ },
122
+
123
+ // Mute toggle with immediate icon change
124
+ toggleMuteOptimistic: async () => {
125
+ const action: OptimisticMediaAction = {
126
+ type: "TOGGLE_MUTE",
127
+ optimistic: true,
128
+ timestamp: Date.now()
129
+ }
130
+ await dispatchOptimistic(action)
131
+ },
132
+
133
+ // Fullscreen toggle with immediate layout change
134
+ toggleFullscreenOptimistic: async () => {
135
+ const action: OptimisticMediaAction = {
136
+ type: optimisticState.isFullscreen ? "EXIT_FULLSCREEN" : "ENTER_FULLSCREEN",
137
+ optimistic: true,
138
+ timestamp: Date.now()
139
+ }
140
+ await dispatchOptimistic(action)
141
+ },
142
+
143
+ // Playback rate with immediate feedback
144
+ setPlaybackRateOptimistic: async (rate: number) => {
145
+ const action: OptimisticMediaAction = {
146
+ type: "SET_PLAYBACK_RATE",
147
+ detail: rate,
148
+ optimistic: true,
149
+ timestamp: Date.now()
150
+ }
151
+ await dispatchOptimistic(action)
152
+ },
153
+
154
+ // Subtitle toggle with immediate visibility change
155
+ toggleSubtitlesOptimistic: async () => {
156
+ const action: OptimisticMediaAction = {
157
+ type: "TOGGLE_SUBTITLES",
158
+ optimistic: true,
159
+ timestamp: Date.now()
160
+ }
161
+ await dispatchOptimistic(action)
162
+ }
163
+ }), [optimisticState, dispatchOptimistic])
164
+
165
+ // State selectors that return optimistic values
166
+ const selectors = React.useMemo(() => ({
167
+ isPlaying: optimisticState.isPlaying,
168
+ currentTime: optimisticState.currentTime,
169
+ volume: optimisticState.volume,
170
+ isMuted: optimisticState.isMuted,
171
+ isFullscreen: optimisticState.isFullscreen,
172
+ playbackRate: optimisticState.playbackRate,
173
+ subtitlesShowing: optimisticState.subtitlesShowing,
174
+ previewTime: optimisticState.previewTime,
175
+
176
+ // Computed values
177
+ progress: optimisticState.duration > 0
178
+ ? optimisticState.currentTime / optimisticState.duration
179
+ : 0,
180
+
181
+ formattedTime: formatTime(optimisticState.currentTime),
182
+ formattedDuration: formatTime(optimisticState.duration),
183
+
184
+ // State checks
185
+ canPlay: !optimisticState.isLoading && !optimisticState.error,
186
+ hasError: !!optimisticState.error,
187
+ isBuffering: optimisticState.isLoading
188
+ }), [optimisticState])
189
+
190
+ return {
191
+ state: optimisticState,
192
+ actualState,
193
+ ...selectors,
194
+ ...optimisticActions,
195
+ dispatchOptimistic
196
+ }
197
+ }
198
+
199
+ // Time formatting utility
200
+ function formatTime(seconds: number): string {
201
+ if (!seconds || !isFinite(seconds)) return "0:00"
202
+
203
+ const mins = Math.floor(seconds / 60)
204
+ const secs = Math.floor(seconds % 60)
205
+ const hours = Math.floor(mins / 60)
206
+
207
+ if (hours > 0) {
208
+ return `${hours}:${String(mins % 60).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
209
+ }
210
+
211
+ return `${mins}:${String(secs).padStart(2, '0')}`
212
+ }
213
+
214
+ // Hook for components that need to detect optimistic updates
215
+ export function useOptimisticUpdates(store: EnhancedMediaStore) {
216
+ const [hasOptimisticUpdates, setHasOptimisticUpdates] = React.useState(false)
217
+
218
+ React.useEffect(() => {
219
+ const unsubscribe = store.subscribe(() => {
220
+ const actualState = store.getState()
221
+ const optimisticState = store.getOptimisticState()
222
+
223
+ const hasUpdates = JSON.stringify(actualState) !== JSON.stringify(optimisticState)
224
+ setHasOptimisticUpdates(hasUpdates)
225
+ })
226
+
227
+ return unsubscribe
228
+ }, [store])
229
+
230
+ return hasOptimisticUpdates
231
+ }
232
+
233
+ // Hook to get both actual and optimistic state for debugging
234
+ export function useMediaStateComparison(store: EnhancedMediaStore) {
235
+ const actualState = React.useSyncExternalStore(
236
+ store.subscribe,
237
+ store.getState,
238
+ store.getState
239
+ )
240
+
241
+ const optimisticState = React.useSyncExternalStore(
242
+ store.subscribe,
243
+ store.getOptimisticState,
244
+ store.getOptimisticState
245
+ )
246
+
247
+ const differences = React.useMemo(() => {
248
+ const diffs: any[] = []
249
+
250
+ const stateKeys = Object.keys(actualState) as Array<keyof MediaState>
251
+ stateKeys.forEach(key => {
252
+ if (JSON.stringify(actualState[key]) !== JSON.stringify(optimisticState[key])) {
253
+ diffs.push({
254
+ key: key as string,
255
+ actual: actualState[key],
256
+ optimistic: optimisticState[key]
257
+ })
258
+ }
259
+ })
260
+
261
+ return diffs
262
+ }, [actualState, optimisticState])
263
+
264
+ return {
265
+ actualState,
266
+ optimisticState,
267
+ differences,
268
+ hasOptimisticUpdates: differences.length > 0
269
+ }
270
+ }
@@ -0,0 +1,312 @@
1
+ "use client"
2
+
3
+ import { useCallback, useEffect, useRef } from "react"
4
+ import type { MediaPlayerControls } from "../types/index"
5
+ import { useMediaState } from "./use-media-state"
6
+
7
+ interface UseMediaPlayerOptions {
8
+ src: string | string[]
9
+ autoPlay?: boolean
10
+ loop?: boolean
11
+ muted?: boolean
12
+ volume?: number
13
+ preload?: "none" | "metadata" | "auto"
14
+ crossOrigin?: "anonymous" | "use-credentials"
15
+ playbackRates?: number[]
16
+ keyboardControls?: boolean
17
+ showControls?: boolean
18
+ customControls?: boolean
19
+ onPlay?: () => void
20
+ onPause?: () => void
21
+ onTimeUpdate?: (currentTime: number) => void
22
+ onDurationChange?: (duration: number) => void
23
+ onVolumeChange?: (volume: number) => void
24
+ onError?: (error: string) => void
25
+ onLoadStart?: () => void
26
+ onLoadedData?: () => void
27
+ onEnded?: () => void
28
+ }
29
+
30
+ export function useMediaPlayer(options: UseMediaPlayerOptions) {
31
+ const mediaRef = useRef<HTMLVideoElement | HTMLAudioElement>(null)
32
+ const containerRef = useRef<HTMLDivElement>(null)
33
+
34
+ const {
35
+ state,
36
+ setPlaying,
37
+ setCurrentTime,
38
+ setDuration,
39
+ setVolume,
40
+ setMuted,
41
+ setLoading,
42
+ setFullscreen,
43
+ setError,
44
+ setBuffered
45
+ } = useMediaState()
46
+
47
+ // Initialize media element with options
48
+ useEffect(() => {
49
+ const media = mediaRef.current
50
+ if (!media) return
51
+
52
+ // Set initial properties
53
+ if (options.volume !== undefined) {
54
+ media.volume = options.volume
55
+ setVolume(options.volume)
56
+ }
57
+ if (options.muted !== undefined) {
58
+ media.muted = options.muted
59
+ setMuted(options.muted)
60
+ }
61
+ if (options.autoPlay !== undefined) {
62
+ media.autoplay = options.autoPlay
63
+ }
64
+ if (options.loop !== undefined) {
65
+ media.loop = options.loop
66
+ }
67
+ if (options.preload !== undefined) {
68
+ media.preload = options.preload
69
+ }
70
+ if (options.crossOrigin !== undefined) {
71
+ media.crossOrigin = options.crossOrigin
72
+ }
73
+ }, [options, setVolume, setMuted])
74
+
75
+ // Event handlers
76
+ const handlePlay = () => {
77
+ setPlaying(true)
78
+ options.onPlay?.()
79
+ }
80
+
81
+ const handlePause = () => {
82
+ setPlaying(false)
83
+ options.onPause?.()
84
+ }
85
+
86
+ const handleTimeUpdate = () => {
87
+ const media = mediaRef.current
88
+ if (!media) return
89
+
90
+ setCurrentTime(media.currentTime)
91
+ options.onTimeUpdate?.(media.currentTime)
92
+ }
93
+
94
+ const handleDurationChange = () => {
95
+ const media = mediaRef.current
96
+ if (!media) return
97
+
98
+ setDuration(media.duration)
99
+ options.onDurationChange?.(media.duration)
100
+ }
101
+
102
+ const handleVolumeChange = () => {
103
+ const media = mediaRef.current
104
+ if (!media) return
105
+
106
+ setVolume(media.volume)
107
+ setMuted(media.muted)
108
+ options.onVolumeChange?.(media.volume)
109
+ }
110
+
111
+ const handleLoadStart = () => {
112
+ setLoading(true)
113
+ setError(null)
114
+ options.onLoadStart?.()
115
+ }
116
+
117
+ const handleLoadedData = () => {
118
+ setLoading(false)
119
+ options.onLoadedData?.()
120
+ }
121
+
122
+ const handleError = () => {
123
+ const media = mediaRef.current
124
+ if (!media) return
125
+
126
+ const errorMessage = media.error?.message || "An error occurred while loading the media"
127
+ setError(errorMessage)
128
+ setLoading(false)
129
+ options.onError?.(errorMessage)
130
+ }
131
+
132
+ const handleEnded = () => {
133
+ setPlaying(false)
134
+ options.onEnded?.()
135
+ }
136
+
137
+ const handleProgress = () => {
138
+ const media = mediaRef.current
139
+ if (!media) return
140
+
141
+ setBuffered(media.buffered)
142
+ }
143
+
144
+ // Attach event listeners
145
+ useEffect(() => {
146
+ const media = mediaRef.current
147
+ if (!media) return
148
+
149
+ media.addEventListener("play", handlePlay)
150
+ media.addEventListener("pause", handlePause)
151
+ media.addEventListener("timeupdate", handleTimeUpdate)
152
+ media.addEventListener("durationchange", handleDurationChange)
153
+ media.addEventListener("volumechange", handleVolumeChange)
154
+ media.addEventListener("loadstart", handleLoadStart)
155
+ media.addEventListener("loadeddata", handleLoadedData)
156
+ media.addEventListener("error", handleError)
157
+ media.addEventListener("ended", handleEnded)
158
+ media.addEventListener("progress", handleProgress)
159
+
160
+ return () => {
161
+ media.removeEventListener("play", handlePlay)
162
+ media.removeEventListener("pause", handlePause)
163
+ media.removeEventListener("timeupdate", handleTimeUpdate)
164
+ media.removeEventListener("durationchange", handleDurationChange)
165
+ media.removeEventListener("volumechange", handleVolumeChange)
166
+ media.removeEventListener("loadstart", handleLoadStart)
167
+ media.removeEventListener("loadeddata", handleLoadedData)
168
+ media.removeEventListener("error", handleError)
169
+ media.removeEventListener("ended", handleEnded)
170
+ media.removeEventListener("progress", handleProgress)
171
+ }
172
+ }, [
173
+ handlePlay,
174
+ handlePause,
175
+ handleTimeUpdate,
176
+ handleDurationChange,
177
+ handleVolumeChange,
178
+ handleLoadStart,
179
+ handleLoadedData,
180
+ handleError,
181
+ handleEnded,
182
+ handleProgress
183
+ ])
184
+
185
+ // Control functions
186
+ const play = async () => {
187
+ const media = mediaRef.current
188
+ if (!media) return
189
+
190
+ try {
191
+ await media.play()
192
+ } catch (error) {
193
+ console.error("Failed to play media:", error)
194
+ setError("Failed to play media")
195
+ }
196
+ }
197
+
198
+ const pause = () => {
199
+ const media = mediaRef.current
200
+ if (!media) return
201
+
202
+ media.pause()
203
+ }
204
+
205
+ const toggle = () => {
206
+ if (state.isPlaying) {
207
+ pause()
208
+ } else {
209
+ play()
210
+ }
211
+ }
212
+
213
+ const seek = (time: number) => {
214
+ const media = mediaRef.current
215
+ if (!media) return
216
+
217
+ media.currentTime = Math.max(0, Math.min(time, media.duration || 0))
218
+ }
219
+
220
+ const setVolumeControl = (volume: number) => {
221
+ const media = mediaRef.current
222
+ if (!media) return
223
+
224
+ const clampedVolume = Math.max(0, Math.min(1, volume))
225
+ media.volume = clampedVolume
226
+ }
227
+
228
+ const mute = () => {
229
+ const media = mediaRef.current
230
+ if (!media) return
231
+
232
+ media.muted = true
233
+ }
234
+
235
+ const unmute = () => {
236
+ const media = mediaRef.current
237
+ if (!media) return
238
+
239
+ media.muted = false
240
+ }
241
+
242
+ const toggleMute = () => {
243
+ const media = mediaRef.current
244
+ if (!media) return
245
+
246
+ media.muted = !media.muted
247
+ }
248
+
249
+ const enterFullscreen = async () => {
250
+ const container = containerRef.current
251
+ if (!container || !document.fullscreenEnabled) return
252
+
253
+ try {
254
+ await container.requestFullscreen()
255
+ setFullscreen(true)
256
+ } catch (error) {
257
+ console.error("Failed to enter fullscreen:", error)
258
+ }
259
+ }
260
+
261
+ const exitFullscreen = async () => {
262
+ if (!document.fullscreenElement) return
263
+
264
+ try {
265
+ await document.exitFullscreen()
266
+ setFullscreen(false)
267
+ } catch (error) {
268
+ console.error("Failed to exit fullscreen:", error)
269
+ }
270
+ }
271
+
272
+ const toggleFullscreen = () => {
273
+ if (document.fullscreenElement) {
274
+ exitFullscreen()
275
+ } else {
276
+ enterFullscreen()
277
+ }
278
+ }
279
+
280
+ // Listen for fullscreen changes
281
+ useEffect(() => {
282
+ const handleFullscreenChange = () => {
283
+ setFullscreen(!!document.fullscreenElement)
284
+ }
285
+
286
+ document.addEventListener("fullscreenchange", handleFullscreenChange)
287
+ return () => {
288
+ document.removeEventListener("fullscreenchange", handleFullscreenChange)
289
+ }
290
+ }, [setFullscreen])
291
+
292
+ const controls: MediaPlayerControls = {
293
+ play,
294
+ pause,
295
+ toggle,
296
+ seek,
297
+ setVolume: setVolumeControl,
298
+ mute,
299
+ unmute,
300
+ toggleMute,
301
+ enterFullscreen,
302
+ exitFullscreen,
303
+ toggleFullscreen
304
+ }
305
+
306
+ return {
307
+ state,
308
+ controls,
309
+ mediaRef,
310
+ containerRef
311
+ }
312
+ }