feat: add resolution selection to settings
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useCallback, useState } from 'react'
|
||||
import { usePlayerContext } from '../contexts/PlayerContext'
|
||||
import type { SubtitleTrack, AudioTrack } from '../types'
|
||||
import type { SubtitleTrack, AudioTrack, VideoQuality } from '../types'
|
||||
import { validateVideoURL, getCORSErrorMessage, isCORSError } from '../utils/corsHelper'
|
||||
import { getHlsAudioTracks, setHlsAudioTrack } from '../utils/hlsLoader'
|
||||
import { getHlsAudioTracks, getHlsQualities, setHlsAudioTrack, setHlsQualityLevel } from '../utils/hlsLoader'
|
||||
import './VideoElement.css'
|
||||
|
||||
interface VideoElementProps {
|
||||
@@ -22,6 +22,7 @@ interface VideoElementProps {
|
||||
onSeeking?: () => void
|
||||
onSeeked?: () => void
|
||||
onAudioTracksLoaded?: (tracks: AudioTrack[]) => void
|
||||
onQualityLevelsLoaded?: (qualities: VideoQuality[]) => void
|
||||
}
|
||||
|
||||
export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
@@ -41,10 +42,12 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
onSeeking,
|
||||
onSeeked,
|
||||
onAudioTracksLoaded,
|
||||
onQualityLevelsLoaded,
|
||||
}) => {
|
||||
const { videoRef, setVideoState, toggleFullscreen, settings } = usePlayerContext()
|
||||
const { videoRef, setVideoState, toggleFullscreen, settings, setQuality } = usePlayerContext()
|
||||
const lastClickTimeRef = React.useRef<number>(0)
|
||||
const [availableAudioTracks, setAvailableAudioTracks] = useState<AudioTrack[]>([])
|
||||
const [availableQualities, setAvailableQualities] = useState<VideoQuality[]>([])
|
||||
|
||||
// Handle video events
|
||||
const handlePlay = useCallback(() => {
|
||||
@@ -197,6 +200,11 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
const video = videoRef.current
|
||||
if (!video) return
|
||||
|
||||
setAvailableAudioTracks([])
|
||||
onAudioTracksLoaded?.([])
|
||||
setAvailableQualities([])
|
||||
onQualityLevelsLoaded?.([])
|
||||
|
||||
// Validate video URL first
|
||||
const validation = validateVideoURL(src)
|
||||
if (!validation.valid) {
|
||||
@@ -234,11 +242,15 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
// Sometimes audio tracks are not immediately available, so we try with a small delay
|
||||
setTimeout(() => {
|
||||
const tracks = getHlsAudioTracks(hls)
|
||||
const qualities = getHlsQualities(hls)
|
||||
|
||||
if (tracks.length > 0) {
|
||||
setAvailableAudioTracks(tracks)
|
||||
onAudioTracksLoaded?.(tracks)
|
||||
}
|
||||
|
||||
setAvailableQualities(qualities)
|
||||
onQualityLevelsLoaded?.(qualities)
|
||||
}, 100)
|
||||
|
||||
if (autoplay) {
|
||||
@@ -321,7 +333,16 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
delete (video as any).__hlsInstance
|
||||
}
|
||||
}
|
||||
}, [src, autoplay, videoRef, handleError, setVideoState, onError, onAudioTracksLoaded])
|
||||
}, [
|
||||
src,
|
||||
autoplay,
|
||||
videoRef,
|
||||
handleError,
|
||||
setVideoState,
|
||||
onError,
|
||||
onAudioTracksLoaded,
|
||||
onQualityLevelsLoaded,
|
||||
])
|
||||
|
||||
// Handle audio track changes
|
||||
useEffect(() => {
|
||||
@@ -341,6 +362,79 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
||||
}
|
||||
}, [settings.audioTrack, availableAudioTracks, videoRef])
|
||||
|
||||
// Reset selected quality if it no longer exists
|
||||
useEffect(() => {
|
||||
if (!settings.quality) return
|
||||
if (availableQualities.length === 0) return
|
||||
|
||||
const hasQuality = availableQualities.some((quality) => {
|
||||
if (
|
||||
typeof settings.quality?.levelIndex === 'number' &&
|
||||
typeof quality.levelIndex === 'number' &&
|
||||
quality.levelIndex === settings.quality.levelIndex
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
typeof settings.quality?.height === 'number' &&
|
||||
typeof quality.height === 'number' &&
|
||||
quality.height === settings.quality.height
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return quality.label === settings.quality?.label
|
||||
})
|
||||
|
||||
if (!hasQuality) {
|
||||
setQuality(null)
|
||||
}
|
||||
}, [availableQualities, settings.quality, setQuality])
|
||||
|
||||
// Apply selected quality to HLS instance
|
||||
useEffect(() => {
|
||||
const video = videoRef.current
|
||||
if (!video) return
|
||||
|
||||
const hlsInstance = (video as any).__hlsInstance
|
||||
if (!hlsInstance) return
|
||||
|
||||
if (!settings.quality) {
|
||||
setHlsQualityLevel(hlsInstance, null)
|
||||
return
|
||||
}
|
||||
|
||||
let targetLevelIndex =
|
||||
typeof settings.quality.levelIndex === 'number' ? settings.quality.levelIndex : undefined
|
||||
|
||||
if (typeof targetLevelIndex !== 'number') {
|
||||
const matchingQuality = availableQualities.find((quality) => {
|
||||
if (
|
||||
typeof settings.quality?.levelIndex === 'number' &&
|
||||
typeof quality.levelIndex === 'number'
|
||||
) {
|
||||
return quality.levelIndex === settings.quality.levelIndex
|
||||
}
|
||||
|
||||
if (
|
||||
typeof settings.quality?.height === 'number' &&
|
||||
typeof quality.height === 'number'
|
||||
) {
|
||||
return quality.height === settings.quality.height
|
||||
}
|
||||
|
||||
return quality.label === settings.quality?.label
|
||||
})
|
||||
|
||||
if (matchingQuality && typeof matchingQuality.levelIndex === 'number') {
|
||||
targetLevelIndex = matchingQuality.levelIndex
|
||||
}
|
||||
}
|
||||
|
||||
setHlsQualityLevel(hlsInstance, targetLevelIndex)
|
||||
}, [settings.quality, availableQualities, videoRef])
|
||||
|
||||
return (
|
||||
<div className="video-container">
|
||||
<video
|
||||
|
||||
Reference in New Issue
Block a user