mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
fix(studio,runtime,engine,compiler): 8 bug fixes — audio, render, timeline, Lottie, thumbnails, video render (#133)
## Summary
**Original 5 bugs fixed:**
- **Bug 1 — Audio silent after seek**: Added `Accept-Ranges` / `Content-Length` + `206 Partial Content` to the static asset server for byte-range seeking.
- **Bug 2 — Download 404 after restart**: Render list endpoint now registers on-disk renders into the in-memory job map.
- **Bug 3 — Timeline stops at GSAP end**: `resolveRootTimelineFromDocument` pads the GSAP timeline to match `data-duration` when the composition declares longer.
- **Bug 4 — Render stuck at 0%**: Store `jobState` reference (not spread copy) so async progress mutations reach the SSE stream.
- **Bug 5 — Lottie missing in preview/render**: Two fixes — (a) moved Lottie adapter before GSAP so `onUpdate` wins; (b) fixed bundler silently dropping external CDN `\<script src>` tags from sub-compositions (root cause: `$content(s).html()` returns `""` for external scripts).
**3 additional bugs fixed:**
- **Bug 6 — Blank thumbnails outside monorepo**: Implemented `generateThumbnail` in the CLI adapter using Puppeteer.
- **Bug 7 — Video empty in rendered sub-compositions**: Fixed `parseVideoElements` selector from `video[id][src]` to `video[src][data-start]` + auto-assign IDs.
- **Render errors**: Failed renders now show their error message in the renders panel.
## Commits
| Commit | Description |
| --- | --- |
| `3951c6f` | fix(studio): store render job reference instead of snapshot copy |
| `f331c30` | fix(studio): make previously-completed renders downloadable after restart |
| `a5e2d04` | fix(studio): add range request support for audio/video seeking in preview |
| `f24317a` | fix(runtime): pad GSAP timeline to data-duration when composition declares longer duration |
| `7cf38ca` | fix(runtime): fix Lottie adapter conflicting with GSAP-driven animations |
| `bc99209` | fix(studio): surface render error messages in the renders panel |
| `8fc9e8b` | fix(cli): implement generateThumbnail in studio adapter |
| `90277ea` | fix(engine): render videos inside sub-compositions that lack an explicit id |
| `f5bb579` | fix(compiler): preserve external CDN scripts from sub-compositions in bundle |
## Test plan
- [x] `golden-lyric-video`: seek → audio plays from seeked position
- [x] Any project: render → progress advances past 0%, reaches 100%
- [x] Any project: complete render, restart `hyperframes dev`, Download → works
- [x] `intro-vid`: play → runs full 5s (not stopping at 3s)
- [x] `hyperframe-build-up-demo`: play → rocket Lottie visible during 0-2s ✅ verified
- [x] Outside monorepo: Compositions sidebar shows thumbnail images (not blank)
- [x] `bug.zip` project: render → video in polaroid sub-composition appears in output
- [x] Trigger a failed render → error message shown
This commit is contained in:
@@ -146,6 +146,12 @@ async function compileHtmlFile(
|
||||
compiledHtml = clampDurations(compiledHtml, clampList);
|
||||
}
|
||||
|
||||
// Strip crossorigin from video elements: the render pipeline replaces them with
|
||||
// injected frame images, so the browser never needs to load the source.
|
||||
// Without this, videos with crossorigin="anonymous" targeting CORS-restricted
|
||||
// origins (e.g. S3 without CORS headers) keep readyState=0, blocking page setup.
|
||||
compiledHtml = compiledHtml.replace(/(<video\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
|
||||
|
||||
return { html: compiledHtml, unresolvedCompositions };
|
||||
}
|
||||
|
||||
@@ -728,6 +734,28 @@ export async function compileForRender(
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// Persist auto-assigned IDs back into the HTML so the compiled file served
|
||||
// to Puppeteer has matching element IDs. parseVideoElements uses parseHTML
|
||||
// internally and sets el.id = "hf-video-N" on the JSDOM node, but that does
|
||||
// not mutate the html string. We do one more DOM pass here to write those IDs
|
||||
// into the document and re-serialize — only if there are any id-less videos.
|
||||
const autoIdVideos = videos.filter((v) => v.id.startsWith("hf-video-"));
|
||||
let htmlWithIds = html;
|
||||
if (autoIdVideos.length > 0) {
|
||||
const { document: idDoc } = parseHTML(html);
|
||||
let changed = false;
|
||||
for (const v of autoIdVideos) {
|
||||
const el = idDoc.querySelector(`video[src="${v.src}"]:not([id])`);
|
||||
if (el) {
|
||||
el.id = v.id;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
htmlWithIds = idDoc.documentElement?.outerHTML ?? html;
|
||||
}
|
||||
}
|
||||
|
||||
// Read dimensions from root composition element using DOM parser
|
||||
const { document } = parseHTML(html);
|
||||
const rootEl = document.querySelector("[data-composition-id]");
|
||||
@@ -745,7 +773,7 @@ export async function compileForRender(
|
||||
: 0;
|
||||
|
||||
return {
|
||||
html,
|
||||
html: htmlWithIds,
|
||||
subCompositions,
|
||||
videos,
|
||||
audios,
|
||||
|
||||
Reference in New Issue
Block a user