@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,58 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@rp/ui/components/tooltip"
|
|
4
|
+
import React from "react"
|
|
5
|
+
import { useMediaContext } from "../context/media-context"
|
|
6
|
+
|
|
7
|
+
export interface MediaPlayerTooltipProps {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
tooltip?: string
|
|
10
|
+
shortcut?: string | string[]
|
|
11
|
+
delayDuration?: number
|
|
12
|
+
sideOffset?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function MediaPlayerTooltip({
|
|
16
|
+
children,
|
|
17
|
+
tooltip,
|
|
18
|
+
shortcut,
|
|
19
|
+
delayDuration,
|
|
20
|
+
sideOffset
|
|
21
|
+
}: MediaPlayerTooltipProps) {
|
|
22
|
+
const context = useMediaContext()
|
|
23
|
+
|
|
24
|
+
if (context.withoutTooltip || !tooltip) {
|
|
25
|
+
return <>{children}</>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tooltipDelayDuration = delayDuration ?? context.tooltipDelayDuration
|
|
29
|
+
const tooltipSideOffset = sideOffset ?? context.tooltipSideOffset
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Tooltip delayDuration={tooltipDelayDuration}>
|
|
33
|
+
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
|
34
|
+
<TooltipContent sideOffset={tooltipSideOffset} className="flex items-center gap-2 text-sm">
|
|
35
|
+
<span>{tooltip}</span>
|
|
36
|
+
{shortcut && (
|
|
37
|
+
<div className="flex items-center gap-1">
|
|
38
|
+
{Array.isArray(shortcut) ? (
|
|
39
|
+
shortcut.map((key, index) => (
|
|
40
|
+
<kbd
|
|
41
|
+
key={index}
|
|
42
|
+
className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-medium font-mono text-[10px] text-muted-foreground opacity-100">
|
|
43
|
+
{key}
|
|
44
|
+
</kbd>
|
|
45
|
+
))
|
|
46
|
+
) : (
|
|
47
|
+
<kbd className="pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-medium font-mono text-[10px] text-muted-foreground opacity-100">
|
|
48
|
+
{shortcut}
|
|
49
|
+
</kbd>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
)}
|
|
53
|
+
</TooltipContent>
|
|
54
|
+
</Tooltip>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
MediaPlayerTooltip.displayName = "MediaPlayerTooltip"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { useMediaContext } from "../context/media-context"
|
|
5
|
+
|
|
6
|
+
export interface MediaPlayerVideoProps extends React.VideoHTMLAttributes<HTMLVideoElement> {
|
|
7
|
+
asChild?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MediaPlayerVideo({
|
|
11
|
+
asChild = false,
|
|
12
|
+
className,
|
|
13
|
+
children,
|
|
14
|
+
...props
|
|
15
|
+
}: MediaPlayerVideoProps) {
|
|
16
|
+
const context = useMediaContext()
|
|
17
|
+
|
|
18
|
+
if (asChild) {
|
|
19
|
+
// When asChild is true, clone the child element and add our props
|
|
20
|
+
const child = React.Children.only(children) as React.ReactElement
|
|
21
|
+
return React.cloneElement(child, {
|
|
22
|
+
...props,
|
|
23
|
+
ref: context.mediaRef,
|
|
24
|
+
"data-slot": "media-player-video"
|
|
25
|
+
} as any)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<video
|
|
30
|
+
ref={context.mediaRef as React.RefObject<HTMLVideoElement>}
|
|
31
|
+
data-slot="media-player-video"
|
|
32
|
+
className={className}
|
|
33
|
+
{...props}>
|
|
34
|
+
{children}
|
|
35
|
+
</video>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
MediaPlayerVideo.displayName = "MediaPlayerVideo"
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Button } from "@rp/ui/components/button"
|
|
4
|
+
import { Slider } from "@rp/ui/components/slider"
|
|
5
|
+
import { Volume1Icon, Volume2Icon, VolumeXIcon } from "lucide-react"
|
|
6
|
+
import React from "react"
|
|
7
|
+
import {
|
|
8
|
+
MediaActionTypes,
|
|
9
|
+
useMediaContext,
|
|
10
|
+
useMediaDispatch,
|
|
11
|
+
useMediaSelector
|
|
12
|
+
} from "../context/media-context"
|
|
13
|
+
import { cn } from "../lib/utils"
|
|
14
|
+
import { MediaPlayerTooltip } from "./media-player-tooltip"
|
|
15
|
+
|
|
16
|
+
export interface MediaPlayerVolumeProps {
|
|
17
|
+
className?: string
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
asChild?: boolean
|
|
20
|
+
expandable?: boolean
|
|
21
|
+
children?: React.ReactNode
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function MediaPlayerVolume({
|
|
25
|
+
className,
|
|
26
|
+
disabled = false,
|
|
27
|
+
asChild = false,
|
|
28
|
+
expandable = true,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}: MediaPlayerVolumeProps) {
|
|
32
|
+
const context = useMediaContext()
|
|
33
|
+
const dispatch = useMediaDispatch()
|
|
34
|
+
|
|
35
|
+
const volume = useMediaSelector((state) => state.volume)
|
|
36
|
+
const isMuted = useMediaSelector((state) => state.isMuted)
|
|
37
|
+
const isDisabled = disabled || useMediaSelector((state) => state.error !== null)
|
|
38
|
+
|
|
39
|
+
// Determine volume level for icon
|
|
40
|
+
const volumeLevel = React.useMemo(() => {
|
|
41
|
+
if (isMuted || volume === 0) return 0
|
|
42
|
+
if (volume < 0.5) return 1
|
|
43
|
+
return 2
|
|
44
|
+
}, [volume, isMuted])
|
|
45
|
+
|
|
46
|
+
const handleMuteToggle = React.useCallback(() => {
|
|
47
|
+
dispatch({
|
|
48
|
+
type: MediaActionTypes.TOGGLE_MUTE
|
|
49
|
+
})
|
|
50
|
+
}, [dispatch])
|
|
51
|
+
|
|
52
|
+
const handleVolumeChange = React.useCallback(
|
|
53
|
+
(values: number[]) => {
|
|
54
|
+
const newVolume = values[0]
|
|
55
|
+
dispatch({
|
|
56
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
57
|
+
detail: newVolume
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
[dispatch]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const handleVolumeCommit = React.useCallback(
|
|
64
|
+
(values: number[]) => {
|
|
65
|
+
const newVolume = values[0]
|
|
66
|
+
dispatch({
|
|
67
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
68
|
+
detail: newVolume
|
|
69
|
+
})
|
|
70
|
+
},
|
|
71
|
+
[dispatch]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const VolumeIcon = volumeLevel === 0 ? VolumeXIcon : volumeLevel === 1 ? Volume1Icon : Volume2Icon
|
|
75
|
+
|
|
76
|
+
const containerProps = {
|
|
77
|
+
"data-disabled": isDisabled ? "" : undefined,
|
|
78
|
+
"data-slot": "media-player-volume",
|
|
79
|
+
className: cn(
|
|
80
|
+
"group flex items-center",
|
|
81
|
+
expandable ? "gap-2 transition-all duration-200 hover:gap-3" : "gap-2",
|
|
82
|
+
className
|
|
83
|
+
),
|
|
84
|
+
...props
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const buttonProps = {
|
|
88
|
+
type: "button" as const,
|
|
89
|
+
"aria-controls": context.mediaId,
|
|
90
|
+
"aria-label": isMuted ? "Unmute" : "Mute",
|
|
91
|
+
"aria-pressed": isMuted,
|
|
92
|
+
"data-slot": "media-player-volume-button",
|
|
93
|
+
"data-state": isMuted ? "muted" : "unmuted",
|
|
94
|
+
variant: "ghost" as const,
|
|
95
|
+
size: "icon" as const,
|
|
96
|
+
className: "size-8 shrink-0",
|
|
97
|
+
disabled: isDisabled,
|
|
98
|
+
onClick: handleMuteToggle
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const sliderProps = {
|
|
102
|
+
"aria-controls": context.mediaId,
|
|
103
|
+
"aria-valuetext": `Volume ${Math.round(volume * 100)}%`,
|
|
104
|
+
"data-slot": "media-player-volume-slider",
|
|
105
|
+
min: 0,
|
|
106
|
+
max: 1,
|
|
107
|
+
step: 0.01,
|
|
108
|
+
value: [isMuted ? 0 : volume],
|
|
109
|
+
onValueChange: handleVolumeChange,
|
|
110
|
+
onValueCommit: handleVolumeCommit,
|
|
111
|
+
disabled: isDisabled,
|
|
112
|
+
className: cn(
|
|
113
|
+
"relative flex touch-none select-none items-center",
|
|
114
|
+
expandable
|
|
115
|
+
? "w-0 opacity-0 transition-all duration-200 group-hover:w-20 group-hover:opacity-100"
|
|
116
|
+
: "w-20",
|
|
117
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const content = (
|
|
122
|
+
<div {...containerProps}>
|
|
123
|
+
<MediaPlayerTooltip tooltip={isMuted ? "Unmute" : "Mute"} shortcut="M">
|
|
124
|
+
<Button {...buttonProps}>
|
|
125
|
+
<VolumeIcon className="size-4" />
|
|
126
|
+
</Button>
|
|
127
|
+
</MediaPlayerTooltip>
|
|
128
|
+
|
|
129
|
+
<Slider {...sliderProps}>
|
|
130
|
+
<div className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
|
131
|
+
<div
|
|
132
|
+
className="h-full bg-primary transition-all"
|
|
133
|
+
style={{ width: `${(isMuted ? 0 : volume) * 100}%` }}
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
<div
|
|
137
|
+
className="block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
|
138
|
+
style={{
|
|
139
|
+
transform: `translateX(-50%)`,
|
|
140
|
+
left: `${(isMuted ? 0 : volume) * 100}%`
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
</Slider>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if (asChild) {
|
|
148
|
+
const child = React.Children.only(children) as React.ReactElement
|
|
149
|
+
return React.cloneElement(child, containerProps as any, content)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return content
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
MediaPlayerVolume.displayName = "MediaPlayerVolume"
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useMemo, useTransition, startTransition } from "react"
|
|
4
|
+
import { useKeyboardControls } from "../hooks/use-keyboard-controls"
|
|
5
|
+
import { useEnhancedMediaPlayer } from "../hooks/use-enhanced-media-player"
|
|
6
|
+
import { useMemoryOptimization } from "../hooks/use-memory-optimization"
|
|
7
|
+
import { EnhancedMediaWrapper } from "./media-suspense"
|
|
8
|
+
import { cn } from "../lib/utils"
|
|
9
|
+
import type { MediaPlayerProps } from "../types/index"
|
|
10
|
+
|
|
11
|
+
export const MediaPlayer = forwardRef<HTMLDivElement, MediaPlayerProps>(
|
|
12
|
+
(
|
|
13
|
+
{
|
|
14
|
+
src,
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
keyboardControls = true,
|
|
18
|
+
showControls = true,
|
|
19
|
+
customControls = false,
|
|
20
|
+
crossOrigin,
|
|
21
|
+
enablePerformanceMonitoring = true,
|
|
22
|
+
bufferingStrategy = "adaptive",
|
|
23
|
+
preloadStrategy = "progressive",
|
|
24
|
+
enableOptimizations = true,
|
|
25
|
+
...options
|
|
26
|
+
},
|
|
27
|
+
ref
|
|
28
|
+
) => {
|
|
29
|
+
// React 19 concurrent features
|
|
30
|
+
const [isPending] = useTransition()
|
|
31
|
+
|
|
32
|
+
// Enhanced media player with optimizations
|
|
33
|
+
const { state, controls, mediaRef, containerRef, performanceMetrics, isEnhanced } = useEnhancedMediaPlayer({
|
|
34
|
+
src,
|
|
35
|
+
crossOrigin,
|
|
36
|
+
enablePerformanceMonitoring,
|
|
37
|
+
bufferingStrategy,
|
|
38
|
+
preloadStrategy,
|
|
39
|
+
enableTimeUpdateThrottling: true,
|
|
40
|
+
timeUpdateThrottleMs: 100,
|
|
41
|
+
enableSeekPreview: true,
|
|
42
|
+
...options
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Memory optimization
|
|
46
|
+
const { memoryStats, addEventListener } = useMemoryOptimization({
|
|
47
|
+
maxMemoryUsage: 50, // 50MB for media player
|
|
48
|
+
cleanupInterval: 15000, // 15 seconds
|
|
49
|
+
enableGC: true,
|
|
50
|
+
monitorMemory: enableOptimizations
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Keyboard controls with performance optimization
|
|
54
|
+
useKeyboardControls({
|
|
55
|
+
enabled: keyboardControls,
|
|
56
|
+
state,
|
|
57
|
+
controls
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Memoize media type detection
|
|
61
|
+
const isVideo = useMemo(() => {
|
|
62
|
+
return Array.isArray(src)
|
|
63
|
+
? src.some((s) => /\.(mp4|webm|ogv|mov|avi)$/i.test(s))
|
|
64
|
+
: /\.(mp4|webm|ogv|mov|avi)$/i.test(src)
|
|
65
|
+
}, [src])
|
|
66
|
+
|
|
67
|
+
// Memoize source rendering
|
|
68
|
+
const renderSources = useMemo(() => {
|
|
69
|
+
if (!Array.isArray(src)) return null
|
|
70
|
+
|
|
71
|
+
return src.map((source, index) => (
|
|
72
|
+
<source key={`${source}-${index}`} src={source} />
|
|
73
|
+
))
|
|
74
|
+
}, [src])
|
|
75
|
+
|
|
76
|
+
// Memoize common props
|
|
77
|
+
const commonProps = useMemo(() => ({
|
|
78
|
+
controls: showControls && !customControls,
|
|
79
|
+
className: cn(
|
|
80
|
+
"w-full transition-all duration-300",
|
|
81
|
+
isVideo && "aspect-video",
|
|
82
|
+
"bg-[color:var(--media-bg)] rounded-lg",
|
|
83
|
+
state.isTransitioning && "opacity-90"
|
|
84
|
+
),
|
|
85
|
+
preload: preloadStrategy === "eager" ? "auto" : "metadata",
|
|
86
|
+
crossOrigin,
|
|
87
|
+
...(Array.isArray(src) ? {} : { src })
|
|
88
|
+
}), [showControls, customControls, isVideo, state.isTransitioning, preloadStrategy, crossOrigin, src])
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<EnhancedMediaWrapper
|
|
92
|
+
priority="high"
|
|
93
|
+
errorFallback={({ error, resetError }) => (
|
|
94
|
+
<div className={cn(
|
|
95
|
+
"absolute inset-0 flex items-center justify-center",
|
|
96
|
+
"glass-strong animate-enter rounded-lg",
|
|
97
|
+
"bg-[color:var(--color-error)]/10 backdrop-blur-md"
|
|
98
|
+
)}>
|
|
99
|
+
<div className="space-y-2 text-center text-[color:var(--media-text)]">
|
|
100
|
+
<div className="mx-auto w-fit rounded-full bg-[color:var(--color-error)]/20 p-3">
|
|
101
|
+
<svg className="size-6 text-[color:var(--color-error)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
102
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
103
|
+
</svg>
|
|
104
|
+
</div>
|
|
105
|
+
<p className="font-semibold text-lg">Playback Error</p>
|
|
106
|
+
<p className="max-w-xs text-[color:var(--media-text-secondary)] text-sm">
|
|
107
|
+
{error.message || state.error}
|
|
108
|
+
</p>
|
|
109
|
+
<button
|
|
110
|
+
onClick={resetError}
|
|
111
|
+
className="mt-2 rounded bg-[color:var(--color-brand-primary)] px-4 py-2 text-sm text-white transition-colors hover:bg-[color:var(--color-brand-accent)]">
|
|
112
|
+
Try Again
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
loadingFallback={
|
|
118
|
+
<div className={cn(
|
|
119
|
+
"absolute inset-0 flex items-center justify-center",
|
|
120
|
+
"glass animate-enter rounded-lg",
|
|
121
|
+
"bg-[color:var(--media-bg)]/20 backdrop-blur-sm"
|
|
122
|
+
)}>
|
|
123
|
+
<div className="space-y-4 text-center text-[color:var(--media-text)]">
|
|
124
|
+
<div className="relative">
|
|
125
|
+
<div className={cn(
|
|
126
|
+
"size-12 animate-spin rounded-full border-2",
|
|
127
|
+
"border-[color:var(--color-brand-accent)]/30",
|
|
128
|
+
"border-t-[color:var(--color-brand-accent)]"
|
|
129
|
+
)}></div>
|
|
130
|
+
<div className={cn(
|
|
131
|
+
"absolute inset-0 size-12 rounded-full",
|
|
132
|
+
"bg-gradient-to-r from-[color:var(--color-brand-primary)] to-[color:var(--color-brand-accent)]",
|
|
133
|
+
"animate-pulse opacity-20 blur-sm"
|
|
134
|
+
)}></div>
|
|
135
|
+
</div>
|
|
136
|
+
<p className="font-medium text-[color:var(--media-text-secondary)] text-sm">
|
|
137
|
+
Loading media...
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
}>
|
|
142
|
+
<div
|
|
143
|
+
ref={ref || containerRef}
|
|
144
|
+
className={cn(
|
|
145
|
+
"media-container relative overflow-hidden contain-strict",
|
|
146
|
+
"bg-[color:var(--media-bg)] focus-within:outline-none",
|
|
147
|
+
"rounded-xl border border-[color:var(--media-border)]/30",
|
|
148
|
+
"shadow-2xl shadow-[color:var(--media-bg)]/50",
|
|
149
|
+
"transition-all duration-300 ease-out",
|
|
150
|
+
"hover:border-[color:var(--media-border)]/50",
|
|
151
|
+
"hover:shadow-3xl hover:shadow-[color:var(--media-bg)]/60",
|
|
152
|
+
// Performance optimizations
|
|
153
|
+
"will-change-transform",
|
|
154
|
+
state.isTransitioning && "cursor-wait",
|
|
155
|
+
className
|
|
156
|
+
)}
|
|
157
|
+
tabIndex={0}
|
|
158
|
+
// Performance attributes
|
|
159
|
+
role="application"
|
|
160
|
+
aria-label={isVideo ? "Video player" : "Audio player"}
|
|
161
|
+
aria-busy={state.isLoading || state.isBuffering}>
|
|
162
|
+
|
|
163
|
+
{isVideo ? (
|
|
164
|
+
<video
|
|
165
|
+
ref={mediaRef as React.RefObject<HTMLVideoElement>}
|
|
166
|
+
{...commonProps}
|
|
167
|
+
// Performance optimizations
|
|
168
|
+
disablePictureInPicture={false}
|
|
169
|
+
playsInline
|
|
170
|
+
x-webkit-airplay="allow"
|
|
171
|
+
style={{ willChange: 'transform' }}
|
|
172
|
+
>
|
|
173
|
+
{renderSources()}
|
|
174
|
+
Your browser does not support the video element.
|
|
175
|
+
</video>
|
|
176
|
+
) : (
|
|
177
|
+
<audio
|
|
178
|
+
ref={mediaRef as React.RefObject<HTMLAudioElement>}
|
|
179
|
+
{...commonProps}
|
|
180
|
+
// Performance optimizations
|
|
181
|
+
preload={preloadStrategy === "eager" ? "auto" : "metadata"}
|
|
182
|
+
>
|
|
183
|
+
{renderSources()}
|
|
184
|
+
Your browser does not support the audio element.
|
|
185
|
+
</audio>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{state.error && (
|
|
189
|
+
<div className={cn(
|
|
190
|
+
"absolute inset-0 flex items-center justify-center",
|
|
191
|
+
"glass-strong animate-enter rounded-lg",
|
|
192
|
+
"bg-[color:var(--color-error)]/10 backdrop-blur-md"
|
|
193
|
+
)}>
|
|
194
|
+
<div className="space-y-2 text-center text-[color:var(--media-text)]">
|
|
195
|
+
<div className="mx-auto w-fit rounded-full bg-[color:var(--color-error)]/20 p-3">
|
|
196
|
+
<svg className="size-6 text-[color:var(--color-error)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
197
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
198
|
+
</svg>
|
|
199
|
+
</div>
|
|
200
|
+
<p className="font-semibold text-lg">Playback Error</p>
|
|
201
|
+
<p className="max-w-xs text-[color:var(--media-text-secondary)] text-sm">
|
|
202
|
+
{state.error}
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{state.isLoading && (
|
|
209
|
+
<div className={cn(
|
|
210
|
+
"absolute inset-0 flex items-center justify-center",
|
|
211
|
+
"glass animate-enter rounded-lg",
|
|
212
|
+
"bg-[color:var(--media-bg)]/20 backdrop-blur-sm"
|
|
213
|
+
)}>
|
|
214
|
+
<div className="space-y-4 text-center text-[color:var(--media-text)]">
|
|
215
|
+
<div className="relative">
|
|
216
|
+
<div className={cn(
|
|
217
|
+
"size-12 animate-spin rounded-full border-2",
|
|
218
|
+
"border-[color:var(--color-brand-accent)]/30",
|
|
219
|
+
"border-t-[color:var(--color-brand-accent)]"
|
|
220
|
+
)}></div>
|
|
221
|
+
<div className={cn(
|
|
222
|
+
"absolute inset-0 size-12 rounded-full",
|
|
223
|
+
"bg-gradient-to-r from-[color:var(--color-brand-primary)] to-[color:var(--color-brand-accent)]",
|
|
224
|
+
"animate-pulse opacity-20 blur-sm"
|
|
225
|
+
)}></div>
|
|
226
|
+
</div>
|
|
227
|
+
<p className="font-medium text-[color:var(--media-text-secondary)] text-sm">
|
|
228
|
+
{state.isBuffering ? "Buffering..." : "Loading media..."}
|
|
229
|
+
</p>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{customControls && typeof children === "function" && (
|
|
235
|
+
<div className="pointer-events-auto absolute inset-0 z-50">
|
|
236
|
+
{children({ state, controls })}
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{customControls && typeof children !== "function" && (
|
|
241
|
+
<div className="pointer-events-auto absolute inset-0 z-50">{children}</div>
|
|
242
|
+
)}
|
|
243
|
+
|
|
244
|
+
{/* Performance indicator for development */}
|
|
245
|
+
{process.env.NODE_ENV === 'development' && enablePerformanceMonitoring && (
|
|
246
|
+
<div className="absolute bottom-2 left-2 rounded bg-black/50 px-2 py-1 text-white text-xs">
|
|
247
|
+
<div>FPS: {performanceMetrics.frameRate || 0}</div>
|
|
248
|
+
<div>Memory: {memoryStats.usage.toFixed(1)}MB</div>
|
|
249
|
+
{isEnhanced && <div>Enhanced ✓</div>}
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</EnhancedMediaWrapper>
|
|
254
|
+
)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
MediaPlayer.displayName = "MediaPlayer"
|