Files
Vance IngallsandClaude Opus 5.5 d05bc4215d perf(core): preview runtime scales with what plays, not with the film's clip count (#5674)
* perf(core): probe media volume with one timeline walk per pass, not one per element

Each bind, rebind and scene-swap pass probes every media element for volume
automation, and each probe flattened the whole captured timeline
(getChildren(true, true, false)) to ask whether any tween could move that one
element's volume. A pass cost elements x tweens. Elements with no automation are
never cached, so the cost came back on every rebind pass.

On a two-hour podcast episode (2,354 media clips, ~100k caption tweens) that was
several seconds of preview main thread per pass (CPU profile of the project's
open in Desktop: IE/PE -> getChildren, ~6 s).

volumeMoversOf() now walks the tweens once and lists the elements a tween names
volume on, plus whether any non-DOM setter is tweened. Each pass builds it lazily
and shares it with every probe; a probe without one walks as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(core): find the master bus from the first composition root

The top-level root is the first [data-composition-id] in document order, so
one match replaces a scan of every composition node, read once per clip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(core): lay out a clip again only when its layout inputs change

applyClipLayout ran on every timeline post and, for every root clip, lifted
display:none, read computed style and restored it. A clip whose inline
layout, class and data-width/height are as last laid out gets the same
answer, so it is skipped. On a 2,354-clip episode that was 4 of every 10 s
of playback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(core): a clip is laid out again only when its layout inputs change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(core): play a long film without re-walking it every frame

- The visibility pass skips root-level media far from the playhead during
  steady playback: hidden last pass, not loading or armed, and its window
  (widened by every look-ahead) clear of the span played and the loop start.
  Seeks, forced syncs and renders visit every node.
- The GSAP re-render saves and restores only the landing marks near the
  playhead, placed once per timeline structure; the zero-duration-callback
  check is cached the same way. A caption track of 38k sets was walked twice
  a frame.
- Registered timeline hosts, the root composition and the per-frame
  whole-film queries ([data-start], audio groups, timed audio) are kept for
  one timing revision; a render reads fresh.
- The runtime's own media release (src reset) and load events of clips with
  an authored length no longer count as timing changes, which re-posted the
  timeline about twice a second.
- The timeline payload resolves a stacking context once per parent.

2,354-clip episode in Desktop: playback 15 -> 67-89 fps (unminified runtime).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(core): check each element once per batch of raster-hint records, and cache the root from init

- A script building a large tree (a caption track) made each attribute record
  re-walk its subtree with getComputedStyle; a batch now checks each element
  once, an element whose own attributes changed still reading its own hints.
- The root composition cache installs the timing observer itself: a root
  with a declared length never reads the duration floors that installed it,
  so init looked the root up once per media clip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): type the playback visibility skip for any element, and cache clip layout only once loaded

The skip's predicate took an HTMLElement where the pass hands it a
StylableElement (tsconfig.runtime.json). The layout cache records a clip only
once the document has loaded, so a pass before stylesheets apply is redone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(core): realm-safe element checks in the timing caches; cut added comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:33:19 +00:00
..
2026-10-16 18:16:28 +00:00

@hyperframes/core

Types, parsers, generators, compiler, linter, runtime, and frame adapters for the Hyperframes video framework.

Install

npm install @hyperframes/core

Most users don't need to install core directly — the CLI, producer, and studio packages depend on it internally.

What's inside

Module Description
Types TimelineElement, CompositionSpec, Asset, canvas dimensions, defaults
Parsers parseHtml — extract timeline elements from HTML; parseGsapScript — parse GSAP animations
Generators generateHyperframesHtml — produce valid Hyperframes HTML from a composition spec
Compiler compileTimingAttrs — resolve data-start / data-duration into absolute times
Linter lintHyperframeHtml — validate Hyperframes HTML (missing attributes, overlapping tracks, etc.)
Runtime IIFE script injected into the browser — manages seek, media playback, and the window.__hf protocol
Frame Adapters Pluggable animation drivers (GSAP, Lottie, CSS, or custom)

Generated composition trust

Composition generators require trusted authors for code-bearing inputs. styles and generateHyperframesStyles preserve authored CSS, which can load external resources. animations may contain __raw: values that are emitted as JavaScript; includeScripts: true includes executable timeline code. serializeGsapAnimations also accepts raw preamble, postamble, and a code-bearing timelineVar. Never fill these inputs with untrusted data. Attribute encoding and closing-tag containment are not a sandbox; render untrusted compositions in an appropriately isolated execution environment and never serve them on a privileged origin.

Text content retains the supported inline-formatting sanitizer contract. The clip parser intentionally flattens inner formatting to text, so parse/generate is not a lossless replacement for editing the source HTML.

Frame Adapters

A frame adapter tells the engine how to seek your animation to a specific frame:

import { createGSAPFrameAdapter } from "@hyperframes/core";

const adapter = createGSAPFrameAdapter({
  getTimeline: () => gsap.timeline(),
  compositionId: "my-video",
});

Implement FrameAdapter for custom animation runtimes:

import type { FrameAdapter } from "@hyperframes/core";

const myAdapter: FrameAdapter = {
  id: "my-adapter",
  getDurationFrames: () => 300,
  seekFrame: (frame) => {
    /* seek your animation */
  },
};

Parsing and generating HTML

import { parseHtml, generateHyperframesHtml } from "@hyperframes/core";

const { elements, metadata } = parseHtml(htmlString);
const html = generateHyperframesHtml(spec);

Linting

import { lintHyperframeHtml } from "@hyperframes/core/lint";

const result = lintHyperframeHtml(htmlString);
// result.findings: { severity, message, elementId }[]

Documentation

Full documentation: hyperframes.heygen.com/packages/core