From 6d5a8b48fcb4988f991ecbd572921885f087a9b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Mar 2026 21:31:31 +0000 Subject: [PATCH] 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 --- .../VideoEmbedInner/VideoEmbedInnerWeb.tsx | 13 +++++++++++++ .../VideoEmbedInner/web-controls/VideoControls.tsx | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx index de3c130c26..ef6fa1cae0 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx @@ -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([]) } }) diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx index d6a5628bb4..d8414e9d35 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx @@ -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