Summary
When a seek targets a position that is not buffered yet, media-time-range and media-time-display keep showing the pre-seek position for the whole stall, and only jump to the new position once the seek completes. With a burst of seek requests (e.g. holding/repeating a keyboard hotkey) nothing in the UI moves at all until the last seek finishes, which reads as a frozen player.
Steps to reproduce
- Play an HLS VOD (mine: 1:38:30) and let it buffer, e.g. up to 2610 s.
- While playing, seek to a position past the buffered range — a single
mediaseekrequest with detail = 2780, or repeated +5 s hotkey presses.
- Watch
media-time-range / media-time-display during the buffering stall.
Expected: the UI reflects the requested position immediately; the loading indicator communicates the stall.
Actual: both stay at 2480.9 s until the seek resolves.
Measured event log (media-chrome 4.19.2, hls.js 1.6.15, Chrome 150, macOS) — columns are event | media.currentTime | readyState | timeRange bar position (s) | medialoading:
seeking 2780.89 1 2480.9 0
A:medialoading 2780.89 1 2480.9 1
waiting 2780.89 1 2480.9 1
… 824 ms, no state change, bar frozen at 2480.9 …
A:mediacurrenttime 2780.90 4 2780.9 1
timeupdate 2780.90 4 2780.9 1
seeked 2780.90 4 2780.9 0
824 ms on a fast desktop connection; on mobile/throttled it lasts as long as the fragment takes to load.
Cause
media.currentTime already returns the new position as soon as the seek starts (visible in the log above at the seeking event), but nothing propagates it into the store:
state-mediator.js — mediaCurrentTime is only recomputed on mediaEvents: ["timeupdate", "loadedmetadata"], and per spec timeupdate is not fired while a seek is pending.
request-map.js — the seek request sets the value but returns nothing, so updateState(undefined) is a no-op and there is no optimistic update:
[MediaUIEvents.MEDIA_SEEK_REQUEST](stateMediator, stateOwners, { detail }) {
const key = "mediaCurrentTime";
const value = detail;
stateMediator[key].set(value, stateOwners);
},
Suggested fix
Return the delta so the UI reflects the requested position right away:
stateMediator[key].set(value, stateOwners);
return { [key]: value };
One caveat worth deciding on: if the browser clamps the requested time (seek outside seekable), the state would be optimistic-but-wrong until the next timeupdate corrects it. Reading stateMediator[key].get(stateOwners) back after the set instead of echoing detail would avoid that.
Happy to open a PR for whichever variant you prefer.
Related
#1306 — same visible symptom (frozen time range) from a different cause: backward seeks under 3% of duration are ignored by RangeAnimation.update. The two are independent; a backward seek into an unbuffered range hits both.
Summary
When a seek targets a position that is not buffered yet,
media-time-rangeandmedia-time-displaykeep showing the pre-seek position for the whole stall, and only jump to the new position once the seek completes. With a burst of seek requests (e.g. holding/repeating a keyboard hotkey) nothing in the UI moves at all until the last seek finishes, which reads as a frozen player.Steps to reproduce
mediaseekrequestwithdetail = 2780, or repeated+5 shotkey presses.media-time-range/media-time-displayduring the buffering stall.Expected: the UI reflects the requested position immediately; the loading indicator communicates the stall.
Actual: both stay at 2480.9 s until the seek resolves.
Measured event log (media-chrome 4.19.2, hls.js 1.6.15, Chrome 150, macOS) — columns are
event | media.currentTime | readyState | timeRange bar position (s) | medialoading:824 ms on a fast desktop connection; on mobile/throttled it lasts as long as the fragment takes to load.
Cause
media.currentTimealready returns the new position as soon as the seek starts (visible in the log above at theseekingevent), but nothing propagates it into the store:state-mediator.js—mediaCurrentTimeis only recomputed onmediaEvents: ["timeupdate", "loadedmetadata"], and per spectimeupdateis not fired while a seek is pending.request-map.js— the seek request sets the value but returns nothing, soupdateState(undefined)is a no-op and there is no optimistic update:Suggested fix
Return the delta so the UI reflects the requested position right away:
One caveat worth deciding on: if the browser clamps the requested time (seek outside
seekable), the state would be optimistic-but-wrong until the nexttimeupdatecorrects it. ReadingstateMediator[key].get(stateOwners)back after thesetinstead of echoingdetailwould avoid that.Happy to open a PR for whichever variant you prefer.
Related
#1306 — same visible symptom (frozen time range) from a different cause: backward seeks under 3% of duration are ignored by
RangeAnimation.update. The two are independent; a backward seek into an unbuffered range hits both.