Files
hyperframes/packages/core
Miguel Ángel f2c63ca424 fix(core): a video's voice keeps pace with the playhead instead of trailing it (#4511)
* fix(core): a video's voice keeps pace with the playhead instead of trailing it

A playing video is exempt from seek-based drift correction, because seeking resets its decoder and stutters. Nothing else corrected it, so a talking-head video that started a few frames late (92 ms measured in Studio) kept its voice behind the playhead for the whole clip, and further behind after each loop. Media sync now runs a video that drifts past the 40 ms sync tolerance 3% fast or slow (pitch preserved) until it is within 10 ms, then returns it to the authored rate. The rate is written only when steering starts or stops, since every rate change costs Chrome a frame; large jumps still hard-seek. Measured in Studio: 92 ms constant on main, about 10 ms steady with the fix.

* fix(core): video steering holds through speed ramps and hard syncs

Review follow-ups: steering inferred itself from the element's current rate, which a speed ramp moves every tick, so a ramped video stopped steering at the 40 ms tolerance instead of 10 ms; the direction is now kept per element. A hard-sync tick plays the authored rate, since its pre-seek offset is stale, and a rate that reads back at lower precision is not rewritten every tick.

* test(core): pin clearing and write-once for video steering

Review follow-up: nothing failed when the steering state was left uncleared on eviction or pause, or when the rate compare went back to strict equality. Tests now cover all three, and the sync-state test seam reports the steering map so the proxy's leak test sees it.

* chore(core): format the video steering and keep its tolerance module-private

* chore(core): shorten the video steering comment
2026-09-26 05:02:44 +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