@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,349 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { useMediaContext, useMediaSelector, useMediaDispatch, MediaActionTypes } from "../../context/media-context"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
// Enhanced media player with better performance and mobile support
|
|
8
|
+
export interface EnhancedMediaPlayerProps {
|
|
9
|
+
children: React.ReactNode
|
|
10
|
+
className?: string
|
|
11
|
+
autoHide?: boolean
|
|
12
|
+
autoHideDelay?: number
|
|
13
|
+
showControlsOnHover?: boolean
|
|
14
|
+
showControlsOnTouch?: boolean
|
|
15
|
+
enableKeyboardControls?: boolean
|
|
16
|
+
enableTouchGestures?: boolean
|
|
17
|
+
onPlay?: () => void
|
|
18
|
+
onPause?: () => void
|
|
19
|
+
onTimeUpdate?: (time: number) => void
|
|
20
|
+
onVolumeChange?: (volume: number) => void
|
|
21
|
+
onFullscreenChange?: (isFullscreen: boolean) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Custom hook for auto-hide controls
|
|
25
|
+
function useAutoHideControls(
|
|
26
|
+
autoHide: boolean,
|
|
27
|
+
delay: number,
|
|
28
|
+
showOnHover: boolean,
|
|
29
|
+
showOnTouch: boolean
|
|
30
|
+
) {
|
|
31
|
+
const [showControls, setShowControls] = React.useState(true)
|
|
32
|
+
const [isHovering, setIsHovering] = React.useState(false)
|
|
33
|
+
const [isTouching, setIsTouching] = React.useState(false)
|
|
34
|
+
const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
|
35
|
+
|
|
36
|
+
const resetTimeout = React.useCallback(() => {
|
|
37
|
+
if (timeoutRef.current) {
|
|
38
|
+
clearTimeout(timeoutRef.current)
|
|
39
|
+
}
|
|
40
|
+
if (autoHide) {
|
|
41
|
+
timeoutRef.current = setTimeout(() => {
|
|
42
|
+
if (!isHovering && !isTouching) {
|
|
43
|
+
setShowControls(false)
|
|
44
|
+
}
|
|
45
|
+
}, delay)
|
|
46
|
+
}
|
|
47
|
+
}, [autoHide, delay, isHovering, isTouching])
|
|
48
|
+
|
|
49
|
+
const showControlsTemporarily = React.useCallback(() => {
|
|
50
|
+
setShowControls(true)
|
|
51
|
+
resetTimeout()
|
|
52
|
+
}, [resetTimeout])
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (autoHide) {
|
|
56
|
+
resetTimeout()
|
|
57
|
+
} else {
|
|
58
|
+
setShowControls(true)
|
|
59
|
+
}
|
|
60
|
+
return () => {
|
|
61
|
+
if (timeoutRef.current) {
|
|
62
|
+
clearTimeout(timeoutRef.current)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}, [autoHide, resetTimeout])
|
|
66
|
+
|
|
67
|
+
const handleMouseEnter = React.useCallback(() => {
|
|
68
|
+
if (showOnHover) {
|
|
69
|
+
setIsHovering(true)
|
|
70
|
+
setShowControls(true)
|
|
71
|
+
}
|
|
72
|
+
}, [showOnHover])
|
|
73
|
+
|
|
74
|
+
const handleMouseLeave = React.useCallback(() => {
|
|
75
|
+
if (showOnHover) {
|
|
76
|
+
setIsHovering(false)
|
|
77
|
+
resetTimeout()
|
|
78
|
+
}
|
|
79
|
+
}, [showOnHover, resetTimeout])
|
|
80
|
+
|
|
81
|
+
const handleTouchStart = React.useCallback(() => {
|
|
82
|
+
if (showOnTouch) {
|
|
83
|
+
setIsTouching(true)
|
|
84
|
+
showControlsTemporarily()
|
|
85
|
+
}
|
|
86
|
+
}, [showOnTouch, showControlsTemporarily])
|
|
87
|
+
|
|
88
|
+
const handleTouchEnd = React.useCallback(() => {
|
|
89
|
+
if (showOnTouch) {
|
|
90
|
+
setIsTouching(false)
|
|
91
|
+
resetTimeout()
|
|
92
|
+
}
|
|
93
|
+
}, [showOnTouch, resetTimeout])
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
showControls,
|
|
97
|
+
showControlsTemporarily,
|
|
98
|
+
handleMouseEnter,
|
|
99
|
+
handleMouseLeave,
|
|
100
|
+
handleTouchStart,
|
|
101
|
+
handleTouchEnd
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Custom hook for touch gestures
|
|
106
|
+
function useTouchGestures(enabled: boolean) {
|
|
107
|
+
const dispatch = useMediaDispatch()
|
|
108
|
+
const isPlaying = useMediaSelector(state => state.isPlaying)
|
|
109
|
+
const currentTime = useMediaSelector(state => state.currentTime)
|
|
110
|
+
const duration = useMediaSelector(state => state.duration)
|
|
111
|
+
const volume = useMediaSelector(state => state.volume)
|
|
112
|
+
|
|
113
|
+
const [touchStart, setTouchStart] = React.useState<{ x: number; y: number; time: number } | null>(null)
|
|
114
|
+
const [isDoubleTap, setIsDoubleTap] = React.useState(false)
|
|
115
|
+
|
|
116
|
+
const handleTouchStart = React.useCallback((e: React.TouchEvent) => {
|
|
117
|
+
if (!enabled) return
|
|
118
|
+
|
|
119
|
+
const touch = e.touches[0]
|
|
120
|
+
if (!touch) return
|
|
121
|
+
|
|
122
|
+
const now = Date.now()
|
|
123
|
+
|
|
124
|
+
if (touchStart && now - touchStart.time < 300) {
|
|
125
|
+
setIsDoubleTap(true)
|
|
126
|
+
// Double tap to play/pause
|
|
127
|
+
dispatch({
|
|
128
|
+
type: isPlaying ? MediaActionTypes.PAUSE : MediaActionTypes.PLAY
|
|
129
|
+
})
|
|
130
|
+
} else {
|
|
131
|
+
setTouchStart({
|
|
132
|
+
x: touch.clientX,
|
|
133
|
+
y: touch.clientY,
|
|
134
|
+
time: now
|
|
135
|
+
})
|
|
136
|
+
setIsDoubleTap(false)
|
|
137
|
+
}
|
|
138
|
+
}, [enabled, touchStart, isPlaying, dispatch])
|
|
139
|
+
|
|
140
|
+
const handleTouchMove = React.useCallback((e: React.TouchEvent) => {
|
|
141
|
+
if (!enabled || !touchStart || isDoubleTap) return
|
|
142
|
+
|
|
143
|
+
const touch = e.touches[0]
|
|
144
|
+
if (!touch) return
|
|
145
|
+
|
|
146
|
+
const deltaX = touch.clientX - touchStart.x
|
|
147
|
+
const deltaY = touch.clientY - touchStart.y
|
|
148
|
+
|
|
149
|
+
// Horizontal swipe for seeking
|
|
150
|
+
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
|
|
151
|
+
const seekAmount = (deltaX / window.innerWidth) * 30 // 30 seconds max
|
|
152
|
+
const newTime = Math.max(0, Math.min(duration, currentTime + seekAmount))
|
|
153
|
+
dispatch({
|
|
154
|
+
type: MediaActionTypes.SEEK,
|
|
155
|
+
detail: newTime
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Vertical swipe for volume (right side of screen)
|
|
160
|
+
if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 50 && touchStart.x > window.innerWidth / 2) {
|
|
161
|
+
const volumeChange = -(deltaY / window.innerHeight) * 1 // Full range
|
|
162
|
+
const newVolume = Math.max(0, Math.min(1, volume + volumeChange))
|
|
163
|
+
dispatch({
|
|
164
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
165
|
+
detail: newVolume
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
}, [enabled, touchStart, isDoubleTap, duration, currentTime, volume, dispatch])
|
|
169
|
+
|
|
170
|
+
const handleTouchEnd = React.useCallback(() => {
|
|
171
|
+
if (!enabled) return
|
|
172
|
+
// Reset touch state after a delay to allow for double tap detection
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
setTouchStart(null)
|
|
175
|
+
setIsDoubleTap(false)
|
|
176
|
+
}, 300)
|
|
177
|
+
}, [enabled])
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
handleTouchStart,
|
|
181
|
+
handleTouchMove,
|
|
182
|
+
handleTouchEnd
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const EnhancedMediaPlayer = function EnhancedMediaPlayer({
|
|
187
|
+
children,
|
|
188
|
+
className,
|
|
189
|
+
autoHide = true,
|
|
190
|
+
autoHideDelay = 3000,
|
|
191
|
+
showControlsOnHover = true,
|
|
192
|
+
showControlsOnTouch = true,
|
|
193
|
+
enableKeyboardControls = true,
|
|
194
|
+
enableTouchGestures = true,
|
|
195
|
+
onPlay,
|
|
196
|
+
onPause,
|
|
197
|
+
onTimeUpdate,
|
|
198
|
+
onVolumeChange,
|
|
199
|
+
onFullscreenChange
|
|
200
|
+
}: EnhancedMediaPlayerProps) {
|
|
201
|
+
const context = useMediaContext()
|
|
202
|
+
const dispatch = useMediaDispatch()
|
|
203
|
+
|
|
204
|
+
// State selectors with memoization
|
|
205
|
+
const isPlaying = useMediaSelector(React.useCallback(state => state.isPlaying, []))
|
|
206
|
+
const isFullscreen = useMediaSelector(React.useCallback(state => state.isFullscreen, []))
|
|
207
|
+
const currentTime = useMediaSelector(React.useCallback(state => state.currentTime, []))
|
|
208
|
+
const volume = useMediaSelector(React.useCallback(state => state.volume, []))
|
|
209
|
+
const error = useMediaSelector(React.useCallback(state => state.error, []))
|
|
210
|
+
|
|
211
|
+
// Custom hooks
|
|
212
|
+
const {
|
|
213
|
+
showControls,
|
|
214
|
+
showControlsTemporarily,
|
|
215
|
+
handleMouseEnter,
|
|
216
|
+
handleMouseLeave,
|
|
217
|
+
handleTouchStart: handleControlsTouch,
|
|
218
|
+
handleTouchEnd: handleControlsTouchEnd
|
|
219
|
+
} = useAutoHideControls(autoHide, autoHideDelay, showControlsOnHover, showControlsOnTouch)
|
|
220
|
+
|
|
221
|
+
const {
|
|
222
|
+
handleTouchStart: handleGestureStart,
|
|
223
|
+
handleTouchMove: handleGestureMove,
|
|
224
|
+
handleTouchEnd: handleGestureEnd
|
|
225
|
+
} = useTouchGestures(enableTouchGestures)
|
|
226
|
+
|
|
227
|
+
// Combine touch handlers
|
|
228
|
+
const handleTouchStart = React.useCallback((e: React.TouchEvent) => {
|
|
229
|
+
handleControlsTouch()
|
|
230
|
+
handleGestureStart(e)
|
|
231
|
+
}, [handleControlsTouch, handleGestureStart])
|
|
232
|
+
|
|
233
|
+
const handleTouchEnd = React.useCallback(() => {
|
|
234
|
+
handleControlsTouchEnd()
|
|
235
|
+
handleGestureEnd()
|
|
236
|
+
}, [handleControlsTouchEnd, handleGestureEnd])
|
|
237
|
+
|
|
238
|
+
// Event handlers with memoization
|
|
239
|
+
const handleClick = React.useCallback(() => {
|
|
240
|
+
showControlsTemporarily()
|
|
241
|
+
}, [showControlsTemporarily])
|
|
242
|
+
|
|
243
|
+
// Keyboard controls
|
|
244
|
+
React.useEffect(() => {
|
|
245
|
+
if (!enableKeyboardControls) return
|
|
246
|
+
|
|
247
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
248
|
+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
switch (e.code) {
|
|
253
|
+
case 'Space':
|
|
254
|
+
case 'KeyK':
|
|
255
|
+
e.preventDefault()
|
|
256
|
+
dispatch({
|
|
257
|
+
type: isPlaying ? MediaActionTypes.PAUSE : MediaActionTypes.PLAY
|
|
258
|
+
})
|
|
259
|
+
break
|
|
260
|
+
case 'KeyF':
|
|
261
|
+
e.preventDefault()
|
|
262
|
+
dispatch({
|
|
263
|
+
type: isFullscreen ? MediaActionTypes.EXIT_FULLSCREEN : MediaActionTypes.ENTER_FULLSCREEN
|
|
264
|
+
})
|
|
265
|
+
break
|
|
266
|
+
case 'KeyM':
|
|
267
|
+
e.preventDefault()
|
|
268
|
+
dispatch({ type: MediaActionTypes.TOGGLE_MUTE })
|
|
269
|
+
break
|
|
270
|
+
case 'ArrowLeft':
|
|
271
|
+
e.preventDefault()
|
|
272
|
+
dispatch({
|
|
273
|
+
type: MediaActionTypes.SEEK,
|
|
274
|
+
detail: Math.max(0, currentTime - 10)
|
|
275
|
+
})
|
|
276
|
+
break
|
|
277
|
+
case 'ArrowRight':
|
|
278
|
+
e.preventDefault()
|
|
279
|
+
dispatch({
|
|
280
|
+
type: MediaActionTypes.SEEK,
|
|
281
|
+
detail: currentTime + 10
|
|
282
|
+
})
|
|
283
|
+
break
|
|
284
|
+
case 'ArrowUp':
|
|
285
|
+
e.preventDefault()
|
|
286
|
+
dispatch({
|
|
287
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
288
|
+
detail: Math.min(1, volume + 0.1)
|
|
289
|
+
})
|
|
290
|
+
break
|
|
291
|
+
case 'ArrowDown':
|
|
292
|
+
e.preventDefault()
|
|
293
|
+
dispatch({
|
|
294
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
295
|
+
detail: Math.max(0, volume - 0.1)
|
|
296
|
+
})
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
302
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
303
|
+
}, [enableKeyboardControls, dispatch, isPlaying, isFullscreen, currentTime, volume])
|
|
304
|
+
|
|
305
|
+
// Event callbacks
|
|
306
|
+
React.useEffect(() => {
|
|
307
|
+
if (isPlaying) onPlay?.()
|
|
308
|
+
else onPause?.()
|
|
309
|
+
}, [isPlaying, onPlay, onPause])
|
|
310
|
+
|
|
311
|
+
React.useEffect(() => {
|
|
312
|
+
onTimeUpdate?.(currentTime)
|
|
313
|
+
}, [currentTime, onTimeUpdate])
|
|
314
|
+
|
|
315
|
+
React.useEffect(() => {
|
|
316
|
+
onVolumeChange?.(volume)
|
|
317
|
+
}, [volume, onVolumeChange])
|
|
318
|
+
|
|
319
|
+
React.useEffect(() => {
|
|
320
|
+
onFullscreenChange?.(isFullscreen)
|
|
321
|
+
}, [isFullscreen, onFullscreenChange])
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<div
|
|
325
|
+
ref={context.rootRef}
|
|
326
|
+
className={cn(
|
|
327
|
+
"relative isolate flex flex-col overflow-hidden rounded-lg bg-black outline-none",
|
|
328
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
329
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
330
|
+
isFullscreen && "fixed inset-0 z-50 rounded-none",
|
|
331
|
+
className
|
|
332
|
+
)}
|
|
333
|
+
data-state={isFullscreen ? "fullscreen" : "inline"}
|
|
334
|
+
data-disabled={error ? "" : undefined}
|
|
335
|
+
data-controls-visible={showControls ? "" : undefined}
|
|
336
|
+
tabIndex={0}
|
|
337
|
+
onMouseEnter={handleMouseEnter}
|
|
338
|
+
onMouseLeave={handleMouseLeave}
|
|
339
|
+
onTouchStart={handleTouchStart}
|
|
340
|
+
onTouchMove={handleGestureMove}
|
|
341
|
+
onTouchEnd={handleTouchEnd}
|
|
342
|
+
onClick={handleClick}
|
|
343
|
+
>
|
|
344
|
+
{children}
|
|
345
|
+
</div>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
EnhancedMediaPlayer.displayName = "EnhancedMediaPlayer"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { MediaPlayerRoot } from "../media-player-root"
|
|
5
|
+
import { MediaPlayerVideo } from "../media-player-video"
|
|
6
|
+
import { MediaPlayerControls, MediaPlayerControlsOverlay } from "../media-player-controls"
|
|
7
|
+
import { MediaPlayerPlay } from "../media-player-play"
|
|
8
|
+
import { MediaPlayerSeek } from "../media-player-seek"
|
|
9
|
+
import { MediaPlayerVolume } from "../media-player-volume"
|
|
10
|
+
import { MediaPlayerTime } from "../media-player-time"
|
|
11
|
+
import { MediaPlayerFullscreen } from "../media-player-fullscreen"
|
|
12
|
+
import { MediaPlayerLoading } from "../media-player-loading"
|
|
13
|
+
import { MediaPlayerError } from "../media-player-error"
|
|
14
|
+
|
|
15
|
+
export interface BasicVideoPlayerProps {
|
|
16
|
+
src: string
|
|
17
|
+
poster?: string
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function BasicVideoPlayer({ src, poster, className }: BasicVideoPlayerProps) {
|
|
22
|
+
return (
|
|
23
|
+
<MediaPlayerRoot className={className}>
|
|
24
|
+
<MediaPlayerVideo
|
|
25
|
+
src={src}
|
|
26
|
+
poster={poster}
|
|
27
|
+
crossOrigin="anonymous"
|
|
28
|
+
playsInline
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
<MediaPlayerLoading />
|
|
32
|
+
<MediaPlayerError />
|
|
33
|
+
|
|
34
|
+
<MediaPlayerControlsOverlay />
|
|
35
|
+
<MediaPlayerControls>
|
|
36
|
+
<div className="flex items-center gap-2">
|
|
37
|
+
<MediaPlayerPlay />
|
|
38
|
+
<MediaPlayerVolume />
|
|
39
|
+
<MediaPlayerTime variant="current" />
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div className="flex-1 px-2">
|
|
43
|
+
<MediaPlayerSeek />
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<MediaPlayerTime variant="duration" />
|
|
48
|
+
<MediaPlayerFullscreen />
|
|
49
|
+
</div>
|
|
50
|
+
</MediaPlayerControls>
|
|
51
|
+
</MediaPlayerRoot>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
BasicVideoPlayer.displayName = "BasicVideoPlayer"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { MediaPlayer } from "../media-player"
|
|
4
|
+
import { FullscreenButton } from "../ui/fullscreen-button"
|
|
5
|
+
import { PlayButton } from "../ui/play-button"
|
|
6
|
+
import { SeekBar } from "../ui/seek-bar"
|
|
7
|
+
import { TimeDisplay } from "../ui/time-display"
|
|
8
|
+
import { VolumeControl } from "../ui/volume-control"
|
|
9
|
+
|
|
10
|
+
export interface CustomControlsExampleProps {
|
|
11
|
+
src: string
|
|
12
|
+
poster?: string
|
|
13
|
+
className?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CustomControlsExample({ src, poster, className }: CustomControlsExampleProps) {
|
|
17
|
+
return (
|
|
18
|
+
<MediaPlayer src={src} customControls={true} showControls={false} className={className}>
|
|
19
|
+
{({ state, controls }) => (
|
|
20
|
+
<div className="absolute inset-0 flex flex-col justify-end">
|
|
21
|
+
{/* Background gradient */}
|
|
22
|
+
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent" />
|
|
23
|
+
|
|
24
|
+
{/* Controls container */}
|
|
25
|
+
<div className="relative z-10 space-y-3 p-4">
|
|
26
|
+
{/* Seek bar */}
|
|
27
|
+
<div className="w-full">
|
|
28
|
+
<SeekBar
|
|
29
|
+
currentTime={state.currentTime}
|
|
30
|
+
duration={state.duration}
|
|
31
|
+
onSeek={controls.seek}
|
|
32
|
+
className="w-full"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
{/* Bottom controls */}
|
|
37
|
+
<div className="flex items-center justify-between">
|
|
38
|
+
<div className="flex items-center gap-3">
|
|
39
|
+
<PlayButton isPlaying={state.isPlaying} onToggle={controls.toggle} size="lg" />
|
|
40
|
+
<VolumeControl
|
|
41
|
+
volume={state.volume}
|
|
42
|
+
isMuted={state.isMuted}
|
|
43
|
+
onVolumeChange={controls.setVolume}
|
|
44
|
+
onToggleMute={controls.toggleMute}
|
|
45
|
+
/>
|
|
46
|
+
<TimeDisplay currentTime={state.currentTime} duration={state.duration} />
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<FullscreenButton
|
|
51
|
+
isFullscreen={state.isFullscreen}
|
|
52
|
+
onToggle={controls.toggleFullscreen}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
</MediaPlayer>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
CustomControlsExample.displayName = "CustomControlsExample"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { MediaProvider } from "../../context/media-provider"
|
|
5
|
+
import { MediaPlayerRoot } from "../media-player-root"
|
|
6
|
+
import { MediaPlayerVideo } from "../media-player-video"
|
|
7
|
+
import {
|
|
8
|
+
EnhancedControlBar,
|
|
9
|
+
EnhancedPlayButton,
|
|
10
|
+
EnhancedSeekBar,
|
|
11
|
+
EnhancedBackwardButton,
|
|
12
|
+
EnhancedForwardButton,
|
|
13
|
+
EnhancedVolumeControl,
|
|
14
|
+
EnhancedTimeDisplay
|
|
15
|
+
} from "../enhanced/enhanced-controls"
|
|
16
|
+
|
|
17
|
+
export interface EnhancedControlsExampleProps {
|
|
18
|
+
src: string
|
|
19
|
+
poster?: string
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Complete example with all enhanced controls
|
|
24
|
+
export function EnhancedControlsExample({
|
|
25
|
+
src,
|
|
26
|
+
poster,
|
|
27
|
+
className
|
|
28
|
+
}: EnhancedControlsExampleProps) {
|
|
29
|
+
return (
|
|
30
|
+
<MediaProvider>
|
|
31
|
+
<MediaPlayerRoot className={className}>
|
|
32
|
+
<MediaPlayerVideo src={src} poster={poster} />
|
|
33
|
+
|
|
34
|
+
{/* Complete control bar with all features */}
|
|
35
|
+
<EnhancedControlBar
|
|
36
|
+
className="absolute right-0 bottom-0 left-0"
|
|
37
|
+
showSkipButtons={true}
|
|
38
|
+
showVolumeControl={true}
|
|
39
|
+
showTimeDisplay={true}
|
|
40
|
+
playButtonSize="xl"
|
|
41
|
+
skipButtonSize="md"
|
|
42
|
+
skipTime={10}
|
|
43
|
+
/>
|
|
44
|
+
</MediaPlayerRoot>
|
|
45
|
+
</MediaProvider>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Example with individual components for custom layouts
|
|
50
|
+
export function CustomEnhancedControlsExample({
|
|
51
|
+
src,
|
|
52
|
+
poster,
|
|
53
|
+
className
|
|
54
|
+
}: EnhancedControlsExampleProps) {
|
|
55
|
+
return (
|
|
56
|
+
<MediaProvider>
|
|
57
|
+
<MediaPlayerRoot className={className}>
|
|
58
|
+
<MediaPlayerVideo src={src} poster={poster} />
|
|
59
|
+
|
|
60
|
+
<div className="absolute right-0 bottom-0 left-0 bg-gradient-to-t from-black/80 to-transparent p-4">
|
|
61
|
+
{/* Seek bar */}
|
|
62
|
+
<EnhancedSeekBar className="mb-4" />
|
|
63
|
+
|
|
64
|
+
{/* Custom control layout */}
|
|
65
|
+
<div className="flex items-center justify-between">
|
|
66
|
+
{/* Left side controls */}
|
|
67
|
+
<div className="flex items-center gap-3">
|
|
68
|
+
<EnhancedBackwardButton skipTime={15} size="md" />
|
|
69
|
+
<EnhancedPlayButton size="xl" />
|
|
70
|
+
<EnhancedForwardButton skipTime={15} size="md" />
|
|
71
|
+
<EnhancedVolumeControl />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* Right side info */}
|
|
75
|
+
<div className="flex items-center gap-2 font-mono text-sm text-white">
|
|
76
|
+
<EnhancedTimeDisplay variant="current" />
|
|
77
|
+
<span className="text-white/60">/</span>
|
|
78
|
+
<EnhancedTimeDisplay variant="duration" />
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</MediaPlayerRoot>
|
|
83
|
+
</MediaProvider>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Minimal example with just skip buttons
|
|
88
|
+
export function SkipButtonsOnlyExample({
|
|
89
|
+
src,
|
|
90
|
+
poster,
|
|
91
|
+
className
|
|
92
|
+
}: EnhancedControlsExampleProps) {
|
|
93
|
+
return (
|
|
94
|
+
<MediaProvider>
|
|
95
|
+
<MediaPlayerRoot className={className}>
|
|
96
|
+
<MediaPlayerVideo src={src} poster={poster} />
|
|
97
|
+
|
|
98
|
+
<div className="-translate-x-1/2 absolute bottom-4 left-1/2 transform">
|
|
99
|
+
<div className="flex items-center gap-4">
|
|
100
|
+
<EnhancedBackwardButton
|
|
101
|
+
skipTime={10}
|
|
102
|
+
size="lg"
|
|
103
|
+
/>
|
|
104
|
+
<EnhancedPlayButton size="xl" />
|
|
105
|
+
<EnhancedForwardButton
|
|
106
|
+
skipTime={10}
|
|
107
|
+
size="lg"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</MediaPlayerRoot>
|
|
112
|
+
</MediaProvider>
|
|
113
|
+
)
|
|
114
|
+
}
|