175 lines
4.8 KiB
TypeScript
175 lines
4.8 KiB
TypeScript
import React, { useEffect, useState, useCallback } from 'react'
|
|
import { PlayerProvider, usePlayerContext } from '../contexts/PlayerContext'
|
|
import { VideoElement } from './VideoElement'
|
|
import { ControlsLayer } from './ControlsLayer'
|
|
import type { VideoPlayerProps, AudioTrack, VideoQuality } from '../types'
|
|
import '../styles/variables.css'
|
|
import './VideoPlayer.css'
|
|
|
|
// Lazy load polyfills only if needed
|
|
let polyfillsInitialized = false
|
|
const initializePolyfillsIfNeeded = async () => {
|
|
if (polyfillsInitialized) return
|
|
|
|
// Check if polyfills are needed
|
|
const needsFullscreenPolyfill = !document.fullscreenEnabled && !(document as any).webkitFullscreenEnabled
|
|
const needsPIPPolyfill = !('pictureInPictureEnabled' in document)
|
|
|
|
if (needsFullscreenPolyfill || needsPIPPolyfill) {
|
|
const { initializePolyfills } = await import('../utils/polyfills')
|
|
initializePolyfills()
|
|
polyfillsInitialized = true
|
|
}
|
|
}
|
|
|
|
// Initialize polyfills asynchronously
|
|
initializePolyfillsIfNeeded()
|
|
|
|
const VideoPlayerContent: React.FC<
|
|
VideoPlayerProps & {
|
|
audioTracks: AudioTrack[]
|
|
onAudioTracksLoadedInternal: (tracks: AudioTrack[]) => void
|
|
qualities: VideoQuality[]
|
|
onQualityLevelsLoadedInternal: (qualities: VideoQuality[]) => void
|
|
}
|
|
> = ({
|
|
src,
|
|
poster,
|
|
autoplay = false,
|
|
loop = false,
|
|
muted = false,
|
|
controls = true,
|
|
subtitles = [],
|
|
keyboardShortcuts = true,
|
|
pictureInPicture = true,
|
|
className = '',
|
|
style,
|
|
onPlay,
|
|
onPause,
|
|
onEnded,
|
|
onTimeUpdate,
|
|
onVolumeChange,
|
|
onError,
|
|
onLoadedMetadata,
|
|
onSeeking,
|
|
onSeeked,
|
|
audioTracks,
|
|
onAudioTracksLoadedInternal,
|
|
qualities,
|
|
onQualityLevelsLoadedInternal,
|
|
}) => {
|
|
const { containerRef, uiState } = usePlayerContext()
|
|
const controlsHiddenClass = !uiState.controlsVisible ? 'controls-hidden' : ''
|
|
|
|
return (
|
|
<div ref={containerRef} className={`video-player ${controlsHiddenClass} ${className}`} style={style}>
|
|
<VideoElement
|
|
src={src}
|
|
poster={poster}
|
|
autoplay={autoplay}
|
|
loop={loop}
|
|
muted={muted}
|
|
subtitles={subtitles}
|
|
onPlay={onPlay}
|
|
onPause={onPause}
|
|
onEnded={onEnded}
|
|
onTimeUpdate={onTimeUpdate}
|
|
onVolumeChange={onVolumeChange}
|
|
onError={onError}
|
|
onLoadedMetadata={onLoadedMetadata}
|
|
onSeeking={onSeeking}
|
|
onSeeked={onSeeked}
|
|
onAudioTracksLoaded={onAudioTracksLoadedInternal}
|
|
onQualityLevelsLoaded={onQualityLevelsLoadedInternal}
|
|
/>
|
|
{controls && (
|
|
<ControlsLayer
|
|
keyboardShortcuts={keyboardShortcuts}
|
|
pictureInPicture={pictureInPicture}
|
|
subtitles={subtitles}
|
|
audioTracks={audioTracks}
|
|
qualities={qualities}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|
src,
|
|
poster,
|
|
autoplay = false,
|
|
loop = false,
|
|
muted = false,
|
|
controls = true,
|
|
subtitles = [],
|
|
theme,
|
|
language,
|
|
keyboardShortcuts = true,
|
|
pictureInPicture = true,
|
|
className = '',
|
|
style,
|
|
onPlay,
|
|
onPause,
|
|
onEnded,
|
|
onTimeUpdate,
|
|
onVolumeChange,
|
|
onError,
|
|
onLoadedMetadata,
|
|
onSeeking,
|
|
onSeeked,
|
|
}) => {
|
|
const [audioTracks, setAudioTracks] = useState<AudioTrack[]>([])
|
|
const [qualities, setQualities] = useState<VideoQuality[]>([])
|
|
|
|
// Apply theme CSS variables
|
|
useEffect(() => {
|
|
if (theme) {
|
|
const root = document.documentElement
|
|
if (theme.primaryColor) root.style.setProperty('--player-primary', theme.primaryColor)
|
|
if (theme.accentColor) root.style.setProperty('--player-primary-hover', theme.accentColor)
|
|
if (theme.backgroundColor) root.style.setProperty('--player-bg', theme.backgroundColor)
|
|
if (theme.textColor) root.style.setProperty('--player-text', theme.textColor)
|
|
}
|
|
}, [theme])
|
|
|
|
const handleAudioTracksLoaded = useCallback((tracks: AudioTrack[]) => {
|
|
setAudioTracks(tracks)
|
|
}, [])
|
|
|
|
const handleQualityLevelsLoaded = useCallback((levels: VideoQuality[]) => {
|
|
setQualities(levels)
|
|
}, [])
|
|
|
|
return (
|
|
<PlayerProvider initialMuted={muted} language={language}>
|
|
<VideoPlayerContent
|
|
src={src}
|
|
poster={poster}
|
|
autoplay={autoplay}
|
|
loop={loop}
|
|
muted={muted}
|
|
controls={controls}
|
|
subtitles={subtitles}
|
|
keyboardShortcuts={keyboardShortcuts}
|
|
pictureInPicture={pictureInPicture}
|
|
className={className}
|
|
style={style}
|
|
onPlay={onPlay}
|
|
onPause={onPause}
|
|
onEnded={onEnded}
|
|
onTimeUpdate={onTimeUpdate}
|
|
onVolumeChange={onVolumeChange}
|
|
onError={onError}
|
|
onLoadedMetadata={onLoadedMetadata}
|
|
onSeeking={onSeeking}
|
|
onSeeked={onSeeked}
|
|
audioTracks={audioTracks}
|
|
onAudioTracksLoadedInternal={handleAudioTracksLoaded}
|
|
qualities={qualities}
|
|
onQualityLevelsLoadedInternal={handleQualityLevelsLoaded}
|
|
/>
|
|
</PlayerProvider>
|
|
)
|
|
}
|