镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

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

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

tonze wants to merge 2 commits into
vidstack:mainfrom
tonze:fix/reset-playback-clip-relative

Conversation

@tonze

@tonze tonze commented Jun 24, 2026 •

Copy link
Copy Markdown

What

Fixes #1858

Fix #resetPlaybackIfNeeded passing the absolute seekableStart() value to the clip-relative media-seek-request handler.

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

Corrected explanation

currentTime and seek requests were already clip-relative in 1.12.13. They also already passed through boundTime().

The relevant change was #1824. The previous boundary check happened to accept the absolute seekableStart() value passed by #resetPlaybackIfNeeded. The revised check exposed that incorrect argument.

I rechecked the published packages. For a clip spanning 300–600 seconds, boundTime(300) returns 300 in 1.12.13 and 1.13.0, but 600 in 1.14.0 and 1.15.6. Passing 0 returns the intended 300 in the affected versions.

The proposed fix still looks correct. My explanation of why it became necessary was wrong. The Maverick attribution in #1843 was also stronger than the evidence supported.

Verification

I added eight regression tests covering clipped replay, playback before the clip start or near its end, playback within a clip, unclipped replay, and live-DVR window resets. They exercise the state manager and the real seek handler with a mocked provider. Five fail with the original seekableStart() argument. All eight pass with 0.

I also tested a standalone MP4 player in Chrome 153 and Safari 27, without Rails or Stimulus. For a clip spanning 30–34 seconds, replay jumps to 34 without the fix. With the fix, it seeks to 30 and plays the four-second clip again.

Local checks passed on Node 22.22.3 and pnpm 8.7.0:

  • pnpm format:check
  • pnpm typecheck
  • pnpm build
  • pnpm test, 71 tests passed

The build emits the same fscreen resolution and Remotion dynamic-import warnings with and without the fix.

These browser checks cover replay after the clip has stopped. An immediate restart from a pause callback hit a separate race in Safari, where an end notification stopped the restarted playback. This change does not address that case.

Reproduction

Save this as packages/vidstack/sandbox/clip-replay.html in a checkout of this PR:

<!doctype html>
<title>Clip replay</title>
<media-player
  src="https://files.vidstack.io/sprite-fight/480p.mp4"
  clip-start-time="30"
  clip-end-time="34"
  load="eager"
  controls
  muted
  playsinline
>
  <media-provider></media-provider>
</media-player>
<button disabled>Play or replay</button>
<script type="module">
  import '../src/elements/bundles/player';

  const player = document.querySelector('media-player');
  const button = document.querySelector('button');
  player.addEventListener('can-play', () => { button.disabled = false; });
  button.addEventListener('click', () => player.play());
</script>

Run pnpm install --frozen-lockfile, then pnpm --filter vidstack exec vite --port 3101. Open http://localhost:3101/sandbox/clip-replay.html, play the clip, wait until it stops, and click the button again.

To reproduce the failure, change detail: 0 back to detail: seekableStart() in #resetPlaybackIfNeeded and reload.


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.
@tonze

tonze commented Sep 28, 2026

Copy link
Copy Markdown
Author

Correction to my explanation above: currentTime and seek requests were already clip-relative in 1.12.13. They also already passed through boundTime().

The relevant change was #1824. The previous boundary check happened to accept the absolute seekableStart() value passed by #resetPlaybackIfNeeded. The revised check exposed that incorrect argument.

I rechecked the published packages. For a clip spanning 300–600 seconds, boundTime(300) returns 300 in 1.12.13 and 1.13.0, but 600 in 1.14.0 and 1.15.6. Passing 0 returns the intended 300 in the affected versions.

The proposed fix still looks correct. My explanation of why it became necessary was wrong. The Maverick attribution in #1843 was also stronger than the evidence supported.

This re-check verifies the boundary calculation, not a fresh full-browser regression run.

This branch has not been deployed

No deployments
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.

Clipped video seeks to its end on play since 1.14.0 (#resetPlaybackIfNeeded passes absolute seekableStart to a clip-relative seek request)

1 participant