fix(ui): hold must-watch seek lock on progressive (non-HLS) video

The lock clamped currentTime only during the seeking event. HLS defers the
forward seek until the fragment loads, so that clamp caught it; but for
progressive MP4 the forward seek takes effect immediately and the mid-seek
write is ignored, so playback leaked past the watched point (snackbar showed,
no snap-back).

Keep the immediate seeking-time clamp (HLS relies on it to avoid buffering
ahead) and add an authoritative re-clamp in seeked: if the playhead still
landed past the limit, snap back via a backward seek into buffered content,
which holds. Snackbar now fires once per blocked gesture and only when a
clamp actually happens.
This commit is contained in:
Rotimi Best
2026-06-22 10:52:36 +01:00
parent 4e2e02e7e7
commit bbdb2e9419
@@ -84,7 +84,8 @@
let playedSeconds = 0;
let lastValidTime = element.currentTime;
let isSeeking = false;
let hasBlockedThisSeek = false;
let isReclamping = false;
let seekBlockNotified = false;
let lastHeartbeatAt = 0;
const seekTolerance = 0.5;
const heartbeatIntervalMs = 15_000;
@@ -105,18 +106,50 @@
playedSeconds = 0;
};
const isAheadOfLimit = () => player.currentTime > furthestSeconds + seekTolerance;
const notifySeekBlocked = () => {
if (seekBlockNotified) return;
seekBlockNotified = true;
policy.onSeekBlocked?.();
};
// Immediate clamp. For HLS this short-circuits the forward fragment fetch
// so the player never buffers ahead. For progressive MP4 the write is
// often ignored mid-seek, so `onSeeked` re-asserts it once settled.
const onSeeking = () => {
isSeeking = true;
if (!hasBlockedThisSeek && player.currentTime > furthestSeconds + seekTolerance) {
hasBlockedThisSeek = true;
if (isReclamping) return;
if (isAheadOfLimit()) {
notifySeekBlocked();
player.currentTime = furthestSeconds;
policy.onSeekBlocked?.();
}
};
const onSeeked = () => {
// Consume the `seeked` produced by our own clamp write, then settle.
if (isReclamping) {
isReclamping = false;
isSeeking = false;
seekBlockNotified = false;
lastValidTime = player.currentTime;
return;
}
// Authoritative enforcement: if the seek landed past the limit (the
// mid-seek clamp didn't hold, e.g. progressive MP4), snap back now —
// a backward seek into buffered content, which holds reliably.
if (isAheadOfLimit()) {
isReclamping = true;
notifySeekBlocked();
player.currentTime = furthestSeconds;
return;
}
isSeeking = false;
hasBlockedThisSeek = false;
seekBlockNotified = false;
lastValidTime = player.currentTime;
};