Fix video quality for short videos (#5996)

* remove auto level capping

* flush first fragment on loop

(cherry picked from commit d1355d52fc)
This commit is contained in:
Samuel Newman
2024-10-29 15:35:43 +02:00
committed by Hailey
parent 66b3f359f8
commit dfbfcddef5
2 changed files with 36 additions and 13 deletions
@@ -33,7 +33,6 @@ export function VideoEmbedInnerWeb({
}
const hlsRef = useHLS({
focused,
playlist: embed.playlist,
setHasSubtitleTrack,
setError,
@@ -113,14 +112,12 @@ promiseForHls.then(Hls => {
})
function useHLS({
focused,
playlist,
setHasSubtitleTrack,
setError,
videoRef,
setHlsLoading,
}: {
focused: boolean
playlist: string
setHasSubtitleTrack: (v: boolean) => void
setError: (v: Error | null) => void
@@ -155,8 +152,8 @@ function useHLS({
if (!hlsRef.current) return
const hls = hlsRef.current
if (focused && hls.nextAutoLevel > 0) {
// if the current quality level goes above 0, flush the low quality segments
// if the current quality level goes above 0, flush the low quality segments
if (hls.nextAutoLevel > 0) {
const flushed: HlsTypes.Fragment[] = []
for (const lowQualFrag of lowQualityFragments) {
@@ -179,6 +176,29 @@ function useHLS({
},
)
const flushOnLoop = useNonReactiveCallback(() => {
if (!Hls) return
if (!hlsRef.current) return
const hls = hlsRef.current
// the above callback will catch most stale frags, but there's a corner case -
// if there's only one segment in the video, it won't get flushed because it avoids
// flushing the currently active segment. Therefore, we have to catch it when we loop
if (
hls.nextAutoLevel > 0 &&
lowQualityFragments.length === 1 &&
lowQualityFragments[0].start === 0
) {
const lowQualFrag = lowQualityFragments[0]
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
startOffset: lowQualFrag.start,
endOffset: lowQualFrag.end,
type: 'video',
})
setLowQualityFragments([])
}
})
useEffect(() => {
if (!videoRef.current) return
if (!Hls) return
@@ -197,16 +217,14 @@ function useHLS({
hls.attachMedia(videoRef.current)
hls.loadSource(playlist)
// initial value, later on it's managed by Controls
hls.autoLevelCapping = 0
// manually loop, so if we've flushed the first buffer it doesn't get confused
const abortController = new AbortController()
const {signal} = abortController
const videoNode = videoRef.current
videoNode.addEventListener(
'ended',
function () {
() => {
flushOnLoop()
videoNode.currentTime = 0
videoNode.play()
},
@@ -248,7 +266,15 @@ function useHLS({
hls.destroy()
abortController.abort()
}
}, [playlist, setError, setHasSubtitleTrack, videoRef, handleFragChange, Hls])
}, [
playlist,
setError,
setHasSubtitleTrack,
videoRef,
handleFragChange,
flushOnLoop,
Hls,
])
return hlsRef
}
@@ -138,13 +138,10 @@ export function Controls({
useEffect(() => {
if (!hlsRef.current) return
if (focused) {
// auto decide quality based on network conditions
hlsRef.current.autoLevelCapping = -1
// allow 30s of buffering
hlsRef.current.config.maxMaxBufferLength = 30
} else {
// back to what we initially set
hlsRef.current.autoLevelCapping = 0
hlsRef.current.config.maxMaxBufferLength = 10
}
}, [hlsRef, focused])