mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Review finding 1, the last of the fifteen. A group's identity was the raw author `id`, which is unique only per composition FILE — and the render document is the inlined union of every file. So a sub-composition declaring a bus AND its members, used twice, put both instances' members under one key: one sub-mix for two independent buses, and the second bus element overwrote the first, applying its fader, chain, label and automation to the first instance's audio. With only the SECOND instance muted, `memberGroupHidden` dropped every member of the merged group — both instances gone from the export. Compiled a twice-used sub-composition to find out what actually separates the two instances, rather than guessing: - members are ALREADY disambiguated — `data-hf-render-id="m1"` / `"m1__hf2"` - buses are not: `MEDIA_SELECTOR` is `video[src], audio[src], img[src]` - both instances carry `data-composition-id="bedcomp"`, the file's own id, so id strings cannot tell them apart — only the subtree element can Fixed at the boundary that already owns this collision class. `assignMediaRenderIds` now stamps every `<hf-audio-group>` with a document-unique `data-hf-render-id` from the SAME `taken` set (so a bus key can never collide with a clip key either), and stamps each member with `data-hf-group-render-id` = the render id of the bus in its OWN composition subtree, resolved with `closest()`. A member whose bus is not in its subtree — a hand-authored bus in the root with members in scenes — falls back to the first, which is the pre-existing reading and the only sensible one there. `resolveAudioGroups` and the mixer prefer the stamped key and fall back to the author id, so the LIVE PREVIEW — which has no stamps — reads exactly as before. `resolveGroupElement` tries the stamped instance first, since `getElementById` can only ever find the author id. Verified with the reviewer's own repro, end to end through the real compiler: before, `parseAudioElements` returned both members under one `bed` group; after, muting instance B drops only B's member and A survives at its own 0.5 fader. Two compiler tests (instance pairing, single-instance stability, element-less group untouched) and two mixer tests, all verified against a revert. **Still divergent, deliberately: the live preview.** `groupInput` resolves by id against the uncompiled document, so two instances still share one bus there. The export was the audible bug — a muted instance silencing another's audio — and fixing preview needs runtime subtree resolution, which is a separate change. core 2493, engine 1617, studio 4389. fallow clean.
@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