From c1b56b51b0efdc65edb622b8d24f8f6da981b191 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 31 Aug 2026 11:17:56 -0600 Subject: [PATCH] Backport 0.19 iOS HLS fixes (#24147) --- web/src/components/player/HlsVideoPlayer.tsx | 6 ++++++ .../player/dynamic/DynamicVideoController.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/web/src/components/player/HlsVideoPlayer.tsx b/web/src/components/player/HlsVideoPlayer.tsx index c1aaa09d68..a09b8a4e8d 100644 --- a/web/src/components/player/HlsVideoPlayer.tsx +++ b/web/src/components/player/HlsVideoPlayer.tsx @@ -490,6 +490,12 @@ export default function HlsVideoPlayer({ clearTimeout(mobileCtrlTimeout); } }} + onSeeking={() => { + // iOS ManagedMediaSource gates hls.js fragment loading off + // while paused and never resumes it on seek, so a seek + // into unbuffered media would never complete + hlsRef.current?.resumeBuffering(); + }} onWaiting={() => { if (onError != undefined) { if (videoRef.current?.paused) { diff --git a/web/src/components/player/dynamic/DynamicVideoController.ts b/web/src/components/player/dynamic/DynamicVideoController.ts index 151ea4022f..7609b055db 100644 --- a/web/src/components/player/dynamic/DynamicVideoController.ts +++ b/web/src/components/player/dynamic/DynamicVideoController.ts @@ -10,6 +10,10 @@ import { playWithTemporaryMuteFallback } from "@/utils/videoUtil.ts"; type PlayerMode = "playback" | "scrubbing"; +// how long a seek may wait for its `seeked` event before playback starts +// anyway; long enough that a normally completing seek always wins +const SEEK_PLAY_FALLBACK_MS = 1000; + export class DynamicVideoController { // main state public camera = ""; @@ -106,7 +110,10 @@ export class DynamicVideoController { waitAndPlay() { return new Promise((resolve) => { + let fallback: NodeJS.Timeout | undefined; + const onSeekedHandler = () => { + clearTimeout(fallback); this.playerController.removeEventListener("seeked", onSeekedHandler); playWithTemporaryMuteFallback(this.playerController); resolve(undefined); @@ -115,6 +122,12 @@ export class DynamicVideoController { this.playerController.addEventListener("seeked", onSeekedHandler, { once: true, }); + + // iOS ManagedMediaSource pauses hls.js buffering, so `seeked` may + // never fire; playing is what prompts WebKit to resume streaming + if ("ManagedMediaSource" in window) { + fallback = setTimeout(onSeekedHandler, SEEK_PLAY_FALLBACK_MS); + } }); }