Skip to content

fix(player): detect the runtime by __player and honour runtime-src for src embeds - #4246

Closed
srikarsunchu wants to merge 1 commit into
heygen-com:mainfrom
srikarsunchu:fix/player-src-runtime-bridge-and-runtime-src
Closed

srikarsunchu wants to merge 1 commit into
heygen-com:mainfrom
srikarsunchu:fix/player-src-runtime-bridge-and-runtime-src

Conversation

@srikarsunchu

Copy link
Copy Markdown

Closes #4002
Closes #4003

Problem

Two related defects in how <hyperframes-player src="..."> discovers and injects the core runtime:

  1. window.__hf was treated as the runtime bridge. @hyperframes/shader-transitions also creates that global (window.__hf = window.__hf || {}) to publish shaderTransitionsReady. Any authored composition that uses shader transitions and registers its own __timelines therefore looked like it already had the runtime: the probe never injected it, hasRuntimeBridge() refused the direct-timeline adapter, and the element emitted error: "Composition timeline not found after 8s" with a black frame (player: window.__hf from @hyperframes/shader-transitions makes an src embed reject its own composition #4002).
  2. runtime-src only applied to srcdoc. The probe's late injection closed over the module-level CDN constant, so an src embed always fetched the runtime from jsDelivr even when the element carried a valid runtime-src. Offline, air-gapped, or behind a script-src 'self' CSP that silently fails and the embed times out (player: runtime-src is honoured for srcdoc only - an src embed always fetches the runtime from jsDelivr #4003).

Fix

  • hasRuntimeBridge(win) now checks for window.__player only. The core runtime installs __player synchronously in the same task as __hf (initSandboxRuntimeModular has no awaits between the two), so there is no tick on which a real runtime has __hf but not __player. Both the probe tick's hasRuntime and the adapter path use the same helper.
  • The runtime-src resolver in shader-options.ts is exported as resolveRuntimeUrlFromElement and the player hands it to the probe via a new resolveRuntimeUrl callback. It is read at injection time, so a runtime-src set after construction is still honoured. The existing origin/loopback validation is unchanged and now covers both paths.
  • The injected <script> gets an onerror handler: a blocked or 404 runtime now stops the probe and reports HyperFrames runtime failed to load from <url> instead of waiting out the 8 s timeout with a message about the timeline.

Tests

New unit tests in composition-probe.test.ts cover: the shader-transitions __hf namespace next to __timelines resolving via the direct-timeline adapter; __player still winning over __timelines; injection into a nested composition despite a pre-existing __hf; injection using resolveRuntimeUrl; and the script error path stopping the probe without a second timeout error. hyperframes-player.test.ts gains an element-level test that an src embed with runtime-src injects the configured URL.

bun run --filter @hyperframes/player test (excluding the pre-existing slideshow presenter-mode failures, which fail identically on main in this environment), tsc --noEmit, oxlint, oxfmt --check, the lefthook pre-commit gate, and the player build with verify-runtime-pin.mjs all pass.

Repro from the issues (both host.html variants in the linked gist) is expected to report ready for the shader-transitions composition and to request /local-runtime.js instead of jsDelivr for the src embed.

🤖 Generated with Claude Code

…r src embeds

`<hyperframes-player src="...">` treated the existence of `window.__hf` in
the composition frame as "the core runtime is present". `__hf` is a shared
namespace, not a bridge: `@hyperframes/shader-transitions` creates it too
(`window.__hf = window.__hf || {}`) to publish `shaderTransitionsReady`. An
authored composition that used shader transitions and registered its own
`__timelines` therefore never had the runtime injected and had its
direct-timeline adapter refused, and the embed failed 8 s later with
"Composition timeline not found". The only global the player ever drives is
`__player`, which the runtime installs synchronously in the same task as
`__hf`, so that is now the bridge check on both the probe tick and the
adapter path.

The probe's injection also always loaded the runtime from jsDelivr: the
`runtime-src` attribute was resolved for srcdoc only. The resolver is now
shared, and the player passes it to the probe, so an `src` embed can serve
its own pinned copy offline or under a `script-src 'self'` CSP. A runtime
that fails to load reports its URL on the script's own error event instead
of being indistinguishable from a slow one until the timeout.

Closes heygen-com#4002
Closes heygen-com#4003

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@miguel-heygen

Copy link
Copy Markdown
Collaborator

Thanks for this. CI is green now except the captures gate, which is a repo rule for any player change: the PR body needs a ## Before and a ## After H2 section, each with an image or short video, showing the player in an embed where the runtime is detected via __player and runtime src is honoured (before: the old behaviour, after: this branch). Attach with gh pr edit 4246 --attach <file> or drag into the body. Once the gate is green it goes to review.

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Closing as stale after a few weeks without activity. Thanks for the contribution; feel free to reopen after rebasing on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants