feat: add resolution selection to settings

This commit is contained in:
Mert Uyanık
2025-10-29 09:45:02 +03:00
parent 78c699fcea
commit 002ec26b30
7 changed files with 306 additions and 15 deletions
+98 -4
View File
@@ -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