diff --git a/web/src/components/player/dynamic/AutoQualityGovernor.ts b/web/src/components/player/dynamic/AutoQualityGovernor.ts index d7805eefe2..bd0871a8d5 100644 --- a/web/src/components/player/dynamic/AutoQualityGovernor.ts +++ b/web/src/components/player/dynamic/AutoQualityGovernor.ts @@ -340,8 +340,11 @@ export class AutoQualityGovernor { private triggerDownswitch(reason: DownswitchReason): boolean { const handled = this.requestDownswitch(reason); if (handled) { - // the low stream starts with a clean record + // the low stream starts with a clean record, and the probe lets a + // recovered connection (or a wrong downswitch) return to full + // quality mid-chunk rather than at the next boundary this.resetStallHistory(); + this.armUpswitchProbe(); } return handled; } diff --git a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx index bdcbe9a350..2aaeff5f62 100644 --- a/web/src/components/player/dynamic/DynamicVideoPlayer.tsx +++ b/web/src/components/player/dynamic/DynamicVideoPlayer.tsx @@ -484,9 +484,6 @@ export default function DynamicVideoPlayer({ } setAutoLowQuality(true); setAutoLowReason(reason === "codec" ? "codec" : "bandwidth"); - // so a recovered connection (or a wrong downswitch) returns to - // full quality mid-chunk rather than at the next boundary - governor.armUpswitchProbe(); return true; }; tryUpswitchRef.current = () => { @@ -495,7 +492,7 @@ export default function DynamicVideoPlayer({ setAutoLowReason(undefined); } }; - }, [resolvedQuality, subAvailable, governor]); + }, [resolvedQuality, subAvailable]); // persisted across sessions so a device on a known-slow connection // starts low instead of paying the first stall to find out @@ -616,7 +613,19 @@ export default function DynamicVideoPlayer({ governor.sourceLoadStarted(); }, [source, isScrubbing, governor]); + const lastChunkRef = useRef(timeRange); useEffect(() => { + // the seed effect picks the starting quality, so only a later chunk + // boundary reconsiders it + const lastChunk = lastChunkRef.current; + lastChunkRef.current = timeRange; + if ( + lastChunk.after === timeRange.after && + lastChunk.before === timeRange.before + ) { + return; + } + // a chunk boundary is where full quality may be retried, and a // natural point to persist what the governor has learned setAutoLowQuality((prev) => prev && !governor.shouldRetryMain());