feat: add @hyperframes/renderer - client-side video rendering#239
Draft
miguel-heygen wants to merge 31 commits intomainfrom
Draft
feat: add @hyperframes/renderer - client-side video rendering#239miguel-heygen wants to merge 31 commits intomainfrom
miguel-heygen wants to merge 31 commits intomainfrom
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Design spec for @hyperframes/renderer — a new browser-only package that renders compositions to MP4 using WebCodecs + MediaBunny + OffscreenCanvas workers, with SnapDOM as interim frame source and html-in-canvas as the future pixel-perfect path. Replaces the need for Puppeteer + FFmpeg. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13-task plan covering package scaffold, SnapDOM frame source, WebCodecs encoding worker, MediaBunny muxer, OfflineAudioContext audio mixer, parallel iframe capture, video frame injector, and main orchestrator. 56 total steps with TDD approach. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the @hyperframes/renderer package skeleton: package.json, tsconfig.json, src/types.ts (all pipeline interfaces), src/compat.ts (WebCodecs feature detection), and src/index.ts (re-exports). Typecheck passes with zero errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements SnapdomFrameSource class that loads compositions in hidden iframes, seeks via window.__hf.seek(), and captures DOM frames using @zumer/snapdom. Adds jsdom dev dependency for the test environment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the encoding pipeline for @hyperframes/renderer: worker message protocol types, Web Worker entry point (VideoEncoder + MediaBunny muxer), and the main-thread Encoder class that manages the worker lifecycle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements mixAudio() with TDD: decodes audio sources, applies volume/timing via GainNode, and renders a mixed PCM AudioBuffer using OfflineAudioContext. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…enderer) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix audio format: replace interleaveChannels with concatPlanarChannels - Fix MIME type: derive video/mp4 vs video/webm from outputFormat in finalize - Add isSupported() guard at top of render() for early failure - Fix durationMs to report wall-clock render time (totalMs) - Fix audio filter to use strict equality (hasAudio === true) - Remove unused progress type from WorkerOutMessage union Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…page - Replace requestAnimationFrame with setTimeout in __hf protocol polling (rAF doesn't fire for off-screen iframes) - Add workerUrl option to EncoderOptions and RenderConfig for configuring the encoding worker URL (needed for bundled deployments) - Add QA test page with self-contained CSS-animated composition Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… progress ETA - Capture doc.body instead of doc.documentElement in SnapDOM to fix 75% viewport mismatch - Make audio track conditional (hasAudio flag) to prevent corrupt empty audio tracks - Add publishConfig for npm publishing - Wire ProgressTracker for estimatedTimeRemaining and captureRate in progress callbacks - Recompute video overlay canvas position per-frame for GSAP-animated elements - Add AbortSignal to captureAll for cancel propagation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ression harness drawElementImage: pixel-perfect capture via html-in-canvas API using <canvas layoutsubtree> with iframe child. Achieves 42 dB PSNR vs 30 dB for SnapDOM. Requires Chrome --enable-blink-features=CanvasDrawElement. Tab-capture: experimental getDisplayMedia-based capture. Also adds regression parity scripts for compiling test compositions and comparing PSNR against golden reference renders. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Browser" export button next to the existing server-side Export button in the RenderQueue panel. Uses the @hyperframes/renderer package to render compositions entirely client-side via WebCodecs, with progress tracked in the existing job list and automatic file download on completion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83a0577 to
e924c76
Compare
…ker URL - SnapDOM frame source now detects window.__player (studio runtime) in addition to window.__hf and creates a bridge automatically. This lets the renderer work with studio preview URLs directly. - Studio useBrowserRender hook: force snapdom frame source and provide explicit worker URL for Vite dev compatibility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- iframe-pool: setTimeout(0) yield after each captured frame - SnapDOM source: requestAnimationFrame after seek() before capture to ensure layout/paint is processed This prevents Chrome from becoming unresponsive during long renders, keeping CDP, DevTools, and UI interactions functional. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single-iframe capture keeps Chrome responsive during long renders. With 8 parallel iframes, SnapDOM overwhelms the main thread and locks the browser. Concurrency=1 trades speed for reliability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ckground rendering - Discover media elements from DOM when __player bridge doesn't expose hf.media (scans both <video> and <audio> elements with data-start/data-duration) - Resolve media src to absolute URLs so audio mixer fetch() works cross-origin - Seek original video elements directly instead of clone+canvas overlay approach (SnapDOM natively captures <video> via drawImage) - Force synchronous reflow after GSAP seek to prevent stale computed styles - Add warmup phase: wait for fonts.ready + settle ticks before first capture - Replace all requestAnimationFrame with setTimeout for background tab support - Fix interleaved capture/encoding progress by deferring encoding reports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…set copying - Use async spawn for agent-browser wait so Node event loop stays active for the upload HTTP server to process browser POST requests - Add CORS headers to upload server responses - Add frameSource: 'snapdom' to test page (headless Chrome auto-detects tab-capture which hangs waiting for screen share permission) - Copy source assets to output dir (CSS, fonts not inlined by compiler) - Use compile-test.ts for compilation (injects runtime + __hf bridge) - Write serve.json with cleanUrls:false to prevent .html→extensionless redirects - Increase upload timeout from 10s to 300s for long compositions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…om PR - Remove docs/superpowers/plans and specs (brainstorming artifacts) - Remove packages/renderer/qa/ (dev testing pages) - Remove packages/renderer/pnpm-lock.yaml (monorepo uses root lockfile) - Remove run-parity-test.sh (superseded by regression-parity.ts) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep both @hyperframes/player (from main) and @hyperframes/renderer deps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multi-tab parallel capture using noopener windows + BroadcastChannel for true multi-process SnapDOM parallelism. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Opens N browser tabs (via window.open with noopener) for true multi-process parallelism. Each tab gets its own Chromium renderer process with independent main thread, bypassing the single-thread SnapDOM bottleneck. Architecture: - TurboPool coordinator opens N minimized offscreen tabs - Each tab loads composition via SnapdomFrameSource independently - Frames captured as PNG, sent via BroadcastChannel to coordinator - Coordinator decodes to ImageBitmap, feeds reorder buffer + encoder - Concurrency: min(cores/2, 6), capped at 6 tabs - Falls back to normal IframePool if popups blocked Also: - Add turbo toggle (lightning bolt) to Studio render UI - Add /api/projects/:id/renders/upload endpoint for saving browser-rendered videos to project renders directory - Export isTurboSupported() from @hyperframes/renderer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Vite middleware was reading request bodies as UTF-8 strings, corrupting binary data from the browser renderer's blob upload. Changed to Buffer.concat() for binary-safe body reading. Also adds error handling for failed upload responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Seek to 0.1s first to trigger sub-composition iframe loading - Wait for fonts.ready in all sub-composition iframes (not just main) - Increase font timeout to 5s for CDN fonts - Add more settle ticks (10x10ms) for complex GSAP nested timelines - Double-seek to 0 after warmup for clean first frame - Increase per-frame yield from 0ms to 4ms for better GSAP sync Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ition jumps After seek, force offsetHeight reflow on all nested iframes (not just the main document). Sub-compositions have their own GSAP timelines in separate documents — without reflowing them, transform values (x, y) captured by SnapDOM can be stale between frames, causing text/element position jumps. Also reorders the yield-before-reflow (let runtime propagate seek to sub-compositions first, then reflow everything at once). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
window.open with noopener always returns null to the opener per spec, which caused the POPUP_BLOCKED fallback to always trigger. Removed noopener so the opener gets a window reference for popup-blocked detection. Note: without noopener, popup windows share the opener's event loop in same-origin contexts, limiting parallelism. True multi-process parallelism requires cross-origin isolation or future APIs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
@hyperframes/rendererpackage — renders HyperFrames compositions to MP4 entirely in the browser. Zero server, zero Puppeteer, zero FFmpeg.Three frame sources (pluggable architecture)
How it works
parallelCoordinator)PSNR Parity Results (vs. BeginFrame golden reference)
drawElementImageachieves 42 dB — 12 dB above the producer's 30 dB regression threshold. This is broadcast-quality parity with the server-side renderer.Studio Integration
Added "Browser" export button in the render queue panel. Uses
@hyperframes/renderervia dynamic import — click to render client-side and auto-download the MP4.Package structure
Browser requirements
Chrome 94+, Firefox 130+, Safari 26+ (WebCodecs).
drawElementImagerequires Chrome with--enable-blink-features=CanvasDrawElement.Test plan
🤖 Generated with Claude Code