Files
hyperframes/packages/core
Vance IngallsandClaude Opus 4.8 1ff99a50f5 fix(core): play bounded WebAudio clips full-length at non-1x playback rate (#1494)
* fix(core): play bounded WebAudio clips full-length at non-1x playback rate

startBoundedSource passed `clipDuration * rate` as start()'s duration arg, but
that arg is buffer-content seconds while clipDuration is composition seconds.
Media advances 1:1 with composition (the global rate scales the transport clock
and the source playbackRate together), so the content to play is exactly
clipDuration. Multiplying by rate truncated the clip at rate < 1 (audio cut out
at the midpoint on half-speed playback) and overran it at rate > 1. Drop the
multiply — playbackRate alone stretches the fixed content to the right wall time.

Adds a half-speed regression test and corrects the prior test that asserted the
rate-scaled (overrunning) bound.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(core): restore the mediaRate scaling on the WebAudio clip bound

The bound this branch removed was correct. `start()`'s duration argument is
buffer seconds, and an element with `data-playback-rate="2"` consumes two
buffer seconds per composition second, so a `clipDuration`-second clip needs
`clipDuration * mediaRate` of buffer. Wall time then works out as
`(clipDuration * mediaRate) / (mediaRate * globalRate) = clipDuration /
globalRate`, which is the transport duration that was wanted. Dropping the
factor truncated authored 2x clips at their midpoint and overran authored
0.5x ones — and the sibling line still scaled `sourceElapsed` by mediaRate,
so `remaining` mixed buffer with composition seconds and only landed right at
mediaRate = 1.

The branch's half-speed regression could not have caught this: it changed the
GLOBAL rate on an element whose authored rate is 1, and the global rate
cancels out (it scales the transport clock and the source's playbackRate
together). Both formulas return 10 there, so the test passed before the
change it was meant to justify.

Replaced with the two cases that do discriminate — a clip authored at 2x and
one at 0.5x, each asserting the buffer-second bound. Both fail if the factor
is dropped again, as does the pre-existing authored-2x/global-0.5x contract
test the removal was breaking.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 23:37:30 -07:00
..
2026-08-20 21:47:29 -07: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)

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