Skip to content

fix(runtime): respect keepPlaying option in player seek#863

Open
calcarazgre646 wants to merge 1 commit into
heygen-com:mainfrom
calcarazgre646:fix/runtime-seek-keep-playing
Open

fix(runtime): respect keepPlaying option in player seek#863
calcarazgre646 wants to merge 1 commit into
heygen-com:mainfrom
calcarazgre646:fix/runtime-seek-keep-playing

Conversation

@calcarazgre646
Copy link
Copy Markdown
Contributor

Summary

Closes the follow-up I promised in #842 (comment): the __player runtime adapter ignored the { keepPlaying: true } option that A/E Jump-to-in/out shortcuts send, so playback pauses on every press even though the caller asked to preserve it. Only the wrapTimeline (GSAP) path honoured the option.

Background

PR #842 added seek: (time, options?: { keepPlaying?: boolean }) to PlaybackAdapter and wired the A/E shortcuts in usePlaybackKeyboard to pass { keepPlaying: true }. The fix landed correctly for compositions backed by __timeline / __timelines (GSAP), because wrapTimeline reads the option and skips its internal tl.pause().

For compositions backed by window.__player (packages/core/src/runtime/init.ts:1529), useTimelinePlayer.getAdapter() returns the runtime adapter directly without a wrapper. That adapter's seek is basePlayer.seek from createRuntimePlayer (packages/core/src/runtime/player.ts:147), and the implementation was:

seek: (timeSeconds: number) => {
  // ...quantize + deterministic seek...
  deps.setIsPlaying(false);
  deps.onSyncMedia(quantized, false);
  // ...
},

setIsPlaying(false) runs unconditionally, so A/E pauses the moment the shortcut fires. The option that the Studio sends never reaches the implementation because both the local basePlayer shape in createPlayerApiCompat and the public PlayerAPI.seek / RuntimePlayer.seek types only declared (time: number) => void.

Fix

  1. Extend the type contract from end to end so the option is preserved through createPlayerApiCompat:

    • core.types.tsPlayerAPI.seek(time, options?: { keepPlaying?: boolean })
    • runtime/types.tsRuntimePlayer.seek signature
    • runtime/init.ts:88 — local basePlayer shape in createPlayerApiCompat
  2. Implement the behaviour in createRuntimePlayer.seek:

    • Capture wasPlaying before the deterministic seek helper pauses the master and rearmed siblings.
    • When options.keepPlaying && wasPlaying, resume the master + siblings, reapply playbackRate via timeScale, and emit onDeterministicPlay + onShowNativeVideos + onSyncMedia(t, true) so media and analytics stay in sync.
    • Default branch (no options, explicit keepPlaying: false, or wasPlaying === false) keeps the existing setIsPlaying(false) + onSyncMedia(t, false) path. No back-compat change.

Test plan

Added 6 new tests in packages/core/src/runtime/player.test.ts under describe(\"keepPlaying option\"):

  • Preserves play state when keepPlaying: true and playback was active (no setIsPlaying(false), emits onDeterministicPlay + onSyncMedia(t, true)).
  • Resumes the master timeline after the deterministic seek pauses it (asserts play() is invoked after the last internal pause() via invocationCallOrder).
  • Applies playbackRate to master and siblings on resume.
  • Stays paused when keepPlaying: true but playback was not active (intent is preserve, not force).
  • Explicit keepPlaying: false matches default behaviour.
  • No options matches default behaviour (back-compat).

Other validations:

  • bun run --cwd packages/core test → 868/868 passing (was 862, +6 new).
  • bun run --cwd packages/studio test → 514/514 passing, no regression in the consuming side.
  • bun run --cwd packages/core typecheck and bun run --cwd packages/studio typecheck clean.
  • bunx oxlint and bunx oxfmt --check clean on touched files.
  • Lefthook pre-commit ran lint + format + typecheck + commitlint.

Out of scope

createStaticSeekPlaybackAdapter in packages/studio/src/player/lib/playbackAdapter.ts still has seek: (time) => void (no options). Its current behaviour happens to preserve playback by accident (the playing flag drives the RAF ticker independently), but the contract is mismatched. Happy to follow up if a maintainer wants the static-seek adapter brought in line with the same shape.

Related

The runtime player's seek unconditionally pauses on every invocation, so
A/E Jump-to-in/out shortcuts (which pass { keepPlaying: true } per PR
heygen-com#842) pause playback in compositions backed by the __player runtime
adapter. Only the wrapTimeline path honoured the option.

Extend RuntimePlayer.seek and the PlayerAPI contract to accept
{ keepPlaying?: boolean }. When keepPlaying is set and playback was
active, resume the master plus rearmed sibling timelines and emit the
play-state events so media and analytics stay in sync. Default behaviour
is unchanged when no options are passed.
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