@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,362 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { Suspense } from "react"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
// Loading state component for media operations
|
|
7
|
+
export interface MediaLoadingProps {
|
|
8
|
+
children?: React.ReactNode
|
|
9
|
+
className?: string
|
|
10
|
+
size?: "sm" | "md" | "lg"
|
|
11
|
+
variant?: "spinner" | "skeleton" | "pulse"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function MediaLoading({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
size = "md",
|
|
18
|
+
variant = "spinner"
|
|
19
|
+
}: MediaLoadingProps) {
|
|
20
|
+
const sizeClasses = {
|
|
21
|
+
sm: "w-4 h-4",
|
|
22
|
+
md: "w-8 h-8",
|
|
23
|
+
lg: "w-12 h-12"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (variant === "spinner") {
|
|
27
|
+
return (
|
|
28
|
+
<div className={cn(
|
|
29
|
+
"flex items-center justify-center",
|
|
30
|
+
className
|
|
31
|
+
)}>
|
|
32
|
+
<div className={cn(
|
|
33
|
+
"animate-spin rounded-full border-2 border-current border-t-transparent",
|
|
34
|
+
sizeClasses[size]
|
|
35
|
+
)} />
|
|
36
|
+
{children && (
|
|
37
|
+
<span className="ml-2 text-muted-foreground text-sm">
|
|
38
|
+
{children}
|
|
39
|
+
</span>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (variant === "skeleton") {
|
|
46
|
+
return (
|
|
47
|
+
<div className={cn(
|
|
48
|
+
"animate-pulse rounded bg-muted",
|
|
49
|
+
sizeClasses[size],
|
|
50
|
+
className
|
|
51
|
+
)} />
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Pulse variant
|
|
56
|
+
return (
|
|
57
|
+
<div className={cn(
|
|
58
|
+
"animate-pulse bg-gradient-to-r bg-pos-0 bg-size-200 from-muted via-muted/50 to-muted",
|
|
59
|
+
"animate-shimmer rounded",
|
|
60
|
+
sizeClasses[size],
|
|
61
|
+
className
|
|
62
|
+
)} />
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Suspense wrapper for media components
|
|
67
|
+
export interface MediaSuspenseProps {
|
|
68
|
+
children: React.ReactNode
|
|
69
|
+
fallback?: React.ReactNode
|
|
70
|
+
className?: string
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function MediaSuspense({
|
|
74
|
+
children,
|
|
75
|
+
fallback = <MediaLoading />,
|
|
76
|
+
className
|
|
77
|
+
}: MediaSuspenseProps) {
|
|
78
|
+
return (
|
|
79
|
+
<div className={className}>
|
|
80
|
+
<Suspense fallback={fallback}>
|
|
81
|
+
{children}
|
|
82
|
+
</Suspense>
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Lazy loading wrapper for heavy media components
|
|
88
|
+
export function withMediaSuspense<P extends object>(
|
|
89
|
+
Component: React.ComponentType<P>,
|
|
90
|
+
loadingComponent?: React.ComponentType
|
|
91
|
+
) {
|
|
92
|
+
const WrappedComponent = React.forwardRef<any, P>((props, ref) => {
|
|
93
|
+
const LoadingComp = loadingComponent
|
|
94
|
+
return (
|
|
95
|
+
<MediaSuspense fallback={LoadingComp ? <LoadingComp /> : <MediaLoading />}>
|
|
96
|
+
<Component {...(props as any)} ref={ref} />
|
|
97
|
+
</MediaSuspense>
|
|
98
|
+
)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
WrappedComponent.displayName = `withMediaSuspense(${Component.displayName || Component.name})`
|
|
102
|
+
|
|
103
|
+
return WrappedComponent
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Hook for managing concurrent state updates
|
|
107
|
+
export function useMediaConcurrent() {
|
|
108
|
+
const [isPending, startTransition] = React.useTransition()
|
|
109
|
+
|
|
110
|
+
// Wrap state updates in transitions for better performance
|
|
111
|
+
const updateWithTransition = React.useCallback(
|
|
112
|
+
(updateFn: () => any): Promise<any> => {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
startTransition(() => {
|
|
115
|
+
const result = updateFn()
|
|
116
|
+
resolve(result)
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
[startTransition]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// Batch multiple updates together
|
|
124
|
+
const batchUpdates = React.useCallback(
|
|
125
|
+
(updates: Array<() => any>): Promise<any[]> => {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
startTransition(() => {
|
|
128
|
+
const results = updates.map(update => update())
|
|
129
|
+
resolve(results)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
[startTransition]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
isPending,
|
|
138
|
+
updateWithTransition,
|
|
139
|
+
batchUpdates
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Error boundary for media components
|
|
144
|
+
export interface MediaErrorBoundaryProps {
|
|
145
|
+
children: React.ReactNode
|
|
146
|
+
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>
|
|
147
|
+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface MediaErrorBoundaryState {
|
|
151
|
+
hasError: boolean
|
|
152
|
+
error: Error | null
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Default error fallback component
|
|
156
|
+
function DefaultErrorFallback({
|
|
157
|
+
error,
|
|
158
|
+
resetError
|
|
159
|
+
}: {
|
|
160
|
+
error: Error
|
|
161
|
+
resetError: () => void
|
|
162
|
+
}) {
|
|
163
|
+
return (
|
|
164
|
+
<div className="flex flex-col items-center justify-center rounded-lg border border-destructive/20 bg-destructive/10 p-6">
|
|
165
|
+
<div className="mb-2 font-semibold text-destructive text-lg">
|
|
166
|
+
Media Player Error
|
|
167
|
+
</div>
|
|
168
|
+
<div className="mb-4 text-center text-muted-foreground text-sm">
|
|
169
|
+
{error.message}
|
|
170
|
+
</div>
|
|
171
|
+
<button
|
|
172
|
+
onClick={resetError}
|
|
173
|
+
className="rounded bg-primary px-4 py-2 text-primary-foreground transition-colors hover:bg-primary/90"
|
|
174
|
+
>
|
|
175
|
+
Try Again
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export class MediaErrorBoundary extends React.Component<
|
|
182
|
+
MediaErrorBoundaryProps,
|
|
183
|
+
MediaErrorBoundaryState
|
|
184
|
+
> {
|
|
185
|
+
constructor(props: MediaErrorBoundaryProps) {
|
|
186
|
+
super(props)
|
|
187
|
+
this.state = { hasError: false, error: null }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static getDerivedStateFromError(error: Error): MediaErrorBoundaryState {
|
|
191
|
+
return { hasError: true, error }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
195
|
+
this.props.onError?.(error, errorInfo)
|
|
196
|
+
console.error('MediaErrorBoundary caught an error:', error, errorInfo)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
resetError = () => {
|
|
200
|
+
this.setState({ hasError: false, error: null })
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
render() {
|
|
204
|
+
if (this.state.hasError && this.state.error) {
|
|
205
|
+
const FallbackComponent = this.props.fallback || DefaultErrorFallback
|
|
206
|
+
return <FallbackComponent error={this.state.error} resetError={this.resetError} />
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return this.props.children
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Concurrent media component wrapper
|
|
214
|
+
export interface ConcurrentMediaProps {
|
|
215
|
+
children: React.ReactNode
|
|
216
|
+
priority?: "high" | "normal" | "low"
|
|
217
|
+
deferredMs?: number
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function ConcurrentMedia({
|
|
221
|
+
children,
|
|
222
|
+
priority = "normal",
|
|
223
|
+
deferredMs = 0
|
|
224
|
+
}: ConcurrentMediaProps) {
|
|
225
|
+
const [isDeferred, setIsDeferred] = React.useState(deferredMs > 0)
|
|
226
|
+
|
|
227
|
+
React.useEffect(() => {
|
|
228
|
+
if (deferredMs > 0) {
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
setIsDeferred(false)
|
|
231
|
+
}, deferredMs)
|
|
232
|
+
|
|
233
|
+
return () => clearTimeout(timer)
|
|
234
|
+
}
|
|
235
|
+
}, [deferredMs])
|
|
236
|
+
|
|
237
|
+
if (isDeferred) {
|
|
238
|
+
return <MediaLoading variant="skeleton" />
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Use different rendering strategies based on priority
|
|
242
|
+
if (priority === "high") {
|
|
243
|
+
// Render immediately without deferring
|
|
244
|
+
return <>{children}</>
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (priority === "low") {
|
|
248
|
+
// Use deferred value for lower priority updates
|
|
249
|
+
const deferredChildren = React.useDeferredValue(children)
|
|
250
|
+
return <>{deferredChildren}</>
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Normal priority - use transition
|
|
254
|
+
return (
|
|
255
|
+
<React.Suspense fallback={<MediaLoading />}>
|
|
256
|
+
{children}
|
|
257
|
+
</React.Suspense>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Hook for deferred loading of media resources
|
|
262
|
+
export function useDeferredMediaLoading(delay: number = 100) {
|
|
263
|
+
const [isReady, setIsReady] = React.useState(false)
|
|
264
|
+
const deferredIsReady = React.useDeferredValue(isReady)
|
|
265
|
+
|
|
266
|
+
React.useEffect(() => {
|
|
267
|
+
const timer = setTimeout(() => {
|
|
268
|
+
setIsReady(true)
|
|
269
|
+
}, delay)
|
|
270
|
+
|
|
271
|
+
return () => clearTimeout(timer)
|
|
272
|
+
}, [delay])
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
isReady: deferredIsReady,
|
|
276
|
+
actualIsReady: isReady
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Media component with full React 19 concurrent features
|
|
281
|
+
export interface EnhancedMediaWrapperProps {
|
|
282
|
+
children: React.ReactNode
|
|
283
|
+
className?: string
|
|
284
|
+
loadingFallback?: React.ReactNode
|
|
285
|
+
errorFallback?: React.ComponentType<{ error: Error; resetError: () => void }>
|
|
286
|
+
priority?: "high" | "normal" | "low"
|
|
287
|
+
deferredMs?: number
|
|
288
|
+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function EnhancedMediaWrapper({
|
|
292
|
+
children,
|
|
293
|
+
className,
|
|
294
|
+
loadingFallback = <MediaLoading />,
|
|
295
|
+
errorFallback,
|
|
296
|
+
priority = "normal",
|
|
297
|
+
deferredMs = 0,
|
|
298
|
+
onError
|
|
299
|
+
}: EnhancedMediaWrapperProps) {
|
|
300
|
+
return (
|
|
301
|
+
<div className={className}>
|
|
302
|
+
<MediaErrorBoundary fallback={errorFallback} onError={onError}>
|
|
303
|
+
<MediaSuspense fallback={loadingFallback}>
|
|
304
|
+
<ConcurrentMedia priority={priority} deferredMs={deferredMs}>
|
|
305
|
+
{children}
|
|
306
|
+
</ConcurrentMedia>
|
|
307
|
+
</MediaSuspense>
|
|
308
|
+
</MediaErrorBoundary>
|
|
309
|
+
</div>
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Export enhanced components with concurrent features
|
|
314
|
+
export const EnhancedMediaSuspense = MediaSuspense
|
|
315
|
+
export const EnhancedMediaLoading = MediaLoading
|
|
316
|
+
|
|
317
|
+
// Performance monitoring hook for concurrent features
|
|
318
|
+
export function useMediaPerformance() {
|
|
319
|
+
const [metrics, setMetrics] = React.useState({
|
|
320
|
+
renderTime: 0,
|
|
321
|
+
suspenseCount: 0,
|
|
322
|
+
errorCount: 0,
|
|
323
|
+
transitionCount: 0
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
const recordRenderTime = React.useCallback((startTime: number) => {
|
|
327
|
+
const renderTime = performance.now() - startTime
|
|
328
|
+
setMetrics(prev => ({
|
|
329
|
+
...prev,
|
|
330
|
+
renderTime: Math.round(renderTime * 100) / 100
|
|
331
|
+
}))
|
|
332
|
+
}, [])
|
|
333
|
+
|
|
334
|
+
const incrementSuspense = React.useCallback(() => {
|
|
335
|
+
setMetrics(prev => ({
|
|
336
|
+
...prev,
|
|
337
|
+
suspenseCount: prev.suspenseCount + 1
|
|
338
|
+
}))
|
|
339
|
+
}, [])
|
|
340
|
+
|
|
341
|
+
const incrementError = React.useCallback(() => {
|
|
342
|
+
setMetrics(prev => ({
|
|
343
|
+
...prev,
|
|
344
|
+
errorCount: prev.errorCount + 1
|
|
345
|
+
}))
|
|
346
|
+
}, [])
|
|
347
|
+
|
|
348
|
+
const incrementTransition = React.useCallback(() => {
|
|
349
|
+
setMetrics(prev => ({
|
|
350
|
+
...prev,
|
|
351
|
+
transitionCount: prev.transitionCount + 1
|
|
352
|
+
}))
|
|
353
|
+
}, [])
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
metrics,
|
|
357
|
+
recordRenderTime,
|
|
358
|
+
incrementSuspense,
|
|
359
|
+
incrementError,
|
|
360
|
+
incrementTransition
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useActionState } from 'react'
|
|
2
|
+
|
|
3
|
+
export interface PlayerSettings {
|
|
4
|
+
volume: number
|
|
5
|
+
quality: string
|
|
6
|
+
subtitles: boolean
|
|
7
|
+
playbackRate: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface SettingsState {
|
|
11
|
+
values: PlayerSettings
|
|
12
|
+
error?: string
|
|
13
|
+
success?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaultSettings: PlayerSettings = {
|
|
17
|
+
volume: 0.8,
|
|
18
|
+
quality: 'auto',
|
|
19
|
+
subtitles: false,
|
|
20
|
+
playbackRate: 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function updateSettings(
|
|
24
|
+
prevState: SettingsState,
|
|
25
|
+
formData: FormData
|
|
26
|
+
): Promise<SettingsState> {
|
|
27
|
+
try {
|
|
28
|
+
// Simulate async operation
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
30
|
+
|
|
31
|
+
const settings: PlayerSettings = {
|
|
32
|
+
volume: Number(formData.get('volume')) || 0.8,
|
|
33
|
+
quality: (formData.get('quality') as string) || 'auto',
|
|
34
|
+
subtitles: formData.get('subtitles') === 'on',
|
|
35
|
+
playbackRate: Number(formData.get('playbackRate')) || 1
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate settings
|
|
39
|
+
if (settings.volume < 0 || settings.volume > 1) {
|
|
40
|
+
throw new Error('Volume must be between 0 and 1')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (settings.playbackRate < 0.25 || settings.playbackRate > 2) {
|
|
44
|
+
throw new Error('Playback rate must be between 0.25 and 2')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Save to localStorage or send to server
|
|
48
|
+
localStorage.setItem('player-settings', JSON.stringify(settings))
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
values: settings,
|
|
52
|
+
success: true
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
values: prevState.values,
|
|
57
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function PlayerSettingsForm() {
|
|
63
|
+
const [state, submitAction, isPending] = useActionState(
|
|
64
|
+
updateSettings,
|
|
65
|
+
{
|
|
66
|
+
values: defaultSettings,
|
|
67
|
+
success: false
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="mx-auto w-full max-w-md">
|
|
73
|
+
<h3 className="mb-4 font-semibold text-lg text-white">Player Settings</h3>
|
|
74
|
+
|
|
75
|
+
<form action={submitAction} className="space-y-4">
|
|
76
|
+
{state.error && (
|
|
77
|
+
<div className="rounded-lg border border-red-700 bg-red-900/20 p-3">
|
|
78
|
+
<p className="text-red-400 text-sm">{state.error}</p>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
{state.success && (
|
|
83
|
+
<div className="rounded-lg border border-green-700 bg-green-900/20 p-3">
|
|
84
|
+
<p className="text-green-400 text-sm">Settings saved successfully!</p>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<fieldset disabled={isPending} className="space-y-4 disabled:opacity-50">
|
|
89
|
+
<div>
|
|
90
|
+
<label htmlFor="volume" className="mb-2 block font-medium text-sm text-white">
|
|
91
|
+
Volume: {Math.round(state.values.volume * 100)}%
|
|
92
|
+
</label>
|
|
93
|
+
<input
|
|
94
|
+
type="range"
|
|
95
|
+
id="volume"
|
|
96
|
+
name="volume"
|
|
97
|
+
min="0"
|
|
98
|
+
max="1"
|
|
99
|
+
step="0.01"
|
|
100
|
+
defaultValue={state.values.volume}
|
|
101
|
+
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-700"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div>
|
|
106
|
+
<label htmlFor="quality" className="mb-2 block font-medium text-sm text-white">
|
|
107
|
+
Quality
|
|
108
|
+
</label>
|
|
109
|
+
<select
|
|
110
|
+
id="quality"
|
|
111
|
+
name="quality"
|
|
112
|
+
defaultValue={state.values.quality}
|
|
113
|
+
className="w-full rounded-lg border border-gray-600 bg-gray-700 p-2 text-white focus:ring-2 focus:ring-blue-500"
|
|
114
|
+
>
|
|
115
|
+
<option value="auto">Auto</option>
|
|
116
|
+
<option value="1080p">1080p</option>
|
|
117
|
+
<option value="720p">720p</option>
|
|
118
|
+
<option value="480p">480p</option>
|
|
119
|
+
<option value="360p">360p</option>
|
|
120
|
+
</select>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div>
|
|
124
|
+
<label htmlFor="playbackRate" className="mb-2 block font-medium text-sm text-white">
|
|
125
|
+
Playback Rate: {state.values.playbackRate}x
|
|
126
|
+
</label>
|
|
127
|
+
<input
|
|
128
|
+
type="range"
|
|
129
|
+
id="playbackRate"
|
|
130
|
+
name="playbackRate"
|
|
131
|
+
min="0.25"
|
|
132
|
+
max="2"
|
|
133
|
+
step="0.25"
|
|
134
|
+
defaultValue={state.values.playbackRate}
|
|
135
|
+
className="h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-700"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<div className="flex items-center">
|
|
140
|
+
<input
|
|
141
|
+
type="checkbox"
|
|
142
|
+
id="subtitles"
|
|
143
|
+
name="subtitles"
|
|
144
|
+
defaultChecked={state.values.subtitles}
|
|
145
|
+
className="h-4 w-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-blue-500"
|
|
146
|
+
/>
|
|
147
|
+
<label htmlFor="subtitles" className="ml-2 font-medium text-sm text-white">
|
|
148
|
+
Enable Subtitles
|
|
149
|
+
</label>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<button
|
|
153
|
+
type="submit"
|
|
154
|
+
disabled={isPending}
|
|
155
|
+
className="w-full rounded-lg bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 disabled:bg-gray-600"
|
|
156
|
+
>
|
|
157
|
+
{isPending ? 'Saving...' : 'Save Settings'}
|
|
158
|
+
</button>
|
|
159
|
+
</fieldset>
|
|
160
|
+
</form>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|