@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.
- package/LICENSE +21 -0
- package/README.md +384 -0
- package/dist/context/media.js +2 -0
- package/dist/context/media.js.map +1 -0
- package/dist/context/media.mjs +15 -0
- package/dist/context/media.mjs.map +1 -0
- package/dist/context/store.js +2 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/store.mjs +5 -0
- package/dist/context/store.mjs.map +1 -0
- package/dist/context-BMteAJDN.js +1140 -0
- package/dist/context-BMteAJDN.js.map +1 -0
- package/dist/context-DCJ_scmO.cjs +23 -0
- package/dist/context-DCJ_scmO.cjs.map +1 -0
- package/dist/core-events-B5W_j8yU.js +480 -0
- package/dist/core-events-B5W_j8yU.js.map +1 -0
- package/dist/core-events-CN0-Vhhq.cjs +2 -0
- package/dist/core-events-CN0-Vhhq.cjs.map +1 -0
- package/dist/core-plugins-Bq9KLany.js +696 -0
- package/dist/core-plugins-Bq9KLany.js.map +1 -0
- package/dist/core-plugins-CPDSHMiQ.cjs +2 -0
- package/dist/core-plugins-CPDSHMiQ.cjs.map +1 -0
- package/dist/core.js +37 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +1217 -0
- package/dist/core.mjs.map +1 -0
- package/dist/enhanced-components-DHv0MFvG.js +1073 -0
- package/dist/enhanced-components-DHv0MFvG.js.map +1 -0
- package/dist/enhanced-components-DTIObgB7.cjs +2 -0
- package/dist/enhanced-components-DTIObgB7.cjs.map +1 -0
- package/dist/hooks-C_r8wexn.cjs +4 -0
- package/dist/hooks-C_r8wexn.cjs.map +1 -0
- package/dist/hooks-Da2wVXlF.js +1630 -0
- package/dist/hooks-Da2wVXlF.js.map +1 -0
- package/dist/index.css +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6126 -0
- package/dist/index.mjs.map +1 -0
- package/dist/minimal-components-DSq5sQpy.js +2002 -0
- package/dist/minimal-components-DSq5sQpy.js.map +1 -0
- package/dist/minimal-components-DflWiihN.cjs +2 -0
- package/dist/minimal-components-DflWiihN.cjs.map +1 -0
- package/dist/minimal-components.css +1 -0
- package/dist/mona-loading.gif +0 -0
- package/dist/performance-monitor-CUW6f5ll.cjs +2 -0
- package/dist/performance-monitor-CUW6f5ll.cjs.map +1 -0
- package/dist/performance-monitor-rMW1RgV3.js +302 -0
- package/dist/performance-monitor-rMW1RgV3.js.map +1 -0
- package/dist/utils-BnscpYYd.js +2798 -0
- package/dist/utils-BnscpYYd.js.map +1 -0
- package/dist/utils-D_czS_0K.cjs +9 -0
- package/dist/utils-D_czS_0K.cjs.map +1 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +7 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +114 -0
- package/src/adapters/videojs-adapter.tsx +470 -0
- package/src/assets/mona-loading.gif +0 -0
- package/src/components/AnimateLoading.tsx +115 -0
- package/src/components/LoadingState.tsx +248 -0
- package/src/components/accessibility/keyboard-help-overlay.tsx +213 -0
- package/src/components/audio-player.tsx +85 -0
- package/src/components/basic/index.ts +37 -0
- package/src/components/enhanced/advanced-features.tsx +316 -0
- package/src/components/enhanced/debug-panel.tsx +422 -0
- package/src/components/enhanced/enhanced-controls.tsx +600 -0
- package/src/components/enhanced/enhanced-media-player.tsx +509 -0
- package/src/components/enhanced/error-handling.tsx +345 -0
- package/src/components/enhanced/index.ts +39 -0
- package/src/components/enhanced/media-player-enhanced.tsx +349 -0
- package/src/components/examples/basic-video-player.tsx +55 -0
- package/src/components/examples/custom-controls-example.tsx +63 -0
- package/src/components/examples/enhanced-controls-example.tsx +114 -0
- package/src/components/examples/enhanced-media-player-example.tsx +293 -0
- package/src/components/examples/loading-states-demo.tsx +142 -0
- package/src/components/examples/react-19-demo.tsx +381 -0
- package/src/components/examples/volume-orientation-demo.tsx +114 -0
- package/src/components/icon-demo.tsx +209 -0
- package/src/components/icons/index.tsx +166 -0
- package/src/components/icons/svg-icons/ArrowLeftIcon.tsx +18 -0
- package/src/components/icons/svg-icons/ArrowRightIcon.tsx +18 -0
- package/src/components/icons/svg-icons/AudioIcon.tsx +22 -0
- package/src/components/icons/svg-icons/BackwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/CameraIcon.tsx +19 -0
- package/src/components/icons/svg-icons/CheckIcon.tsx +19 -0
- package/src/components/icons/svg-icons/EnablePiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/ForwardIcon.tsx +12 -0
- package/src/components/icons/svg-icons/FullscreenEnterIcon.tsx +23 -0
- package/src/components/icons/svg-icons/FullscreenExitIcon.tsx +23 -0
- package/src/components/icons/svg-icons/LoadingIcon.tsx +32 -0
- package/src/components/icons/svg-icons/PauseIcon.tsx +23 -0
- package/src/components/icons/svg-icons/PlayIcon.tsx +21 -0
- package/src/components/icons/svg-icons/PlaybackSpeedIcon.tsx +23 -0
- package/src/components/icons/svg-icons/QualityIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SettingsIcon.tsx +12 -0
- package/src/components/icons/svg-icons/SliderIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleIcon.tsx +21 -0
- package/src/components/icons/svg-icons/SubtitleOffIcon.tsx +25 -0
- package/src/components/icons/svg-icons/UnPiPIcon.tsx +17 -0
- package/src/components/icons/svg-icons/VolumeMutedIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeOneIcon.tsx +22 -0
- package/src/components/icons/svg-icons/VolumeThreeIcon.tsx +16 -0
- package/src/components/icons/svg-icons/VolumeTwoIcon.tsx +16 -0
- package/src/components/legacy.tsx +93 -0
- package/src/components/media-metadata-display.tsx +100 -0
- package/src/components/media-player-audio.tsx +39 -0
- package/src/components/media-player-controls.tsx +226 -0
- package/src/components/media-player-error.tsx +163 -0
- package/src/components/media-player-fullscreen.tsx +109 -0
- package/src/components/media-player-hls.tsx +111 -0
- package/src/components/media-player-loading.tsx +76 -0
- package/src/components/media-player-play.tsx +75 -0
- package/src/components/media-player-root.tsx +340 -0
- package/src/components/media-player-seek.tsx +168 -0
- package/src/components/media-player-time.tsx +99 -0
- package/src/components/media-player-tooltip.tsx +58 -0
- package/src/components/media-player-video.tsx +39 -0
- package/src/components/media-player-volume.tsx +155 -0
- package/src/components/media-player.tsx +257 -0
- package/src/components/media-suspense.tsx +362 -0
- package/src/components/player-settings-form.tsx +163 -0
- package/src/components/react-player-wrapper.tsx +651 -0
- package/src/components/simple-video-player.tsx +60 -0
- package/src/components/ui/enhanced-seek-bar.tsx +235 -0
- package/src/components/ui/fullscreen-button.tsx +60 -0
- package/src/components/ui/media-resource-preloader.tsx +395 -0
- package/src/components/ui/play-button.tsx +107 -0
- package/src/components/ui/seek-bar.tsx +161 -0
- package/src/components/ui/time-display.tsx +40 -0
- package/src/components/ui/volume-control.tsx +143 -0
- package/src/components/unified/base-media-player.tsx +246 -0
- package/src/components/unified/media-player.tsx +75 -0
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +561 -0
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +552 -0
- package/src/components/variants/minimal/MinimalControls.tsx +142 -0
- package/src/components/variants/minimal/MinimalLoading.tsx +32 -0
- package/src/components/variants/minimal/MinimalMediaPlayer.tsx +315 -0
- package/src/components/variants/minimal/MinimalOverlay.tsx +64 -0
- package/src/components/variants/minimal/MinimalPlayButton.tsx +65 -0
- package/src/components/variants/minimal/MinimalSeekBar.tsx +150 -0
- package/src/components/variants/minimal/MinimalVolumeControl.tsx +80 -0
- package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +210 -0
- package/src/components/variants/minimal/components/CenterPlayButton.tsx +22 -0
- package/src/components/variants/minimal/components/ControlBar.tsx +146 -0
- package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +361 -0
- package/src/components/variants/minimal/components/PlaybackControls.tsx +51 -0
- package/src/components/variants/minimal/components/RightControls.tsx +173 -0
- package/src/components/variants/minimal/components/SeekBar.tsx +131 -0
- package/src/components/variants/minimal/components/SettingsDropdown.tsx +70 -0
- package/src/components/variants/minimal/components/SubtitleDisplay.tsx +21 -0
- package/src/components/variants/minimal/components/TimeDisplay.tsx +18 -0
- package/src/components/variants/minimal/components/VideoElement.tsx +40 -0
- package/src/components/variants/minimal/components/VolumeControl.tsx +217 -0
- package/src/components/variants/minimal/index.ts +23 -0
- package/src/components/video-player.tsx +100 -0
- package/src/context/error-handling.ts +394 -0
- package/src/context/media-context.tsx +208 -0
- package/src/context/media-provider.tsx +265 -0
- package/src/context/media-store.ts +379 -0
- package/src/context/persistence.ts +453 -0
- package/src/core/events/event-system.ts +442 -0
- package/src/core/events/hooks.ts +302 -0
- package/src/core/events/index.ts +22 -0
- package/src/core/events/middleware.ts +269 -0
- package/src/core/index.ts +22 -0
- package/src/core/optimization/bundle-optimizer.tsx +308 -0
- package/src/core/performance/cicd.ts +96 -0
- package/src/core/performance/index.ts +38 -0
- package/src/core/performance/media-loading-optimizer.ts +538 -0
- package/src/core/performance/performance-budgets.ts +432 -0
- package/src/core/performance/performance-monitor.tsx +476 -0
- package/src/core/performance/performance-testing.ts +459 -0
- package/src/core/plugins/base-plugin.ts +224 -0
- package/src/core/plugins/enhanced-plugin-system.ts +411 -0
- package/src/core/plugins/examples/analytics-plugin.ts +242 -0
- package/src/core/plugins/index.ts +39 -0
- package/src/core/plugins/registry.ts +327 -0
- package/src/core/plugins/types.ts +159 -0
- package/src/core/plugins/utils.ts +316 -0
- package/src/core/progressive-loading.tsx +322 -0
- package/src/core/providers/enhanced-hls-provider.ts +339 -0
- package/src/core/providers/service-provider.tsx +168 -0
- package/src/core/utils.tsx +38 -0
- package/src/features/media-session/media-session-manager.ts +429 -0
- package/src/features/picture-in-picture/pip-manager.ts +429 -0
- package/src/features/playlist/playlist-manager.ts +606 -0
- package/src/features/subtitles/subtitle-manager.ts +441 -0
- package/src/features/subtitles/subtitle-parser.ts +280 -0
- package/src/features/thumbnails/thumbnail-generator.ts +519 -0
- package/src/hooks/accessibility/index.ts +27 -0
- package/src/hooks/accessibility/use-accessibility-preferences.ts +188 -0
- package/src/hooks/accessibility/use-focus-management.ts +176 -0
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +144 -0
- package/src/hooks/accessibility/use-screen-reader.ts +182 -0
- package/src/hooks/core/index.ts +47 -0
- package/src/hooks/legacy.ts +27 -0
- package/src/hooks/use-enhanced-media-player.ts +468 -0
- package/src/hooks/use-intelligent-preloading.ts +358 -0
- package/src/hooks/use-keyboard-controls.ts +115 -0
- package/src/hooks/use-media-actions.ts +175 -0
- package/src/hooks/use-media-metadata.ts +89 -0
- package/src/hooks/use-media-optimistic.ts +270 -0
- package/src/hooks/use-media-player.ts +312 -0
- package/src/hooks/use-media-state.ts +190 -0
- package/src/hooks/use-memory-optimization.ts +358 -0
- package/src/hooks/use-optimistic-controls.ts +92 -0
- package/src/hooks/use-optimized-media.ts +263 -0
- package/src/hooks/use-touch-gestures.ts +417 -0
- package/src/hooks/useEnhancedSubtitles.ts +258 -0
- package/src/hooks/useKeyboardShortcuts.ts +74 -0
- package/src/hooks/useMediaControls.ts +52 -0
- package/src/hooks/useScreenshot.ts +57 -0
- package/src/hooks/useSubtitles.ts +91 -0
- package/src/hooks/useVideoPlayer.ts +232 -0
- package/src/icons/components/FullscreenIcon.tsx +37 -0
- package/src/icons/components/SettingsIcon.tsx +38 -0
- package/src/icons/components/animated/LoadingIcon.tsx +43 -0
- package/src/icons/components/animated/PlayPauseIcon.tsx +50 -0
- package/src/icons/components/media/PauseIcon.tsx +39 -0
- package/src/icons/components/media/PlayIcon.tsx +38 -0
- package/src/icons/components/volume/VolumeIcon.tsx +39 -0
- package/src/icons/components/volume/VolumeOffIcon.tsx +39 -0
- package/src/icons/index.ts +56 -0
- package/src/icons/provider.tsx +64 -0
- package/src/icons/types.ts +40 -0
- package/src/index-enhanced.ts +35 -0
- package/src/index-legacy.ts +326 -0
- package/src/index.css +102 -0
- package/src/index.ts +216 -0
- package/src/lib/deprecation.ts +51 -0
- package/src/lib/format-time.ts +37 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles/globals.css +462 -0
- package/src/styles/minimal-player.css +589 -0
- package/src/styles/pixel-art.css +46 -0
- package/src/styles/videojs.css +100 -0
- package/src/test/setup.ts +79 -0
- package/src/types/events.ts +47 -0
- package/src/types/formats.ts +95 -0
- package/src/types/index.ts +190 -0
- package/src/types/videojs-plugins.d.ts +14 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState, useActionState, useOptimistic } from "react"
|
|
4
|
+
import type { MediaPlayerState } from "../types/index"
|
|
5
|
+
import { useMediaStore, useMediaSelector } from "../context/media-context"
|
|
6
|
+
import { useMediaOptimistic } from "./use-media-optimistic"
|
|
7
|
+
import { useMediaActions } from "./use-media-actions"
|
|
8
|
+
import type { EnhancedMediaStore } from "../context/media-store"
|
|
9
|
+
|
|
10
|
+
// Legacy hook for backward compatibility
|
|
11
|
+
export function useMediaState() {
|
|
12
|
+
const [state, setState] = useState<MediaPlayerState>({
|
|
13
|
+
isPlaying: false,
|
|
14
|
+
currentTime: 0,
|
|
15
|
+
duration: 0,
|
|
16
|
+
volume: 1,
|
|
17
|
+
isMuted: false,
|
|
18
|
+
isLoading: false,
|
|
19
|
+
isFullscreen: false,
|
|
20
|
+
error: null,
|
|
21
|
+
buffered: null
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const updateState = (updates: Partial<MediaPlayerState>) => {
|
|
25
|
+
setState((prev: MediaPlayerState) => ({ ...prev, ...updates }))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const setPlaying = (isPlaying: boolean) => {
|
|
29
|
+
updateState({ isPlaying })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const setCurrentTime = (currentTime: number) => {
|
|
33
|
+
updateState({ currentTime })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const setDuration = (duration: number) => {
|
|
37
|
+
updateState({ duration })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const setVolume = (volume: number) => {
|
|
41
|
+
updateState({ volume: Math.max(0, Math.min(1, volume)) })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const setMuted = (isMuted: boolean) => {
|
|
45
|
+
updateState({ isMuted })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const setLoading = (isLoading: boolean) => {
|
|
49
|
+
updateState({ isLoading })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const setFullscreen = (isFullscreen: boolean) => {
|
|
53
|
+
updateState({ isFullscreen })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const setError = (error: string | null) => {
|
|
57
|
+
updateState({ error })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const setBuffered = (buffered: TimeRanges | null) => {
|
|
61
|
+
updateState({ buffered })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
state,
|
|
66
|
+
updateState,
|
|
67
|
+
setPlaying,
|
|
68
|
+
setCurrentTime,
|
|
69
|
+
setDuration,
|
|
70
|
+
setVolume,
|
|
71
|
+
setMuted,
|
|
72
|
+
setLoading,
|
|
73
|
+
setFullscreen,
|
|
74
|
+
setError,
|
|
75
|
+
setBuffered
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// React 19 enhanced media state hook
|
|
80
|
+
export function useEnhancedMediaState() {
|
|
81
|
+
const store = useMediaStore()
|
|
82
|
+
|
|
83
|
+
// Check if this is an enhanced store
|
|
84
|
+
const isEnhanced = 'getOptimisticState' in store
|
|
85
|
+
|
|
86
|
+
if (!isEnhanced) {
|
|
87
|
+
// Fall back to regular media state if not enhanced
|
|
88
|
+
return useMediaState() as any
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Cast to enhanced store type
|
|
92
|
+
const enhancedStore = store as unknown as EnhancedMediaStore
|
|
93
|
+
|
|
94
|
+
// Use React 19 optimistic updates
|
|
95
|
+
const optimisticState = useMediaOptimistic(enhancedStore)
|
|
96
|
+
|
|
97
|
+
// Get action handlers with React 19 features
|
|
98
|
+
const actions = useMediaActions(enhancedStore)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
// State (optimistic values for immediate UI feedback)
|
|
102
|
+
state: optimisticState.state,
|
|
103
|
+
actualState: optimisticState.actualState,
|
|
104
|
+
|
|
105
|
+
// Individual state selectors
|
|
106
|
+
isPlaying: optimisticState.isPlaying,
|
|
107
|
+
currentTime: optimisticState.currentTime,
|
|
108
|
+
duration: optimisticState.state.duration,
|
|
109
|
+
volume: optimisticState.volume,
|
|
110
|
+
isMuted: optimisticState.isMuted,
|
|
111
|
+
isLoading: optimisticState.isBuffering,
|
|
112
|
+
isFullscreen: optimisticState.isFullscreen,
|
|
113
|
+
error: optimisticState.state.error,
|
|
114
|
+
buffered: optimisticState.state.buffered,
|
|
115
|
+
|
|
116
|
+
// Computed values
|
|
117
|
+
progress: optimisticState.progress,
|
|
118
|
+
formattedTime: optimisticState.formattedTime,
|
|
119
|
+
formattedDuration: optimisticState.formattedDuration,
|
|
120
|
+
|
|
121
|
+
// Actions with optimistic updates
|
|
122
|
+
actions,
|
|
123
|
+
|
|
124
|
+
// Action state for loading/error handling
|
|
125
|
+
isActionLoading: actions.isLoading,
|
|
126
|
+
actionError: actions.errorMessage,
|
|
127
|
+
retryLastAction: actions.retryLastAction,
|
|
128
|
+
|
|
129
|
+
// Legacy compatibility methods
|
|
130
|
+
updateState: (updates: Partial<MediaPlayerState>) => {
|
|
131
|
+
// Map legacy updates to new actions
|
|
132
|
+
if ('isPlaying' in updates) {
|
|
133
|
+
updates.isPlaying ? actions.play() : actions.pause()
|
|
134
|
+
}
|
|
135
|
+
if ('currentTime' in updates) {
|
|
136
|
+
actions.setCurrentTime(updates.currentTime!)
|
|
137
|
+
}
|
|
138
|
+
if ('duration' in updates) {
|
|
139
|
+
actions.setDuration(updates.duration!)
|
|
140
|
+
}
|
|
141
|
+
if ('volume' in updates) {
|
|
142
|
+
actions.setVolume(updates.volume!)
|
|
143
|
+
}
|
|
144
|
+
if ('isMuted' in updates) {
|
|
145
|
+
updates.isMuted ? actions.mute() : actions.unmute()
|
|
146
|
+
}
|
|
147
|
+
if ('isLoading' in updates) {
|
|
148
|
+
actions.setLoading(updates.isLoading!)
|
|
149
|
+
}
|
|
150
|
+
if ('isFullscreen' in updates) {
|
|
151
|
+
updates.isFullscreen ? actions.enterFullscreen() : actions.exitFullscreen()
|
|
152
|
+
}
|
|
153
|
+
if ('error' in updates) {
|
|
154
|
+
actions.setError(updates.error ?? null)
|
|
155
|
+
}
|
|
156
|
+
if ('buffered' in updates) {
|
|
157
|
+
actions.setBuffered(updates.buffered ?? null)
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// Individual action methods for legacy compatibility
|
|
162
|
+
setPlaying: (isPlaying: boolean) => {
|
|
163
|
+
isPlaying ? actions.play(true) : actions.pause(true) // Use optimistic updates
|
|
164
|
+
},
|
|
165
|
+
setCurrentTime: (time: number) => actions.setCurrentTime(time),
|
|
166
|
+
setDuration: (duration: number) => actions.setDuration(duration),
|
|
167
|
+
setVolume: (volume: number) => actions.setVolume(volume, true),
|
|
168
|
+
setMuted: (muted: boolean) => {
|
|
169
|
+
muted ? actions.mute(true) : actions.unmute(true)
|
|
170
|
+
},
|
|
171
|
+
setLoading: (loading: boolean) => actions.setLoading(loading),
|
|
172
|
+
setFullscreen: (fullscreen: boolean) => {
|
|
173
|
+
fullscreen ? actions.enterFullscreen(true) : actions.exitFullscreen(true)
|
|
174
|
+
},
|
|
175
|
+
setError: (error: string | null) => actions.setError(error),
|
|
176
|
+
setBuffered: (buffered: TimeRanges | null) => actions.setBuffered(buffered)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Hook that automatically chooses between legacy and enhanced based on context availability
|
|
181
|
+
export function useAdaptiveMediaState() {
|
|
182
|
+
try {
|
|
183
|
+
// Try to use the enhanced version if MediaStore context is available
|
|
184
|
+
return useEnhancedMediaState()
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Fall back to legacy version if not in MediaProvider context
|
|
187
|
+
console.warn('MediaStore context not available, using legacy state management')
|
|
188
|
+
return useMediaState()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef, useCallback, useMemo } from "react"
|
|
4
|
+
|
|
5
|
+
// Memory optimization hook for media players
|
|
6
|
+
export function useMemoryOptimization(options: {
|
|
7
|
+
maxMemoryUsage?: number // in MB
|
|
8
|
+
cleanupInterval?: number // in ms
|
|
9
|
+
enableGC?: boolean
|
|
10
|
+
monitorMemory?: boolean
|
|
11
|
+
} = {}) {
|
|
12
|
+
const {
|
|
13
|
+
maxMemoryUsage = 100, // 100MB default
|
|
14
|
+
cleanupInterval = 30000, // 30 seconds default
|
|
15
|
+
enableGC = true,
|
|
16
|
+
monitorMemory = true
|
|
17
|
+
} = options
|
|
18
|
+
|
|
19
|
+
const cleanupTimeoutRef = useRef<NodeJS.Timeout>()
|
|
20
|
+
const observersRef = useRef<Set<MutationObserver>>(new Set())
|
|
21
|
+
const eventListenersRef = useRef<Map<Element, Array<{ event: string; handler: EventListener }>>>(new Map())
|
|
22
|
+
const intervalRefs = useRef<Set<NodeJS.Timeout>>(new Set())
|
|
23
|
+
const animationFrameRefs = useRef<Set<number>>(new Set())
|
|
24
|
+
|
|
25
|
+
// Get current memory usage
|
|
26
|
+
const getMemoryUsage = useCallback(() => {
|
|
27
|
+
if (typeof window === 'undefined' || !('memory' in performance)) {
|
|
28
|
+
return { used: 0, total: 0, limit: 0 }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const memory = (performance as any).memory
|
|
32
|
+
return {
|
|
33
|
+
used: memory.usedJSHeapSize,
|
|
34
|
+
total: memory.totalJSHeapSize,
|
|
35
|
+
limit: memory.jsHeapSizeLimit
|
|
36
|
+
}
|
|
37
|
+
}, [])
|
|
38
|
+
|
|
39
|
+
// Memory monitoring state
|
|
40
|
+
const [memoryStats, setMemoryStats] = React.useState({
|
|
41
|
+
used: 0,
|
|
42
|
+
total: 0,
|
|
43
|
+
limit: 0,
|
|
44
|
+
usage: 0,
|
|
45
|
+
isHighUsage: false
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Memory monitoring
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!monitorMemory) return
|
|
51
|
+
|
|
52
|
+
const checkMemory = () => {
|
|
53
|
+
const memory = getMemoryUsage()
|
|
54
|
+
const usage = (memory.used / 1024 / 1024) // Convert to MB
|
|
55
|
+
const isHighUsage = usage > maxMemoryUsage
|
|
56
|
+
|
|
57
|
+
setMemoryStats({
|
|
58
|
+
...memory,
|
|
59
|
+
usage,
|
|
60
|
+
isHighUsage
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Trigger cleanup if memory usage is high
|
|
64
|
+
if (isHighUsage && enableGC) {
|
|
65
|
+
performCleanup()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
checkMemory()
|
|
70
|
+
const interval = setInterval(checkMemory, cleanupInterval)
|
|
71
|
+
intervalRefs.current.add(interval)
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
clearInterval(interval)
|
|
75
|
+
intervalRefs.current.delete(interval)
|
|
76
|
+
}
|
|
77
|
+
}, [monitorMemory, getMemoryUsage, maxMemoryUsage, enableGC, cleanupInterval])
|
|
78
|
+
|
|
79
|
+
// Cleanup function
|
|
80
|
+
const performCleanup = useCallback(() => {
|
|
81
|
+
// Clear all timeouts
|
|
82
|
+
intervalRefs.current.forEach(timeout => {
|
|
83
|
+
clearTimeout(timeout)
|
|
84
|
+
})
|
|
85
|
+
intervalRefs.current.clear()
|
|
86
|
+
|
|
87
|
+
// Cancel all animation frames
|
|
88
|
+
animationFrameRefs.current.forEach(frameId => {
|
|
89
|
+
cancelAnimationFrame(frameId)
|
|
90
|
+
})
|
|
91
|
+
animationFrameRefs.current.clear()
|
|
92
|
+
|
|
93
|
+
// Disconnect all observers
|
|
94
|
+
observersRef.current.forEach(observer => {
|
|
95
|
+
observer.disconnect()
|
|
96
|
+
})
|
|
97
|
+
observersRef.current.clear()
|
|
98
|
+
|
|
99
|
+
// Remove all event listeners
|
|
100
|
+
eventListenersRef.current.forEach((listeners, element) => {
|
|
101
|
+
listeners.forEach(({ event, handler }) => {
|
|
102
|
+
element.removeEventListener(event, handler)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
eventListenersRef.current.clear()
|
|
106
|
+
|
|
107
|
+
// Trigger garbage collection if available
|
|
108
|
+
if (enableGC && 'gc' in window) {
|
|
109
|
+
(window as any).gc()
|
|
110
|
+
}
|
|
111
|
+
}, [enableGC])
|
|
112
|
+
|
|
113
|
+
// Safe event listener management
|
|
114
|
+
const addEventListener = useCallback((
|
|
115
|
+
element: Element,
|
|
116
|
+
event: string,
|
|
117
|
+
handler: EventListener,
|
|
118
|
+
options?: AddEventListenerOptions
|
|
119
|
+
) => {
|
|
120
|
+
element.addEventListener(event, handler, options)
|
|
121
|
+
|
|
122
|
+
if (!eventListenersRef.current.has(element)) {
|
|
123
|
+
eventListenersRef.current.set(element, [])
|
|
124
|
+
}
|
|
125
|
+
eventListenersRef.current.get(element)!.push({ event, handler })
|
|
126
|
+
|
|
127
|
+
// Return cleanup function
|
|
128
|
+
return () => {
|
|
129
|
+
element.removeEventListener(event, handler)
|
|
130
|
+
const listeners = eventListenersRef.current.get(element)
|
|
131
|
+
if (listeners) {
|
|
132
|
+
const index = listeners.findIndex(l => l.event === event && l.handler === handler)
|
|
133
|
+
if (index > -1) {
|
|
134
|
+
listeners.splice(index, 1)
|
|
135
|
+
}
|
|
136
|
+
if (listeners.length === 0) {
|
|
137
|
+
eventListenersRef.current.delete(element)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}, [])
|
|
142
|
+
|
|
143
|
+
// Safe interval management
|
|
144
|
+
const setInterval = useCallback((callback: () => void, delay: number) => {
|
|
145
|
+
const timeoutId = setInterval(callback, delay)
|
|
146
|
+
intervalRefs.current.add(timeoutId)
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
clearInterval(timeoutId)
|
|
150
|
+
intervalRefs.current.delete(timeoutId)
|
|
151
|
+
}
|
|
152
|
+
}, [])
|
|
153
|
+
|
|
154
|
+
// Safe timeout management
|
|
155
|
+
const setTimeout = useCallback((callback: () => void, delay: number) => {
|
|
156
|
+
const timeoutId = setTimeout(callback, delay)
|
|
157
|
+
intervalRefs.current.add(timeoutId)
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
clearTimeout(timeoutId)
|
|
161
|
+
intervalRefs.current.delete(timeoutId)
|
|
162
|
+
}
|
|
163
|
+
}, [])
|
|
164
|
+
|
|
165
|
+
// Safe animation frame management
|
|
166
|
+
const requestAnimationFrame = useCallback((callback: FrameRequestCallback) => {
|
|
167
|
+
const frameId = requestAnimationFrame(callback)
|
|
168
|
+
animationFrameRefs.current.add(frameId)
|
|
169
|
+
|
|
170
|
+
return () => {
|
|
171
|
+
cancelAnimationFrame(frameId)
|
|
172
|
+
animationFrameRefs.current.delete(frameId)
|
|
173
|
+
}
|
|
174
|
+
}, [])
|
|
175
|
+
|
|
176
|
+
// Safe observer management
|
|
177
|
+
const addObserver = useCallback((observer: MutationObserver) => {
|
|
178
|
+
observersRef.current.add(observer)
|
|
179
|
+
|
|
180
|
+
return () => {
|
|
181
|
+
observer.disconnect()
|
|
182
|
+
observersRef.current.delete(observer)
|
|
183
|
+
}
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
// Cleanup on unmount
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
return () => {
|
|
189
|
+
performCleanup()
|
|
190
|
+
}
|
|
191
|
+
}, [performCleanup])
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
// Memory stats
|
|
195
|
+
memoryStats,
|
|
196
|
+
getMemoryUsage,
|
|
197
|
+
|
|
198
|
+
// Cleanup functions
|
|
199
|
+
performCleanup,
|
|
200
|
+
|
|
201
|
+
// Safe API functions
|
|
202
|
+
addEventListener,
|
|
203
|
+
setInterval,
|
|
204
|
+
setTimeout,
|
|
205
|
+
requestAnimationFrame,
|
|
206
|
+
addObserver,
|
|
207
|
+
|
|
208
|
+
// Utilities
|
|
209
|
+
formatBytes: (bytes: number) => {
|
|
210
|
+
if (bytes === 0) return '0 Bytes'
|
|
211
|
+
const k = 1024
|
|
212
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
213
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
214
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Resource cleanup hook for media elements
|
|
220
|
+
export function useMediaResourceCleanup(mediaRef: React.RefObject<HTMLVideoElement | HTMLAudioElement>) {
|
|
221
|
+
const { addEventListener, performCleanup } = useMemoryOptimization()
|
|
222
|
+
|
|
223
|
+
// Track resource cleanup
|
|
224
|
+
const cleanupResources = useCallback(() => {
|
|
225
|
+
const media = mediaRef.current
|
|
226
|
+
if (!media) return
|
|
227
|
+
|
|
228
|
+
// Stop media playback
|
|
229
|
+
if (!media.paused) {
|
|
230
|
+
_media.pause()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Clear source
|
|
234
|
+
if (media.src) {
|
|
235
|
+
media.src = ''
|
|
236
|
+
media.load()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Remove source elements
|
|
240
|
+
while (media.firstChild) {
|
|
241
|
+
media.removeChild(media.firstChild)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Clear any pending seeks
|
|
245
|
+
media.currentTime = 0
|
|
246
|
+
|
|
247
|
+
// Clear buffered data
|
|
248
|
+
if ('clearBuffered' in media) {
|
|
249
|
+
(media as any).clearBuffered()
|
|
250
|
+
}
|
|
251
|
+
}, [mediaRef])
|
|
252
|
+
|
|
253
|
+
// Enhanced event listener registration
|
|
254
|
+
const registerMediaEventListener = useCallback((
|
|
255
|
+
event: string,
|
|
256
|
+
handler: EventListener,
|
|
257
|
+
options?: AddEventListenerOptions
|
|
258
|
+
) => {
|
|
259
|
+
const media = mediaRef.current
|
|
260
|
+
if (!media) return () => {}
|
|
261
|
+
|
|
262
|
+
return addEventListener(media, event, handler, options)
|
|
263
|
+
}, [mediaRef, addEventListener])
|
|
264
|
+
|
|
265
|
+
// Cleanup on unmount
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
return cleanupResources
|
|
268
|
+
}, [cleanupResources])
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
cleanupResources,
|
|
272
|
+
registerMediaEventListener
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Performance monitoring hook
|
|
277
|
+
export function usePerformanceMonitoring() {
|
|
278
|
+
const [metrics, setMetrics] = React.useState({
|
|
279
|
+
fps: 0,
|
|
280
|
+
frameDrops: 0,
|
|
281
|
+
longTasks: 0,
|
|
282
|
+
totalLongTaskDuration: 0
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const frameCountRef = useRef(0)
|
|
286
|
+
const lastFrameTimeRef = useRef(performance.now())
|
|
287
|
+
const longTaskObserverRef = useRef<PerformanceObserver>()
|
|
288
|
+
|
|
289
|
+
// FPS monitoring
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
let rafId: number
|
|
292
|
+
|
|
293
|
+
const measureFrame = () => {
|
|
294
|
+
frameCountRef.current++
|
|
295
|
+
const now = performance.now()
|
|
296
|
+
|
|
297
|
+
// Calculate FPS every second
|
|
298
|
+
if (now - lastFrameTimeRef.current >= 1000) {
|
|
299
|
+
const fps = Math.round((frameCountRef.current * 1000) / (now - lastFrameTimeRef.current))
|
|
300
|
+
const expectedFrames = Math.round((now - lastFrameTimeRef.current) / 16.67) // 60fps
|
|
301
|
+
const frameDrops = Math.max(0, expectedFrames - frameCountRef.current)
|
|
302
|
+
|
|
303
|
+
setMetrics(prev => ({
|
|
304
|
+
...prev,
|
|
305
|
+
fps,
|
|
306
|
+
frameDrops: prev.frameDrops + frameDrops
|
|
307
|
+
}))
|
|
308
|
+
|
|
309
|
+
frameCountRef.current = 0
|
|
310
|
+
lastFrameTimeRef.current = now
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
rafId = requestAnimationFrame(measureFrame)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
rafId = requestAnimationFrame(measureFrame)
|
|
317
|
+
|
|
318
|
+
return () => {
|
|
319
|
+
cancelAnimationFrame(rafId)
|
|
320
|
+
}
|
|
321
|
+
}, [])
|
|
322
|
+
|
|
323
|
+
// Long task monitoring
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
if (!('PerformanceObserver' in window)) return
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const observer = new PerformanceObserver((list) => {
|
|
329
|
+
for (const entry of list.getEntries()) {
|
|
330
|
+
if (entry.duration > 50) { // Long task threshold
|
|
331
|
+
setMetrics(prev => ({
|
|
332
|
+
...prev,
|
|
333
|
+
longTasks: prev.longTasks + 1,
|
|
334
|
+
totalLongTaskDuration: prev.totalLongTaskDuration + entry.duration
|
|
335
|
+
}))
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
observer.observe({ entryTypes: ['longtask'] })
|
|
341
|
+
longTaskObserverRef.current = observer
|
|
342
|
+
|
|
343
|
+
return () => {
|
|
344
|
+
observer.disconnect()
|
|
345
|
+
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.warn('Long task monitoring not supported:', error)
|
|
348
|
+
}
|
|
349
|
+
}, [])
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
...metrics,
|
|
353
|
+
averageLongTaskDuration: metrics.longTasks > 0
|
|
354
|
+
? metrics.totalLongTaskDuration / metrics.longTasks
|
|
355
|
+
: 0,
|
|
356
|
+
isPerformanceGood: metrics.fps >= 50 && metrics.frameDrops < 5
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useOptimistic, useTransition } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface MediaState {
|
|
4
|
+
isPlaying: boolean
|
|
5
|
+
volume: number
|
|
6
|
+
currentTime: number
|
|
7
|
+
playbackRate: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useOptimisticMediaState(initialState: MediaState, mediaElement?: HTMLMediaElement | null) {
|
|
11
|
+
const [isPending, startTransition] = useTransition()
|
|
12
|
+
|
|
13
|
+
// Optimistic state for each property
|
|
14
|
+
const [optimisticPlaying, setOptimisticPlaying] = useOptimistic(
|
|
15
|
+
initialState.isPlaying,
|
|
16
|
+
(_, value: boolean) => value
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
const [optimisticVolume, setOptimisticVolume] = useOptimistic(
|
|
20
|
+
initialState.volume,
|
|
21
|
+
(_, value: number) => value
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const [optimisticTime, setOptimisticTime] = useOptimistic(
|
|
25
|
+
initialState.currentTime,
|
|
26
|
+
(_, value: number) => value
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const [optimisticRate, setOptimisticRate] = useOptimistic(
|
|
30
|
+
initialState.playbackRate,
|
|
31
|
+
(_, value: number) => value
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
// Wrapped actions with optimistic updates
|
|
35
|
+
const actions = {
|
|
36
|
+
togglePlay: async () => {
|
|
37
|
+
const newState = !optimisticPlaying
|
|
38
|
+
setOptimisticPlaying(newState)
|
|
39
|
+
|
|
40
|
+
startTransition(async () => {
|
|
41
|
+
if (mediaElement) {
|
|
42
|
+
if (newState) {
|
|
43
|
+
await mediaElement.play()
|
|
44
|
+
} else {
|
|
45
|
+
mediaElement.pause()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
setVolume: async (volume: number) => {
|
|
52
|
+
setOptimisticVolume(volume)
|
|
53
|
+
|
|
54
|
+
startTransition(async () => {
|
|
55
|
+
if (mediaElement) {
|
|
56
|
+
mediaElement.volume = volume
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
seek: async (time: number) => {
|
|
62
|
+
setOptimisticTime(time)
|
|
63
|
+
|
|
64
|
+
startTransition(async () => {
|
|
65
|
+
if (mediaElement) {
|
|
66
|
+
mediaElement.currentTime = time
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
setPlaybackRate: async (rate: number) => {
|
|
72
|
+
setOptimisticRate(rate)
|
|
73
|
+
|
|
74
|
+
startTransition(async () => {
|
|
75
|
+
if (mediaElement) {
|
|
76
|
+
mediaElement.playbackRate = rate
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
state: {
|
|
84
|
+
isPlaying: optimisticPlaying,
|
|
85
|
+
volume: optimisticVolume,
|
|
86
|
+
currentTime: optimisticTime,
|
|
87
|
+
playbackRate: optimisticRate
|
|
88
|
+
},
|
|
89
|
+
actions,
|
|
90
|
+
isPending
|
|
91
|
+
}
|
|
92
|
+
}
|