Fix video/audio desync on Firefox in web video player
Two issues were causing audio to drift out of sync with video on Firefox: 1. fastSeek() seeks video to the nearest keyframe but keeps audio at the precise timestamp, causing progressive drift. Disable fastSeek on Firefox and use currentTime for precise seeking instead. 2. Low-quality segment flushing only flushed video buffers, leaving stale audio data that could desync from newly-loaded video. Flush both audio and video buffers together when upgrading quality. https://claude.ai/code/session_01LGFSY2GgQJ3iw5Cp4XwiuL
This commit is contained in:
@@ -169,11 +169,19 @@ function useHLS({
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flush both video and audio buffers to keep them in sync.
|
||||||
|
// Flushing only video leaves stale audio data that can desync,
|
||||||
|
// particularly on Firefox.
|
||||||
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
|
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
|
||||||
startOffset: lowQualFrag.start,
|
startOffset: lowQualFrag.start,
|
||||||
endOffset: lowQualFrag.end,
|
endOffset: lowQualFrag.end,
|
||||||
type: 'video',
|
type: 'video',
|
||||||
})
|
})
|
||||||
|
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
|
||||||
|
startOffset: lowQualFrag.start,
|
||||||
|
endOffset: lowQualFrag.end,
|
||||||
|
type: 'audio',
|
||||||
|
})
|
||||||
|
|
||||||
flushed.push(lowQualFrag)
|
flushed.push(lowQualFrag)
|
||||||
}
|
}
|
||||||
@@ -269,6 +277,11 @@ function useHLS({
|
|||||||
endOffset: lowQualFrag.end,
|
endOffset: lowQualFrag.end,
|
||||||
type: 'video',
|
type: 'video',
|
||||||
})
|
})
|
||||||
|
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
|
||||||
|
startOffset: lowQualFrag.start,
|
||||||
|
endOffset: lowQualFrag.end,
|
||||||
|
type: 'audio',
|
||||||
|
})
|
||||||
setLowQualityFragments([])
|
setLowQualityFragments([])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {Pause_Filled_Corner0_Rounded as PauseIcon} from '#/components/icons/Paus
|
|||||||
import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
|
import {Play_Filled_Corner0_Rounded as PlayIcon} from '#/components/icons/Play'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_WEB_MOBILE_IOS, IS_WEB_TOUCH_DEVICE} from '#/env'
|
import {IS_WEB_FIREFOX, IS_WEB_MOBILE_IOS, IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||||
import {GifPresentationControls} from '../../GifPresentationControls'
|
import {GifPresentationControls} from '../../GifPresentationControls'
|
||||||
import {TimeIndicator} from '../TimeIndicator'
|
import {TimeIndicator} from '../TimeIndicator'
|
||||||
import {ControlButton} from './ControlButton'
|
import {ControlButton} from './ControlButton'
|
||||||
@@ -196,7 +196,9 @@ export function Controls({
|
|||||||
const onSeek = useCallback(
|
const onSeek = useCallback(
|
||||||
(time: number) => {
|
(time: number) => {
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
if (videoRef.current.fastSeek) {
|
// Avoid fastSeek on Firefox - it seeks video to the nearest keyframe
|
||||||
|
// but keeps audio at the precise time, causing audio/video desync
|
||||||
|
if (videoRef.current.fastSeek && !IS_WEB_FIREFOX) {
|
||||||
videoRef.current.fastSeek(time)
|
videoRef.current.fastSeek(time)
|
||||||
} else {
|
} else {
|
||||||
videoRef.current.currentTime = time
|
videoRef.current.currentTime = time
|
||||||
|
|||||||
Reference in New Issue
Block a user