Skip to content

fix: pass clip-relative 0 to media-seek-request in resetPlaybackIfNeeded#1844

Open
tonze wants to merge 1 commit into
vidstack:mainfrom
tonze:fix/reset-playback-clip-relative
Open

fix: pass clip-relative 0 to media-seek-request in resetPlaybackIfNeeded#1844
tonze wants to merge 1 commit into
vidstack:mainfrom
tonze:fix/reset-playback-clip-relative

Conversation

@tonze

@tonze tonze commented Jun 24, 2026

Copy link
Copy Markdown

What

Fixes #resetPlaybackIfNeeded dispatching an absolute seekableStart() value to media-seek-request, which the seek handler treats as clip-relative — causing boundTime() to double-add clipStartTime and jump clipped players to ~ clip end on play().

Why

Since 1.14.0 (maverick 0.44.1), currentTime and seek requests are clip-relative — media-time-slider dispatches percent / 100 * duration (0 to clip-window), and boundTime() converts clip-relative to absolute by adding clipStartTime. But #resetPlaybackIfNeeded was not updated and still passes the absolute seekableStart() where a clip-relative value is expected:

// MediaStateManager.#resetPlaybackIfNeeded
if (shouldReset) {
  this.dispatch('media-seek-request', {
    detail: seekableStart(),   // ABSOLUTE (e.g. 300)
    trigger,
  });
}

// media-seek-request handler
const boundedTime = boundTime(seekTime, this.$state);   // boundTime(300, ...)

// boundTime
const clippedTime = time + store.clipStartTime();       // 300 + 300 = 600 = clip END

Result: calling play() on a clipped player that has ended (or whose realCurrentTime < seekableStart) jumps to ~ clip end instead of clip start. This breaks auto-advance in playlist-style UIs where each item is a clip of a longer video, and any "replay after clip end" flow.

How

Pass clip-relative 0 instead of absolute seekableStart(). boundTime adds clipStartTime back, and its isStart clamp resolves 0 + clipStartTime <= seekableStart to seekableStart() — exactly the intended target.

 if (shouldReset) {
   this.dispatch('media-seek-request', {
-    detail: seekableStart(),
+    detail: 0,
     trigger,
   });
 }

Safe for all video types

boundTime(0, store) resolves correctly for every video type via the isStart clamp (clippedTime = 0 + clipStartTime <= seekableStart always holds since seekableStart = max(mediaStart, clipStartTime)):

Video type clipStart seekableStart boundTime(0) returns
Clipped on-demand 300 300 300 (clip start)
Non-clipped on-demand 0 0 0
Live DVR (no clip) 0 700 (DVR window start) 700

Verified locally across all three cases — no regressions for non-clipped or live DVR playback.

Testing

  • Clipped player: play() after clip end now seeks to currentTime = 0 (realCurrentTime = clipStartTime) instead of clip end.
  • Non-clipped player: play() after ended seeks to 0 (unchanged).
  • Playlist auto-advance (clip ends -> load next clip -> play) works without client-side workarounds.
  • seekableStart semantics for live DVR preserved.

Environment

  • Reproduced on 1.15.6 (latest) and 1.14.0 (first affected).
  • Pre-1.14 (1.12.13, 1.13.x) unaffected — currentTime was absolute and seeks did not route through boundTime.

This contribution was created with the support of GLM-5.2.

#resetPlaybackIfNeeded dispatches seekableStart() (absolute) to
media-seek-request, but since 1.14.0 the seek handler treats the detail
as clip-relative and boundTime() adds clipStartTime back. This double-
adds clipStartTime, jumping clipped players to ~ clip end on play()
after a clip ends or when realCurrentTime < seekableStart.

Pass 0 instead — boundTime's isStart clamp resolves 0 + clipStartTime
(<= seekableStart) to seekableStart(), the intended target. Safe for all
video types: clipped on-demand (-> clipStartTime), non-clipped (-> 0),
live DVR (-> DVR window start).

Fixes the auto-advance / replay-after-clip-end regression that appeared
in 1.14.0 (maverick 0.44.1) when currentTime became clip-relative.

This contribution was created with the support of GLM-5.2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant