Compare commits

...

1 Commits

Author SHA1 Message Date
Claude 6d5a8b48fc 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
2026-03-12 21:31:31 +00:00
2 changed files with 17 additions and 2 deletions
@@ -169,11 +169,19 @@ function useHLS({
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, {
startOffset: lowQualFrag.start,
endOffset: lowQualFrag.end,
type: 'video',
})
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
startOffset: lowQualFrag.start,
endOffset: lowQualFrag.end,
type: 'audio',
})
flushed.push(lowQualFrag)
}
@@ -269,6 +277,11 @@ function useHLS({
endOffset: lowQualFrag.end,
type: 'video',
})
hls.trigger(Hls.Events.BUFFER_FLUSHING, {
startOffset: lowQualFrag.start,
endOffset: lowQualFrag.end,
type: 'audio',
})
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 {Loader} from '#/components/Loader'
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 {TimeIndicator} from '../TimeIndicator'
import {ControlButton} from './ControlButton'
@@ -196,7 +196,9 @@ export function Controls({
const onSeek = useCallback(
(time: number) => {
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)
} else {
videoRef.current.currentTime = time