@therealtinhtute/baroiplayer 1.2.0 → 2.0.0-beta.1
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/README.md +91 -73
- package/dist/components/icons/index.d.ts +36 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ArrowLeftIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/ArrowLeftIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ArrowRightIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/ArrowRightIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/AudioIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/AudioIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/BackwardIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/BackwardIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/CameraIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/CameraIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/CheckIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/CheckIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/EnablePiPIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/EnablePiPIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/ForwardIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/ForwardIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/FullscreenEnterIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/FullscreenEnterIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/FullscreenExitIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/FullscreenExitIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/LoadingIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/LoadingIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PauseIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PauseIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PlayIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PlayIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/PlaybackSpeedIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/PlaybackSpeedIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/QualityIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/QualityIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SettingsIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SettingsIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SliderIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SliderIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SubtitleIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SubtitleIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/SubtitleOffIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/SubtitleOffIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/UnPiPIcon.d.ts +4 -0
- package/dist/components/icons/svg-icons/UnPiPIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeMutedIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeMutedIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeOneIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeOneIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeThreeIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeThreeIcon.d.ts.map +1 -0
- package/dist/components/icons/svg-icons/VolumeTwoIcon.d.ts +5 -0
- package/dist/components/icons/svg-icons/VolumeTwoIcon.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-audio.d.ts +13 -0
- package/dist/components/variants/gradient/gradient-audio.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-controls.d.ts +4 -0
- package/dist/components/variants/gradient/gradient-controls.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-glyphs.d.ts +30 -0
- package/dist/components/variants/gradient/gradient-glyphs.d.ts.map +1 -0
- package/dist/components/variants/gradient/gradient-video.d.ts +16 -0
- package/dist/components/variants/gradient/gradient-video.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -112
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +786 -6271
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +3 -0
- package/package.json +110 -108
- package/src/components/variants/gradient/gradient-audio.tsx +47 -0
- package/src/components/variants/gradient/gradient-controls.tsx +254 -0
- package/src/components/variants/gradient/gradient-glyphs.test.tsx +69 -0
- package/src/components/variants/gradient/gradient-glyphs.tsx +129 -0
- package/src/components/variants/gradient/gradient-skin.css +211 -0
- package/src/components/variants/gradient/gradient-video.tsx +70 -0
- package/src/index.ts +5 -198
- package/src/test/accessibility-utils.ts +0 -59
- package/dist/context/media.js +0 -2
- package/dist/context/media.js.map +0 -1
- package/dist/context/media.mjs +0 -15
- package/dist/context/media.mjs.map +0 -1
- package/dist/context/store.js +0 -2
- package/dist/context/store.js.map +0 -1
- package/dist/context/store.mjs +0 -5
- package/dist/context/store.mjs.map +0 -1
- package/dist/context-BYwDoSRX.js +0 -1172
- package/dist/context-BYwDoSRX.js.map +0 -1
- package/dist/context-eNS62_ac.cjs +0 -23
- package/dist/context-eNS62_ac.cjs.map +0 -1
- package/dist/core-events-B1iM2F46.cjs +0 -2
- package/dist/core-events-B1iM2F46.cjs.map +0 -1
- package/dist/core-events-DunPHKRE.js +0 -484
- package/dist/core-events-DunPHKRE.js.map +0 -1
- package/dist/core-plugins-By-4-kGs.cjs +0 -2
- package/dist/core-plugins-By-4-kGs.cjs.map +0 -1
- package/dist/core-plugins-DSBWcTuX.js +0 -821
- package/dist/core-plugins-DSBWcTuX.js.map +0 -1
- package/dist/core.js +0 -37
- package/dist/core.js.map +0 -1
- package/dist/core.mjs +0 -1233
- package/dist/core.mjs.map +0 -1
- package/dist/enhanced-components-B_l3cist.js +0 -1142
- package/dist/enhanced-components-B_l3cist.js.map +0 -1
- package/dist/enhanced-components-BspFs9OO.cjs +0 -2
- package/dist/enhanced-components-BspFs9OO.cjs.map +0 -1
- package/dist/hooks-BXwI8QG6.cjs +0 -4
- package/dist/hooks-BXwI8QG6.cjs.map +0 -1
- package/dist/hooks-Bpi-jDiO.js +0 -1816
- package/dist/hooks-Bpi-jDiO.js.map +0 -1
- package/dist/minimal-components-C4z900Ro.js +0 -1926
- package/dist/minimal-components-C4z900Ro.js.map +0 -1
- package/dist/minimal-components-v4qBp6jv.cjs +0 -2
- package/dist/minimal-components-v4qBp6jv.cjs.map +0 -1
- package/dist/minimal-components.css +0 -1
- package/dist/mona-loading.gif +0 -0
- package/dist/performance-monitor-C5k6FDXw.js +0 -301
- package/dist/performance-monitor-C5k6FDXw.js.map +0 -1
- package/dist/performance-monitor-lJmuUjQQ.cjs +0 -2
- package/dist/performance-monitor-lJmuUjQQ.cjs.map +0 -1
- package/dist/utils-CFOue_9K.cjs +0 -9
- package/dist/utils-CFOue_9K.cjs.map +0 -1
- package/dist/utils-Dzpl2ArK.js +0 -2844
- package/dist/utils-Dzpl2ArK.js.map +0 -1
- package/dist/utils.js +0 -2
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs +0 -11
- package/dist/utils.mjs.map +0 -1
- package/src/adapters/videojs-adapter.tsx +0 -470
- package/src/assets/mona-loading.gif +0 -0
- package/src/components/AnimateLoading.tsx +0 -107
- package/src/components/LoadingState.tsx +0 -239
- package/src/components/__tests__/media-player-accessibility.test.tsx +0 -510
- package/src/components/__tests__/media-player-react19.test.tsx +0 -457
- package/src/components/accessibility/keyboard-help-overlay.tsx +0 -207
- package/src/components/audio-player.tsx +0 -85
- package/src/components/basic/index.ts +0 -37
- package/src/components/enhanced/advanced-features.tsx +0 -311
- package/src/components/enhanced/debug-panel.tsx +0 -448
- package/src/components/enhanced/enhanced-controls.tsx +0 -597
- package/src/components/enhanced/enhanced-media-player.tsx +0 -566
- package/src/components/enhanced/error-handling.tsx +0 -338
- package/src/components/enhanced/index.ts +0 -39
- package/src/components/enhanced/media-player-enhanced.tsx +0 -383
- package/src/components/examples/basic-video-player.tsx +0 -50
- package/src/components/examples/custom-controls-example.tsx +0 -63
- package/src/components/examples/enhanced-controls-example.tsx +0 -100
- package/src/components/examples/enhanced-media-player-example.tsx +0 -302
- package/src/components/examples/loading-states-demo.tsx +0 -142
- package/src/components/examples/react-19-demo.tsx +0 -353
- package/src/components/examples/volume-orientation-demo.tsx +0 -106
- package/src/components/icon-demo.tsx +0 -196
- package/src/components/legacy.tsx +0 -87
- package/src/components/media-metadata-display.tsx +0 -99
- package/src/components/media-player-audio.tsx +0 -39
- package/src/components/media-player-controls.tsx +0 -223
- package/src/components/media-player-error.tsx +0 -163
- package/src/components/media-player-fullscreen.tsx +0 -109
- package/src/components/media-player-hls.tsx +0 -111
- package/src/components/media-player-loading.tsx +0 -76
- package/src/components/media-player-play.tsx +0 -75
- package/src/components/media-player-root.tsx +0 -338
- package/src/components/media-player-seek.tsx +0 -168
- package/src/components/media-player-time.tsx +0 -99
- package/src/components/media-player-tooltip.tsx +0 -58
- package/src/components/media-player-video.tsx +0 -54
- package/src/components/media-player-volume.tsx +0 -155
- package/src/components/media-player.tsx +0 -292
- package/src/components/media-suspense.tsx +0 -344
- package/src/components/player-settings-form.tsx +0 -158
- package/src/components/react-player-wrapper.tsx +0 -651
- package/src/components/simple-video-player.tsx +0 -62
- package/src/components/ui/blur-poster.tsx +0 -93
- package/src/components/ui/empty-state.tsx +0 -91
- package/src/components/ui/enhanced-seek-bar.tsx +0 -254
- package/src/components/ui/error-state.tsx +0 -124
- package/src/components/ui/fullscreen-button.tsx +0 -81
- package/src/components/ui/loading-skeleton.tsx +0 -107
- package/src/components/ui/media-resource-preloader.tsx +0 -458
- package/src/components/ui/offline-message.tsx +0 -98
- package/src/components/ui/play-button.tsx +0 -99
- package/src/components/ui/seek-bar.tsx +0 -191
- package/src/components/ui/time-display.tsx +0 -61
- package/src/components/ui/volume-control.tsx +0 -190
- package/src/components/unified/base-media-player.tsx +0 -252
- package/src/components/unified/media-player.tsx +0 -75
- package/src/components/variants/minimal/AccessibleMinimalPlayer.tsx +0 -587
- package/src/components/variants/minimal/EnhancedMinimalMediaPlayer.tsx +0 -573
- package/src/components/variants/minimal/MinimalControls.tsx +0 -124
- package/src/components/variants/minimal/MinimalLoading.tsx +0 -28
- package/src/components/variants/minimal/MinimalMediaPlayer.tsx +0 -318
- package/src/components/variants/minimal/MinimalOverlay.tsx +0 -58
- package/src/components/variants/minimal/MinimalPlayButton.tsx +0 -56
- package/src/components/variants/minimal/MinimalSeekBar.tsx +0 -140
- package/src/components/variants/minimal/MinimalVolumeControl.tsx +0 -75
- package/src/components/variants/minimal/SimpleMinimalPlayer.tsx +0 -206
- package/src/components/variants/minimal/components/CenterPlayButton.tsx +0 -22
- package/src/components/variants/minimal/components/ControlBar.tsx +0 -142
- package/src/components/variants/minimal/components/EnhancedSettingsMenu.tsx +0 -412
- package/src/components/variants/minimal/components/PlaybackControls.tsx +0 -46
- package/src/components/variants/minimal/components/RightControls.tsx +0 -164
- package/src/components/variants/minimal/components/SeekBar.tsx +0 -127
- package/src/components/variants/minimal/components/SettingsDropdown.tsx +0 -68
- package/src/components/variants/minimal/components/SubtitleDisplay.tsx +0 -21
- package/src/components/variants/minimal/components/TimeDisplay.tsx +0 -15
- package/src/components/variants/minimal/components/VideoElement.tsx +0 -33
- package/src/components/variants/minimal/components/VolumeControl.tsx +0 -220
- package/src/components/variants/minimal/index.ts +0 -23
- package/src/components/video-player.tsx +0 -100
- package/src/context/__tests__/media-store.test.ts +0 -509
- package/src/context/error-handling.ts +0 -398
- package/src/context/media-context.tsx +0 -254
- package/src/context/media-provider.tsx +0 -264
- package/src/context/media-store.ts +0 -387
- package/src/context/persistence.ts +0 -462
- package/src/core/events/event-system.ts +0 -448
- package/src/core/events/hooks.ts +0 -325
- package/src/core/events/index.ts +0 -22
- package/src/core/events/middleware.ts +0 -286
- package/src/core/index.ts +0 -22
- package/src/core/optimization/bundle-optimizer.tsx +0 -312
- package/src/core/performance/cicd.ts +0 -98
- package/src/core/performance/index.ts +0 -38
- package/src/core/performance/media-loading-optimizer.ts +0 -544
- package/src/core/performance/performance-budgets.ts +0 -437
- package/src/core/performance/performance-monitor.tsx +0 -488
- package/src/core/performance/performance-testing.ts +0 -461
- package/src/core/plugins/__tests__/registry.test.ts +0 -595
- package/src/core/plugins/base-plugin.ts +0 -225
- package/src/core/plugins/enhanced-plugin-system.ts +0 -435
- package/src/core/plugins/examples/analytics-plugin.ts +0 -241
- package/src/core/plugins/index.ts +0 -39
- package/src/core/plugins/registry.ts +0 -546
- package/src/core/plugins/types.ts +0 -169
- package/src/core/plugins/utils.ts +0 -317
- package/src/core/progressive-loading.tsx +0 -331
- package/src/core/providers/enhanced-hls-provider.ts +0 -307
- package/src/core/providers/lazy-provider.tsx +0 -14
- package/src/core/providers/service-provider.tsx +0 -164
- package/src/core/utils.tsx +0 -34
- package/src/features/media-session/media-session-manager.ts +0 -411
- package/src/features/picture-in-picture/pip-manager.ts +0 -428
- package/src/features/playlist/playlist-manager.ts +0 -626
- package/src/features/subtitles/subtitle-manager.ts +0 -435
- package/src/features/subtitles/subtitle-parser.ts +0 -284
- package/src/features/thumbnails/thumbnail-generator.ts +0 -528
- package/src/hooks/__tests__/currentTime-fix.test.ts +0 -149
- package/src/hooks/__tests__/use-media-player.test.ts +0 -712
- package/src/hooks/__tests__/use-media-state.test.tsx +0 -316
- package/src/hooks/accessibility/index.ts +0 -27
- package/src/hooks/accessibility/use-accessibility-preferences.ts +0 -190
- package/src/hooks/accessibility/use-focus-management.ts +0 -183
- package/src/hooks/accessibility/use-keyboard-shortcuts.ts +0 -152
- package/src/hooks/accessibility/use-screen-reader.ts +0 -212
- package/src/hooks/core/index.ts +0 -47
- package/src/hooks/legacy.ts +0 -21
- package/src/hooks/use-buffer-health.ts +0 -218
- package/src/hooks/use-enhanced-media-player.ts +0 -575
- package/src/hooks/use-focus-trap.tsx +0 -162
- package/src/hooks/use-intelligent-preloading.ts +0 -387
- package/src/hooks/use-keyboard-controls.ts +0 -115
- package/src/hooks/use-keyboard-shortcuts.ts +0 -255
- package/src/hooks/use-media-actions.ts +0 -184
- package/src/hooks/use-media-metadata.ts +0 -91
- package/src/hooks/use-media-optimistic.ts +0 -264
- package/src/hooks/use-media-player.ts +0 -343
- package/src/hooks/use-media-state.ts +0 -194
- package/src/hooks/use-memory-optimization.ts +0 -360
- package/src/hooks/use-network-status.ts +0 -124
- package/src/hooks/use-optimistic-controls.ts +0 -95
- package/src/hooks/use-optimized-media.ts +0 -283
- package/src/hooks/use-smart-preload.ts +0 -229
- package/src/hooks/use-touch-gestures.ts +0 -461
- package/src/hooks/useEnhancedSubtitles.ts +0 -258
- package/src/hooks/useKeyboardShortcuts.ts +0 -106
- package/src/hooks/useMediaControls.ts +0 -52
- package/src/hooks/useScreenshot.ts +0 -61
- package/src/hooks/useSubtitles.ts +0 -91
- package/src/hooks/useVideoPlayer.ts +0 -278
- package/src/icons/components/FullscreenIcon.tsx +0 -36
- package/src/icons/components/SettingsIcon.tsx +0 -37
- package/src/icons/components/animated/LoadingIcon.tsx +0 -27
- package/src/icons/components/animated/PlayPauseIcon.tsx +0 -42
- package/src/icons/components/media/PauseIcon.tsx +0 -38
- package/src/icons/components/media/PlayIcon.tsx +0 -37
- package/src/icons/components/volume/VolumeIcon.tsx +0 -38
- package/src/icons/components/volume/VolumeOffIcon.tsx +0 -38
- package/src/icons/index.ts +0 -56
- package/src/icons/provider.tsx +0 -58
- package/src/icons/types.ts +0 -60
- package/src/index-enhanced.ts +0 -35
- package/src/index-legacy.ts +0 -324
- package/src/index.css +0 -102
- package/src/lib/__tests__/async-utils.test.ts +0 -291
- package/src/lib/async-utils.ts +0 -194
- package/src/lib/deprecation.ts +0 -51
- package/src/lib/format-time.ts +0 -37
- package/src/lib/index.ts +0 -4
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -462
- package/src/styles/minimal-player.css +0 -621
- package/src/styles/pixel-art.css +0 -41
- package/src/styles/videojs.css +0 -100
- package/src/types/events.ts +0 -47
- package/src/types/formats.ts +0 -79
- package/src/types/index.ts +0 -203
- package/src/types/videojs-plugins.d.ts +0 -14
|
@@ -1,398 +0,0 @@
|
|
|
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: [MediaErrorType.NETWORK_ERROR, MediaErrorType.TIMEOUT_ERROR],
|
|
45
|
-
retryableActions: ["PLAY", "PAUSE", "SEEK", "SET_VOLUME", "ENTER_FULLSCREEN", "EXIT_FULLSCREEN"]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Error recovery strategies
|
|
49
|
-
export interface ErrorRecoveryStrategy {
|
|
50
|
-
canRecover(error: MediaError): boolean
|
|
51
|
-
recover(error: MediaError): Promise<void>
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Network error recovery
|
|
55
|
-
class NetworkErrorRecovery implements ErrorRecoveryStrategy {
|
|
56
|
-
canRecover(error: MediaError): boolean {
|
|
57
|
-
return error.type === MediaErrorType.NETWORK_ERROR && error.retryCount < 3
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async recover(error: MediaError): Promise<void> {
|
|
61
|
-
// Wait for network to become available
|
|
62
|
-
if (navigator.onLine === false) {
|
|
63
|
-
await new Promise((resolve) => {
|
|
64
|
-
const handleOnline = () => {
|
|
65
|
-
window.removeEventListener("online", handleOnline)
|
|
66
|
-
resolve(void 0)
|
|
67
|
-
}
|
|
68
|
-
window.addEventListener("online", handleOnline)
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Additional delay for network stability
|
|
73
|
-
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Playback error recovery
|
|
78
|
-
class PlaybackErrorRecovery implements ErrorRecoveryStrategy {
|
|
79
|
-
canRecover(error: MediaError): boolean {
|
|
80
|
-
return error.type === MediaErrorType.PLAYBACK_ERROR && error.retryCount < 2
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async recover(error: MediaError): Promise<void> {
|
|
84
|
-
// Try to reload the media element
|
|
85
|
-
const mediaElement = document.querySelector("video, audio") as HTMLMediaElement
|
|
86
|
-
if (mediaElement) {
|
|
87
|
-
mediaElement.load()
|
|
88
|
-
await new Promise((resolve) => {
|
|
89
|
-
const handleCanPlay = () => {
|
|
90
|
-
mediaElement.removeEventListener("canplay", handleCanPlay)
|
|
91
|
-
resolve(void 0)
|
|
92
|
-
}
|
|
93
|
-
mediaElement.addEventListener("canplay", handleCanPlay)
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Permission error recovery (for fullscreen, etc.)
|
|
100
|
-
class PermissionErrorRecovery implements ErrorRecoveryStrategy {
|
|
101
|
-
canRecover(error: MediaError): boolean {
|
|
102
|
-
return (
|
|
103
|
-
error.type === MediaErrorType.PERMISSION_ERROR && error.action?.type === "ENTER_FULLSCREEN"
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async recover(error: MediaError): Promise<void> {
|
|
108
|
-
// Request user interaction for fullscreen
|
|
109
|
-
console.warn("Fullscreen requires user interaction")
|
|
110
|
-
// In a real implementation, you might show a message to the user
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Error handler class
|
|
115
|
-
export class MediaErrorHandler {
|
|
116
|
-
private config: RetryConfig
|
|
117
|
-
private recoveryStrategies: ErrorRecoveryStrategy[]
|
|
118
|
-
private errorQueue: Map<string, MediaError> = new Map()
|
|
119
|
-
private listeners: Set<(error: MediaError) => void> = new Set()
|
|
120
|
-
|
|
121
|
-
constructor(config: Partial<RetryConfig> = {}, recoveryStrategies: ErrorRecoveryStrategy[] = []) {
|
|
122
|
-
this.config = { ...DEFAULT_RETRY_CONFIG, ...config }
|
|
123
|
-
this.recoveryStrategies = [
|
|
124
|
-
new NetworkErrorRecovery(),
|
|
125
|
-
new PlaybackErrorRecovery(),
|
|
126
|
-
new PermissionErrorRecovery(),
|
|
127
|
-
...recoveryStrategies
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Create a media error from a generic error
|
|
132
|
-
createMediaError(error: unknown, action?: OptimisticMediaAction, retryCount = 0): MediaError {
|
|
133
|
-
const timestamp = Date.now()
|
|
134
|
-
|
|
135
|
-
if (error instanceof Error) {
|
|
136
|
-
const errorType = this.categorizeError(error, action)
|
|
137
|
-
return {
|
|
138
|
-
type: errorType,
|
|
139
|
-
message: error.message,
|
|
140
|
-
action,
|
|
141
|
-
timestamp,
|
|
142
|
-
retryable: this.isRetryable(errorType, action),
|
|
143
|
-
retryCount,
|
|
144
|
-
originalError: error
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
type: MediaErrorType.UNKNOWN_ERROR,
|
|
150
|
-
message: typeof error === "string" ? error : "Unknown error occurred",
|
|
151
|
-
action,
|
|
152
|
-
timestamp,
|
|
153
|
-
retryable: this.isRetryable(MediaErrorType.UNKNOWN_ERROR, action),
|
|
154
|
-
retryCount
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Categorize errors based on error properties and action context
|
|
159
|
-
private categorizeError(error: Error, action?: OptimisticMediaAction): MediaErrorType {
|
|
160
|
-
const message = error.message.toLowerCase()
|
|
161
|
-
|
|
162
|
-
// Network-related errors
|
|
163
|
-
if (
|
|
164
|
-
message.includes("network") ||
|
|
165
|
-
message.includes("fetch") ||
|
|
166
|
-
message.includes("connection") ||
|
|
167
|
-
error.name === "NetworkError"
|
|
168
|
-
) {
|
|
169
|
-
return MediaErrorType.NETWORK_ERROR
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Permission errors
|
|
173
|
-
if (
|
|
174
|
-
message.includes("permission") ||
|
|
175
|
-
message.includes("denied") ||
|
|
176
|
-
message.includes("fullscreen") ||
|
|
177
|
-
error.name === "NotAllowedError"
|
|
178
|
-
) {
|
|
179
|
-
return MediaErrorType.PERMISSION_ERROR
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Playback errors
|
|
183
|
-
if (
|
|
184
|
-
message.includes("decode") ||
|
|
185
|
-
message.includes("format") ||
|
|
186
|
-
message.includes("codec") ||
|
|
187
|
-
action?.type === "PLAY"
|
|
188
|
-
) {
|
|
189
|
-
return MediaErrorType.PLAYBACK_ERROR
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Timeout errors
|
|
193
|
-
if (message.includes("timeout") || error.name === "TimeoutError") {
|
|
194
|
-
return MediaErrorType.TIMEOUT_ERROR
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Not supported errors
|
|
198
|
-
if (
|
|
199
|
-
message.includes("not supported") ||
|
|
200
|
-
message.includes("unsupported") ||
|
|
201
|
-
error.name === "NotSupportedError"
|
|
202
|
-
) {
|
|
203
|
-
return MediaErrorType.NOT_SUPPORTED_ERROR
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return MediaErrorType.UNKNOWN_ERROR
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Check if an error/action combination is retryable
|
|
210
|
-
private isRetryable(errorType: MediaErrorType, action?: OptimisticMediaAction): boolean {
|
|
211
|
-
const errorRetryable = this.config.retryableErrors.includes(errorType)
|
|
212
|
-
const actionRetryable = !action || this.config.retryableActions.includes(action.type)
|
|
213
|
-
return errorRetryable && actionRetryable
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Calculate retry delay with exponential backoff
|
|
217
|
-
private calculateRetryDelay(retryCount: number): number {
|
|
218
|
-
if (!this.config.exponentialBackoff) {
|
|
219
|
-
return this.config.baseDelay
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const delay = this.config.baseDelay * Math.pow(2, retryCount)
|
|
223
|
-
return Math.min(delay, this.config.maxDelay)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Handle an error with automatic retry logic
|
|
227
|
-
async handleError(
|
|
228
|
-
error: unknown,
|
|
229
|
-
action?: OptimisticMediaAction,
|
|
230
|
-
retryCallback?: (action: OptimisticMediaAction) => Promise<void>
|
|
231
|
-
): Promise<MediaError> {
|
|
232
|
-
const mediaError = this.createMediaError(error, action, action?.retryCount || 0)
|
|
233
|
-
const errorId = `${action?.type}-${mediaError.timestamp}`
|
|
234
|
-
|
|
235
|
-
// Store error for tracking
|
|
236
|
-
this.errorQueue.set(errorId, mediaError)
|
|
237
|
-
|
|
238
|
-
// Notify listeners
|
|
239
|
-
this.listeners.forEach((listener) => listener(mediaError))
|
|
240
|
-
|
|
241
|
-
// Try recovery strategies
|
|
242
|
-
for (const strategy of this.recoveryStrategies) {
|
|
243
|
-
if (strategy.canRecover(mediaError)) {
|
|
244
|
-
try {
|
|
245
|
-
await strategy.recover(mediaError)
|
|
246
|
-
break
|
|
247
|
-
} catch (recoveryError) {
|
|
248
|
-
console.warn("Recovery strategy failed:", recoveryError)
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Retry if possible
|
|
254
|
-
if (
|
|
255
|
-
mediaError.retryable &&
|
|
256
|
-
mediaError.retryCount < this.config.maxRetries &&
|
|
257
|
-
retryCallback &&
|
|
258
|
-
action
|
|
259
|
-
) {
|
|
260
|
-
const delay = this.calculateRetryDelay(mediaError.retryCount)
|
|
261
|
-
console.log(
|
|
262
|
-
`Retrying action ${action.type} in ${delay}ms (attempt ${mediaError.retryCount + 1}/${this.config.maxRetries})`
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
setTimeout(async () => {
|
|
266
|
-
try {
|
|
267
|
-
await retryCallback({
|
|
268
|
-
...action,
|
|
269
|
-
retryCount: mediaError.retryCount + 1
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
// Remove from error queue on successful retry
|
|
273
|
-
this.errorQueue.delete(errorId)
|
|
274
|
-
} catch (retryError) {
|
|
275
|
-
// Handle retry failure
|
|
276
|
-
await this.handleError(
|
|
277
|
-
retryError,
|
|
278
|
-
{
|
|
279
|
-
...action,
|
|
280
|
-
retryCount: mediaError.retryCount + 1
|
|
281
|
-
},
|
|
282
|
-
retryCallback
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
}, delay)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return mediaError
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Subscribe to error events
|
|
292
|
-
subscribe(listener: (error: MediaError) => void): () => void {
|
|
293
|
-
this.listeners.add(listener)
|
|
294
|
-
return () => this.listeners.delete(listener)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Get current errors
|
|
298
|
-
getErrors(): MediaError[] {
|
|
299
|
-
return Array.from(this.errorQueue.values())
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Clear specific error
|
|
303
|
-
clearError(errorId: string): void {
|
|
304
|
-
this.errorQueue.delete(errorId)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Clear all errors
|
|
308
|
-
clearAllErrors(): void {
|
|
309
|
-
this.errorQueue.clear()
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Get error statistics
|
|
313
|
-
getErrorStats(): {
|
|
314
|
-
totalErrors: number
|
|
315
|
-
errorsByType: Record<MediaErrorType, number>
|
|
316
|
-
retryableErrors: number
|
|
317
|
-
recentErrors: MediaError[]
|
|
318
|
-
} {
|
|
319
|
-
const errors = this.getErrors()
|
|
320
|
-
const now = Date.now()
|
|
321
|
-
const recentThreshold = 5 * 60 * 1000 // 5 minutes
|
|
322
|
-
|
|
323
|
-
const errorsByType = errors.reduce(
|
|
324
|
-
(acc, error) => {
|
|
325
|
-
acc[error.type] = (acc[error.type] || 0) + 1
|
|
326
|
-
return acc
|
|
327
|
-
},
|
|
328
|
-
{} as Record<MediaErrorType, number>
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
const retryableErrors = errors.filter((e) => e.retryable).length
|
|
332
|
-
const recentErrors = errors.filter((e) => now - e.timestamp < recentThreshold)
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
totalErrors: errors.length,
|
|
336
|
-
errorsByType,
|
|
337
|
-
retryableErrors,
|
|
338
|
-
recentErrors
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Hook for using error handling in components
|
|
344
|
-
export function useMediaErrorHandler(config?: Partial<RetryConfig>) {
|
|
345
|
-
const [handler] = React.useState(() => new MediaErrorHandler(config))
|
|
346
|
-
const [errors, setErrors] = React.useState<MediaError[]>([])
|
|
347
|
-
|
|
348
|
-
React.useEffect(() => {
|
|
349
|
-
const unsubscribe = handler.subscribe((error) => {
|
|
350
|
-
setErrors((current) => [...current, error])
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
return unsubscribe
|
|
354
|
-
}, [handler])
|
|
355
|
-
|
|
356
|
-
const clearError = React.useCallback(
|
|
357
|
-
(errorId: string) => {
|
|
358
|
-
handler.clearError(errorId)
|
|
359
|
-
setErrors((current) => current.filter((e) => `${e.action?.type}-${e.timestamp}` !== errorId))
|
|
360
|
-
},
|
|
361
|
-
[handler]
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
const clearAllErrors = React.useCallback(() => {
|
|
365
|
-
handler.clearAllErrors()
|
|
366
|
-
setErrors([])
|
|
367
|
-
}, [handler])
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
handler,
|
|
371
|
-
errors,
|
|
372
|
-
clearError,
|
|
373
|
-
clearAllErrors,
|
|
374
|
-
errorStats: handler.getErrorStats()
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Hook for error boundaries
|
|
379
|
-
export function useMediaErrorBoundary() {
|
|
380
|
-
const [error, setError] = React.useState<MediaError | null>(null)
|
|
381
|
-
|
|
382
|
-
const resetError = React.useCallback(() => {
|
|
383
|
-
setError(null)
|
|
384
|
-
}, [])
|
|
385
|
-
|
|
386
|
-
const captureError = React.useCallback((error: unknown, action?: OptimisticMediaAction) => {
|
|
387
|
-
const handler = new MediaErrorHandler()
|
|
388
|
-
const mediaError = handler.createMediaError(error, action)
|
|
389
|
-
setError(mediaError)
|
|
390
|
-
}, [])
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
error,
|
|
394
|
-
hasError: !!error,
|
|
395
|
-
resetError,
|
|
396
|
-
captureError
|
|
397
|
-
}
|
|
398
|
-
}
|
|
@@ -1,254 +0,0 @@
|
|
|
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
|
-
// Shallow comparison for selector optimization
|
|
131
|
-
function shallowEqual(prev: unknown, next: unknown): boolean {
|
|
132
|
-
if (Object.is(prev, next)) return true
|
|
133
|
-
|
|
134
|
-
if (typeof prev !== "object" || prev === null || typeof next !== "object" || next === null) {
|
|
135
|
-
return false
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const prevKeys = Object.keys(prev as object)
|
|
139
|
-
const nextKeys = Object.keys(next as object)
|
|
140
|
-
|
|
141
|
-
if (prevKeys.length !== nextKeys.length) return false
|
|
142
|
-
|
|
143
|
-
for (const key of prevKeys) {
|
|
144
|
-
if (!nextKeys.includes(key)) return false
|
|
145
|
-
|
|
146
|
-
const prevValue = (prev as Record<string, unknown>)[key]
|
|
147
|
-
const nextValue = (next as Record<string, unknown>)[key]
|
|
148
|
-
|
|
149
|
-
if (!Object.is(prevValue, nextValue)) return false
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return true
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Hook to select specific state values - React 19 optimized with shallow comparison
|
|
156
|
-
export function useMediaSelector<T>(selector: (state: MediaState) => T): T {
|
|
157
|
-
const store = useMediaStore()
|
|
158
|
-
|
|
159
|
-
// React 19 compiler will optimize this callback
|
|
160
|
-
const getSnapshot = () => selector(store.getState())
|
|
161
|
-
|
|
162
|
-
// Track previous selected value for shallow comparison
|
|
163
|
-
const prevRef = React.useRef<T | undefined>(undefined)
|
|
164
|
-
const getServerSnapshot = React.useCallback(() => {
|
|
165
|
-
const value = selector(store.getState())
|
|
166
|
-
prevRef.current = value
|
|
167
|
-
return value
|
|
168
|
-
}, [selector])
|
|
169
|
-
|
|
170
|
-
// Use useSyncExternalStore for subscription
|
|
171
|
-
const selected = React.useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot)
|
|
172
|
-
|
|
173
|
-
// Apply shallow comparison to prevent unnecessary re-renders
|
|
174
|
-
React.useEffect(() => {
|
|
175
|
-
const prev = prevRef.current
|
|
176
|
-
if (prev !== undefined && shallowEqual(prev, selected)) {
|
|
177
|
-
// Values are equal, no re-render needed
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
prevRef.current = selected
|
|
181
|
-
}, [selected])
|
|
182
|
-
|
|
183
|
-
return selected
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Hook to get dispatch function
|
|
187
|
-
export function useMediaDispatch() {
|
|
188
|
-
const store = useMediaStore()
|
|
189
|
-
return store.dispatch
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// React 19 enhanced context selector with optimistic updates
|
|
193
|
-
export function useOptimisticMediaSelector<T>(selector: (state: MediaState) => T): T {
|
|
194
|
-
const store = useMediaStore()
|
|
195
|
-
|
|
196
|
-
// Check if enhanced store is available
|
|
197
|
-
const isEnhanced = "getOptimisticState" in store
|
|
198
|
-
|
|
199
|
-
const getSnapshot = () => {
|
|
200
|
-
if (isEnhanced) {
|
|
201
|
-
// Use optimistic state for immediate UI feedback
|
|
202
|
-
return selector((store as any).getOptimisticState())
|
|
203
|
-
} else {
|
|
204
|
-
// Fall back to regular state
|
|
205
|
-
return selector(store.getState())
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Hook for accessing React 19 enhanced features
|
|
213
|
-
export function useMediaEnhancements() {
|
|
214
|
-
const store = useMediaStore()
|
|
215
|
-
const context = useMediaContext()
|
|
216
|
-
|
|
217
|
-
const isEnhanced = "getOptimisticState" in store
|
|
218
|
-
const errorHandler = "errorHandler" in context ? (context as any).errorHandler : null
|
|
219
|
-
const isRehydrated = "isRehydrated" in context ? (context as any).isRehydrated : true
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
isEnhanced,
|
|
223
|
-
errorHandler,
|
|
224
|
-
isRehydrated,
|
|
225
|
-
actionsState: isEnhanced ? (store as any).getActionState() : null
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Initial media state
|
|
230
|
-
export const initialMediaState: MediaState = {
|
|
231
|
-
isPlaying: false,
|
|
232
|
-
currentTime: 0,
|
|
233
|
-
duration: 0,
|
|
234
|
-
buffered: null,
|
|
235
|
-
isLoading: false,
|
|
236
|
-
isBuffering: false,
|
|
237
|
-
error: null,
|
|
238
|
-
volume: 1,
|
|
239
|
-
isMuted: false,
|
|
240
|
-
isFullscreen: false,
|
|
241
|
-
isPictureInPicture: false,
|
|
242
|
-
playbackRate: 1,
|
|
243
|
-
subtitlesShowing: false,
|
|
244
|
-
selectedSubtitleTrack: null,
|
|
245
|
-
selectedRendition: null,
|
|
246
|
-
qualityLevel: -1,
|
|
247
|
-
bandwidthEstimate: 0,
|
|
248
|
-
activeTextTracks: [],
|
|
249
|
-
previewTime: null,
|
|
250
|
-
previewImage: null,
|
|
251
|
-
previewCoords: null,
|
|
252
|
-
subtitlesList: [],
|
|
253
|
-
renditionList: []
|
|
254
|
-
}
|