mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
* 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>
@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
Related packages
@hyperframes/engine— rendering engine that drives the browser@hyperframes/producer— full render pipeline (capture + encode)hyperframes— CLI