@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,470 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef, useState, useCallback } from "react"
|
|
4
|
+
import "../styles/videojs.css"
|
|
5
|
+
import type VideoJsPlayer from "video.js/dist/types/player"
|
|
6
|
+
import { MediaActionTypes, useMediaContext, useMediaDispatch } from "../context/media-context"
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
|
|
9
|
+
// Lazy load Video.js and HLS.js for better performance
|
|
10
|
+
let videojs: any = null
|
|
11
|
+
let Hls: any = null
|
|
12
|
+
|
|
13
|
+
async function loadVideoJS() {
|
|
14
|
+
if (!videojs) {
|
|
15
|
+
const [videojsModule, hlsModule] = await Promise.all([
|
|
16
|
+
import("video.js"),
|
|
17
|
+
import("hls.js")
|
|
18
|
+
])
|
|
19
|
+
videojs = videojsModule.default
|
|
20
|
+
Hls = hlsModule.default
|
|
21
|
+
|
|
22
|
+
// Import plugins after Video.js is loaded
|
|
23
|
+
await Promise.all([
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
import("@videojs/http-streaming"),
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
import("videojs-contrib-quality-levels"),
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
import("videojs-hls-quality-selector")
|
|
30
|
+
])
|
|
31
|
+
}
|
|
32
|
+
return { videojs, Hls }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VideoSource {
|
|
36
|
+
src: string
|
|
37
|
+
type: string
|
|
38
|
+
label?: string
|
|
39
|
+
selected?: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TextTrack {
|
|
43
|
+
src: string
|
|
44
|
+
kind: "subtitles" | "captions" | "descriptions" | "chapters" | "metadata"
|
|
45
|
+
srclang: string
|
|
46
|
+
label: string
|
|
47
|
+
default?: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface VideoJSAdapterProps {
|
|
51
|
+
sources: VideoSource[]
|
|
52
|
+
poster?: string
|
|
53
|
+
textTracks?: TextTrack[]
|
|
54
|
+
autoplay?: boolean
|
|
55
|
+
muted?: boolean
|
|
56
|
+
controls?: boolean
|
|
57
|
+
preload?: "auto" | "metadata" | "none"
|
|
58
|
+
fluid?: boolean
|
|
59
|
+
responsive?: boolean
|
|
60
|
+
playbackRates?: number[]
|
|
61
|
+
className?: string
|
|
62
|
+
onReady?: (player: VideoJsPlayer) => void
|
|
63
|
+
onError?: (error: any) => void
|
|
64
|
+
hlsConfig?: any // HLS.js config
|
|
65
|
+
enableQualitySelector?: boolean
|
|
66
|
+
enableSubtitles?: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function VideoJSAdapter({
|
|
70
|
+
sources,
|
|
71
|
+
poster,
|
|
72
|
+
textTracks = [],
|
|
73
|
+
autoplay = false,
|
|
74
|
+
muted = false,
|
|
75
|
+
controls = false,
|
|
76
|
+
preload = "metadata",
|
|
77
|
+
fluid = true,
|
|
78
|
+
responsive = true,
|
|
79
|
+
playbackRates = [0.5, 0.75, 1, 1.25, 1.5, 2],
|
|
80
|
+
className,
|
|
81
|
+
onReady,
|
|
82
|
+
onError,
|
|
83
|
+
hlsConfig = {},
|
|
84
|
+
enableQualitySelector = true,
|
|
85
|
+
enableSubtitles = true
|
|
86
|
+
}: VideoJSAdapterProps) {
|
|
87
|
+
const videoRef = useRef<HTMLVideoElement>(null)
|
|
88
|
+
const playerRef = useRef<VideoJsPlayer | null>(null)
|
|
89
|
+
const hlsRef = useRef<any | null>(null) // HLS.js instance
|
|
90
|
+
const [isLoading, setIsLoading] = useState(true)
|
|
91
|
+
const [error, setError] = useState<string | null>(null)
|
|
92
|
+
|
|
93
|
+
const context = useMediaContext()
|
|
94
|
+
const dispatch = useMediaDispatch()
|
|
95
|
+
|
|
96
|
+
// Initialize Video.js player
|
|
97
|
+
const initializePlayer = async () => {
|
|
98
|
+
if (!videoRef.current || playerRef.current) return
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
setIsLoading(true)
|
|
102
|
+
const { videojs, Hls: HlsLib } = await loadVideoJS()
|
|
103
|
+
|
|
104
|
+
// Check if we need HLS.js (for non-Safari browsers)
|
|
105
|
+
const needsHlsJs = sources.some(s => s.type === "application/x-mpegURL") &&
|
|
106
|
+
!videoRef.current.canPlayType("application/vnd.apple.mpegurl")
|
|
107
|
+
|
|
108
|
+
// Video.js options
|
|
109
|
+
const options: any = {
|
|
110
|
+
autoplay,
|
|
111
|
+
muted,
|
|
112
|
+
controls,
|
|
113
|
+
preload,
|
|
114
|
+
fluid,
|
|
115
|
+
responsive,
|
|
116
|
+
playbackRates,
|
|
117
|
+
sources: needsHlsJs ? [] : sources, // Don't set sources if using HLS.js
|
|
118
|
+
poster,
|
|
119
|
+
html5: {
|
|
120
|
+
vhs: {
|
|
121
|
+
// Video.js HTTP Streaming options
|
|
122
|
+
overrideNative: !videoRef.current.canPlayType("application/vnd.apple.mpegurl"),
|
|
123
|
+
enableLowInitialPlaylist: true,
|
|
124
|
+
fastQualityChange: true,
|
|
125
|
+
handleManifestRedirects: true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Create Video.js player
|
|
131
|
+
const player = videojs(videoRef.current, options, function() {
|
|
132
|
+
console.log("Video.js player ready")
|
|
133
|
+
}) as VideoJsPlayer
|
|
134
|
+
|
|
135
|
+
playerRef.current = player
|
|
136
|
+
|
|
137
|
+
// Add text tracks
|
|
138
|
+
textTracks.forEach(track => {
|
|
139
|
+
player.addRemoteTextTrack({
|
|
140
|
+
kind: track.kind,
|
|
141
|
+
src: track.src,
|
|
142
|
+
srclang: track.srclang,
|
|
143
|
+
label: track.label,
|
|
144
|
+
default: track.default
|
|
145
|
+
}, false)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// Setup HLS.js for non-Safari browsers if needed
|
|
149
|
+
if (needsHlsJs) {
|
|
150
|
+
const hlsSource = sources.find(s => s.type === "application/x-mpegURL")
|
|
151
|
+
if (hlsSource) {
|
|
152
|
+
const hls = new HlsLib({
|
|
153
|
+
debug: false,
|
|
154
|
+
enableWorker: true,
|
|
155
|
+
lowLatencyMode: false,
|
|
156
|
+
backBufferLength: 30,
|
|
157
|
+
maxBufferLength: 30,
|
|
158
|
+
maxMaxBufferLength: 600,
|
|
159
|
+
maxBufferSize: 60 * 1000 * 1000,
|
|
160
|
+
maxBufferHole: 0.1,
|
|
161
|
+
highBufferWatchdogPeriod: 2,
|
|
162
|
+
// ABR settings
|
|
163
|
+
abrEwmaFastLive: 3.0,
|
|
164
|
+
abrEwmaSlowLive: 9.0,
|
|
165
|
+
abrEwmaFastVoD: 3.0,
|
|
166
|
+
abrEwmaSlowVoD: 9.0,
|
|
167
|
+
abrEwmaDefaultEstimate: 500000,
|
|
168
|
+
abrBandWidthFactor: 0.95,
|
|
169
|
+
abrBandWidthUpFactor: 0.7,
|
|
170
|
+
abrMaxWithRealBitrate: false,
|
|
171
|
+
maxStarvationDelay: 4,
|
|
172
|
+
maxLoadingDelay: 4,
|
|
173
|
+
capLevelToPlayerSize: true,
|
|
174
|
+
capLevelOnFPSDrop: true,
|
|
175
|
+
// Subtitle settings
|
|
176
|
+
renderTextTracksNatively: false,
|
|
177
|
+
subtitlePreference: {
|
|
178
|
+
lang: navigator.language.split("-")[0],
|
|
179
|
+
forced: false
|
|
180
|
+
},
|
|
181
|
+
...hlsConfig
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
hlsRef.current = hls
|
|
185
|
+
|
|
186
|
+
// Attach HLS to video element
|
|
187
|
+
hls.loadSource(hlsSource.src)
|
|
188
|
+
hls.attachMedia(videoRef.current)
|
|
189
|
+
|
|
190
|
+
// HLS.js event handlers
|
|
191
|
+
hls.on(HlsLib.Events.MANIFEST_PARSED, () => {
|
|
192
|
+
console.log("HLS manifest parsed", {
|
|
193
|
+
levels: hls.levels.map((l: any) => ({
|
|
194
|
+
height: l.height,
|
|
195
|
+
bitrate: l.bitrate,
|
|
196
|
+
codec: l.codecSet
|
|
197
|
+
}))
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Add quality levels to Video.js
|
|
201
|
+
if (enableQualitySelector && (player as any).qualityLevels) {
|
|
202
|
+
const qualityLevels = (player as any).qualityLevels()
|
|
203
|
+
|
|
204
|
+
hls.levels.forEach((level: any, index: number) => {
|
|
205
|
+
qualityLevels.addQualityLevel({
|
|
206
|
+
id: index.toString(),
|
|
207
|
+
label: `${level.height}p`,
|
|
208
|
+
width: level.width,
|
|
209
|
+
height: level.height,
|
|
210
|
+
bitrate: level.bitrate,
|
|
211
|
+
enabled: true
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
hls.on(HlsLib.Events.LEVEL_SWITCHED, (_event: any, data: any) => {
|
|
218
|
+
console.log(`HLS quality switched to level ${data.level}`)
|
|
219
|
+
dispatch({
|
|
220
|
+
type: MediaActionTypes.SET_QUALITY_LEVEL,
|
|
221
|
+
detail: data.level
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
hls.on(HlsLib.Events.ERROR, (_event: any, data: any) => {
|
|
226
|
+
if (data.fatal) {
|
|
227
|
+
switch (data.type) {
|
|
228
|
+
case HlsLib.ErrorTypes.NETWORK_ERROR:
|
|
229
|
+
console.error("Fatal network error encountered, try to recover")
|
|
230
|
+
hls.startLoad()
|
|
231
|
+
break
|
|
232
|
+
case HlsLib.ErrorTypes.MEDIA_ERROR:
|
|
233
|
+
console.error("Fatal media error encountered, try to recover")
|
|
234
|
+
hls.recoverMediaError()
|
|
235
|
+
break
|
|
236
|
+
default:
|
|
237
|
+
console.error("Fatal error, cannot recover")
|
|
238
|
+
setError("Streaming error occurred")
|
|
239
|
+
onError?.(data)
|
|
240
|
+
hls.destroy()
|
|
241
|
+
break
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// Update bandwidth estimate in context
|
|
247
|
+
hls.on(HlsLib.Events.FRAG_LOADED, () => {
|
|
248
|
+
const estimate = hls.bandwidthEstimate
|
|
249
|
+
if (estimate) {
|
|
250
|
+
dispatch({
|
|
251
|
+
type: MediaActionTypes.SET_BANDWIDTH_ESTIMATE,
|
|
252
|
+
detail: estimate
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Enable quality selector plugin
|
|
260
|
+
if (enableQualitySelector && (player as any).qualityLevels) {
|
|
261
|
+
;(player as any).hlsQualitySelector({
|
|
262
|
+
displayCurrentQuality: true
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Video.js event handlers to sync with MediaContext
|
|
267
|
+
player.on("play", () => {
|
|
268
|
+
dispatch({ type: MediaActionTypes.PLAY })
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
player.on("pause", () => {
|
|
272
|
+
dispatch({ type: MediaActionTypes.PAUSE })
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
player.on("timeupdate", () => {
|
|
276
|
+
dispatch({
|
|
277
|
+
type: MediaActionTypes.SET_CURRENT_TIME,
|
|
278
|
+
detail: player.currentTime() || 0
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
player.on("durationchange", () => {
|
|
283
|
+
dispatch({
|
|
284
|
+
type: MediaActionTypes.SET_DURATION,
|
|
285
|
+
detail: player.duration() || 0
|
|
286
|
+
})
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
player.on("volumechange", () => {
|
|
290
|
+
dispatch({
|
|
291
|
+
type: MediaActionTypes.SET_VOLUME,
|
|
292
|
+
detail: player.volume() || 0
|
|
293
|
+
})
|
|
294
|
+
dispatch({
|
|
295
|
+
type: player.muted() ? MediaActionTypes.MUTE : MediaActionTypes.UNMUTE
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
player.on("ended", () => {
|
|
300
|
+
dispatch({ type: MediaActionTypes.PAUSE })
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
player.on("error", (e: any) => {
|
|
304
|
+
const error = player.error()
|
|
305
|
+
setError(error?.message || "An error occurred")
|
|
306
|
+
dispatch({
|
|
307
|
+
type: MediaActionTypes.SET_ERROR,
|
|
308
|
+
detail: error?.message || "Playback error"
|
|
309
|
+
})
|
|
310
|
+
onError?.(error)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
player.on("loadstart", () => {
|
|
314
|
+
dispatch({ type: MediaActionTypes.SET_LOADING, detail: true })
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
player.on("loadeddata", () => {
|
|
318
|
+
setIsLoading(false)
|
|
319
|
+
dispatch({ type: MediaActionTypes.SET_LOADING, detail: false })
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
player.on("waiting", () => {
|
|
323
|
+
dispatch({ type: MediaActionTypes.SET_BUFFERING, detail: true })
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
player.on("playing", () => {
|
|
327
|
+
dispatch({ type: MediaActionTypes.SET_BUFFERING, detail: false })
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// Text track changes
|
|
331
|
+
player.on("texttrackchange", () => {
|
|
332
|
+
const tracks = player.textTracks() as any
|
|
333
|
+
const activeTracks: any[] = []
|
|
334
|
+
|
|
335
|
+
if (tracks && tracks.length) {
|
|
336
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
337
|
+
const track = tracks[i]
|
|
338
|
+
if (track.mode === "showing") {
|
|
339
|
+
activeTracks.push({
|
|
340
|
+
id: track.id,
|
|
341
|
+
label: track.label,
|
|
342
|
+
language: track.language,
|
|
343
|
+
kind: track.kind
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
dispatch({
|
|
350
|
+
type: MediaActionTypes.SET_ACTIVE_TEXT_TRACKS,
|
|
351
|
+
detail: activeTracks
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
// Update media ref in context for gesture/keyboard controls
|
|
356
|
+
if (context.mediaRef) {
|
|
357
|
+
context.mediaRef.current = videoRef.current
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
onReady?.(player)
|
|
361
|
+
setIsLoading(false)
|
|
362
|
+
|
|
363
|
+
} catch (err) {
|
|
364
|
+
console.error("Failed to initialize Video.js player:", err)
|
|
365
|
+
setError("Failed to initialize player")
|
|
366
|
+
setIsLoading(false)
|
|
367
|
+
onError?.(err)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Cleanup on unmount
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
initializePlayer()
|
|
374
|
+
|
|
375
|
+
return () => {
|
|
376
|
+
if (hlsRef.current) {
|
|
377
|
+
hlsRef.current.destroy()
|
|
378
|
+
hlsRef.current = null
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (playerRef.current) {
|
|
382
|
+
playerRef.current.dispose()
|
|
383
|
+
playerRef.current = null
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}, [initializePlayer])
|
|
387
|
+
|
|
388
|
+
// Expose player methods to MediaContext
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
if (!playerRef.current || !context.mediaRef) return
|
|
391
|
+
|
|
392
|
+
// Create a proxy object that bridges Video.js methods to native HTMLVideoElement methods
|
|
393
|
+
const proxyMethods = {
|
|
394
|
+
play: () => playerRef.current?.play(),
|
|
395
|
+
pause: () => playerRef.current?.pause(),
|
|
396
|
+
seekTo: (time: number) => {
|
|
397
|
+
if (playerRef.current) {
|
|
398
|
+
playerRef.current.currentTime(time)
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
setVolume: (volume: number) => {
|
|
402
|
+
if (playerRef.current) {
|
|
403
|
+
playerRef.current.volume(volume)
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
setMuted: (muted: boolean) => {
|
|
407
|
+
if (playerRef.current) {
|
|
408
|
+
playerRef.current.muted(muted)
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
setQualityLevel: (level: number) => {
|
|
412
|
+
if (hlsRef.current) {
|
|
413
|
+
hlsRef.current.currentLevel = level
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
getQualityLevels: () => {
|
|
417
|
+
if (hlsRef.current) {
|
|
418
|
+
return hlsRef.current.levels
|
|
419
|
+
}
|
|
420
|
+
return []
|
|
421
|
+
},
|
|
422
|
+
getCurrentQualityLevel: () => {
|
|
423
|
+
if (hlsRef.current) {
|
|
424
|
+
return hlsRef.current.currentLevel
|
|
425
|
+
}
|
|
426
|
+
return -1
|
|
427
|
+
},
|
|
428
|
+
getBandwidthEstimate: () => {
|
|
429
|
+
if (hlsRef.current) {
|
|
430
|
+
return hlsRef.current.bandwidthEstimate
|
|
431
|
+
}
|
|
432
|
+
return 0
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Attach proxy methods to video element for external access
|
|
437
|
+
if (videoRef.current) {
|
|
438
|
+
Object.assign(videoRef.current, proxyMethods)
|
|
439
|
+
}
|
|
440
|
+
}, [context.mediaRef])
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<div className={cn("relative h-full w-full", className)}>
|
|
444
|
+
<video
|
|
445
|
+
ref={videoRef}
|
|
446
|
+
className="video-js vjs-default-skin vjs-big-play-centered h-full w-full"
|
|
447
|
+
playsInline
|
|
448
|
+
/>
|
|
449
|
+
|
|
450
|
+
{isLoading && (
|
|
451
|
+
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
|
|
452
|
+
<div className="text-white">
|
|
453
|
+
<div className="h-8 w-8 animate-spin rounded-full border-white border-b-2" />
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
)}
|
|
457
|
+
|
|
458
|
+
{error && (
|
|
459
|
+
<div className="absolute inset-0 flex items-center justify-center bg-black/80">
|
|
460
|
+
<div className="text-center text-white">
|
|
461
|
+
<p className="font-semibold text-lg">Error</p>
|
|
462
|
+
<p className="text-sm opacity-80">{error}</p>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
VideoJSAdapter.displayName = "VideoJSAdapter"
|
|
Binary file
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react"
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
// Try to import the GIF using direct import for better compatibility
|
|
7
|
+
let monaLoadingGif: string | undefined
|
|
8
|
+
|
|
9
|
+
// Import the GIF statically for bundling - this will be handled by Vite
|
|
10
|
+
// @ts-ignore - Vite handles this import
|
|
11
|
+
import monaGifSrc from "../assets/mona-loading.gif"
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
monaLoadingGif = monaGifSrc
|
|
15
|
+
} catch {
|
|
16
|
+
// GIF import failed, will use fallback
|
|
17
|
+
monaLoadingGif = undefined
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface AnimateLoadingProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
21
|
+
loadingText?: string
|
|
22
|
+
gifSrc?: string
|
|
23
|
+
showFallback?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function AnimateLoading({
|
|
27
|
+
loadingText = "Loading...",
|
|
28
|
+
gifSrc,
|
|
29
|
+
showFallback = true,
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: AnimateLoadingProps) {
|
|
33
|
+
const [imageError, setImageError] = useState(false)
|
|
34
|
+
const [imageLoaded, setImageLoaded] = useState(false)
|
|
35
|
+
|
|
36
|
+
// Try multiple paths for the GIF
|
|
37
|
+
const gifPaths = [
|
|
38
|
+
gifSrc,
|
|
39
|
+
monaLoadingGif,
|
|
40
|
+
"/mona-loading.gif",
|
|
41
|
+
"./mona-loading.gif",
|
|
42
|
+
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" // 1x1 transparent gif as last resort
|
|
43
|
+
].filter(Boolean) as string[]
|
|
44
|
+
|
|
45
|
+
const [currentGifIndex, setCurrentGifIndex] = useState(0)
|
|
46
|
+
const currentGif = gifPaths[currentGifIndex]
|
|
47
|
+
|
|
48
|
+
const handleImageError = () => {
|
|
49
|
+
if (currentGifIndex < gifPaths.length - 1) {
|
|
50
|
+
setCurrentGifIndex(currentGifIndex + 1)
|
|
51
|
+
} else {
|
|
52
|
+
setImageError(true)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Enhanced fallback CSS animation
|
|
57
|
+
const FallbackAnimation = () => (
|
|
58
|
+
<div className="relative h-12 w-12">
|
|
59
|
+
{/* Outer spinning gradient ring */}
|
|
60
|
+
<div className="absolute inset-0 animate-spin rounded-full bg-gradient-to-r from-cyan-400 via-blue-500 to-cyan-400 opacity-75" />
|
|
61
|
+
|
|
62
|
+
{/* Inner background */}
|
|
63
|
+
<div className="absolute inset-1 rounded-full bg-black/90" />
|
|
64
|
+
|
|
65
|
+
{/* Center spinning element */}
|
|
66
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
67
|
+
<div className="relative">
|
|
68
|
+
<div className="h-8 w-8 rounded-full border-2 border-white/20" />
|
|
69
|
+
<div className="absolute inset-0 h-8 w-8 animate-spin rounded-full border-2 border-transparent border-t-cyan-400" />
|
|
70
|
+
|
|
71
|
+
{/* Pulsing dot in center */}
|
|
72
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
73
|
+
<div className="h-2 w-2 animate-pulse rounded-full bg-cyan-400" />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={cn(
|
|
83
|
+
"flex flex-col items-center justify-center space-y-2 rounded py-2 text-white",
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
{/* Show fallback while image is loading or if error */}
|
|
89
|
+
{(!imageLoaded || imageError) && showFallback && (
|
|
90
|
+
<FallbackAnimation />
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{/* Try to load the actual GIF (hidden if not loaded) */}
|
|
94
|
+
{!imageError && currentGif && (
|
|
95
|
+
<img
|
|
96
|
+
alt="loading"
|
|
97
|
+
height={50}
|
|
98
|
+
width={50}
|
|
99
|
+
src={currentGif}
|
|
100
|
+
onError={handleImageError}
|
|
101
|
+
onLoad={() => setImageLoaded(true)}
|
|
102
|
+
className={cn(
|
|
103
|
+
"object-contain",
|
|
104
|
+
!imageLoaded && "hidden"
|
|
105
|
+
)}
|
|
106
|
+
style={{ display: imageLoaded ? 'block' : 'none' }}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
<span className="mt-2 text-sm tracking-wider">
|
|
111
|
+
{loadingText}
|
|
112
|
+
</span>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|