feat(player): synchronous seek() API with same-origin detection#397
feat(player): synchronous seek() API with same-origin detection#397vanceingalls wants to merge 2 commits intoperf/p1-4-coalesce-mirror-parent-media-timefrom
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
jrusso1020
left a comment
There was a problem hiding this comment.
Formalizes a pattern Studio was already reaching for under the hood (useTimelinePlayer.ts:233). Good: single-task seek on same-origin embeds, transparent postMessage fallback on SecurityError / missing __player / typeof seek !== "function" / runtime panic. Every branch is tested, including the _currentTime cache must update regardless — that catches the easy-to-miss regression where a cross-origin scrub leaves the controls UI showing stale time.
One subtle semantic change worth calling out in the release notes: the sync path passes timeInSeconds through verbatim, while the postMessage path rounds to Math.round(timeInSeconds * DEFAULT_FPS) at the wire boundary. Same-origin embeds will now land sub-frame scrubs at their exact requested time; cross-origin embeds still quantize to frames. Scrub UIs can now drive fractional-frame times on same-origin, which is the improvement — just means a test that does player.seek(7.3333) and asserts exact-equality will only pass same-origin.
Approved.
— Rames Jusso
7600cd7 to
62e91f3
Compare
9c5a23d to
048a3c0
Compare
Formalizes the same-origin shortcut Studio has been using privately (`iframe.contentWindow.__player.seek` in `useTimelinePlayer.ts`) as a first-class behavior of `<hyperframes-player>`'s public `seek()` method. The player now tries the runtime's `__player.seek` directly via a new `_trySyncSeek()` helper. When the iframe is reachable (same-origin and the runtime has installed `__player`), the seek lands in the same task as the input event — no postMessage hop, no extra microtask, no perceived scrub lag. Cross-origin embeds and pre-bootstrap calls fall through to the existing `_sendControl` postMessage bridge transparently, preserving the original async semantics for external hosts. Detection is a try/catch on `contentWindow` access (real cross-origin iframes throw a SecurityError) plus a typeof guard on `__player.seek`. Local `_currentTime`, the `paused` flag, and the controls UI are updated on both paths so scrubs never leave stale state. The runtime-side `seek` is the same wrapped function the postMessage handler calls (`installRuntimeControlBridge` routes through `player.seek`), so `markExplicitSeek()` and downstream runtime state stay identical between the two paths.
Per reviewer feedback on #397: the same-origin path forwards `timeInSeconds` verbatim to `__player.seek` (sub-frame precision), while the postMessage fallback rounds to the nearest integer frame for transport. Surface that asymmetry on the public `seek` JSDoc so external callers (notably studio scrub UIs) understand which transport is in effect for their embed. Doc-only — no behavior delta.
048a3c0 to
61189b6
Compare

Summary
Formalizes the same-origin shortcut Studio has been using privately (
iframe.contentWindow.__player.seekinuseTimelinePlayer.ts) as a first-class behavior of<hyperframes-player>'s publicseek()method. Same-origin seeks now land in the same task as the input event — no postMessage hop, no extra microtask, no perceived scrub lag. Cross-origin embeds fall through to the existing async bridge transparently.Why
Step
P3-1of the player perf proposal. The currentseek()always posts a message to the iframe runtime, which means a single user scrub incurs:markExplicitSeekand updates DOMSame-origin embeds (Studio, preview pane, embedded compositions) can skip all four by calling the runtime's
seekdirectly. Studio was already doing this manually but had to duplicate the local-state bookkeeping (_currentTime,paused, controls UI) — making it a first-class behavior of the player removes the workaround and gives every same-origin consumer the win for free.What changed
_trySyncSeek(time)helper attempts a synchronous call into the iframe'swindow.__player.seek. Returnstrueon success,falseon cross-origin or pre-bootstrap.seek()calls_trySyncSeekfirst, falls through to the existing_sendControlpostMessage path when sync isn't available.try/catchoncontentWindowaccess (real cross-origin iframes throwSecurityError) plus atypeofguard on__player.seek._currentTime, thepausedflag, and the controls UI update on both paths so scrubs never leave stale state.seekis the same wrapped function the postMessage handler calls —installRuntimeControlBridgeroutes throughplayer.seek, somarkExplicitSeek()and downstream runtime state are identical between the two paths.Test plan
hyperframes-player.test.tscovering:__player.seeksynchronously and skips postMessage.SecurityErroroncontentWindow) falls back to postMessage.__playerinstalled) falls back to postMessage.__player.seeknot a function falls back to postMessage._currentTime,paused, and controls all stay in sync on both paths.__player.seekpropagate without corrupting state.Stack
Step
P3-1of the player perf proposal. Independent of theP1-*work — this is a pure latency win on the seek/scrub path. Combined withP3-2(srcdoc composition switching, next in the stack) it removes most of the iframe-bridge overhead from the studio scrubber.