Commit Graph

22 Commits

Author SHA1 Message Date
Miguel Ángel 0eb4d079dc fix(renderer): remove noopener from turbo popup (returns null per spec)
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>
2026-04-11 16:29:16 +02:00
Miguel Ángel 4992555c27 fix(renderer): force reflow on sub-composition iframes to prevent position 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>
2026-04-11 16:16:02 +02:00
Miguel Ángel d4774bedf2 fix(renderer): improve warmup for sub-composition fonts and GSAP settle
- 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>
2026-04-11 16:08:41 +02:00
Miguel Ángel 04b4eee462 feat(renderer): add turbo render mode — multi-tab parallel capture
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>
2026-04-11 14:42:23 +02:00
Miguel Ángel 6d4ad2ecce fix(renderer): fix video frame drops, audio, animation jitter, and background 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>
2026-04-11 12:58:32 +02:00
Miguel Ángel 7d36ab38e3 fix(renderer): add yields between frames to prevent main thread lockup
- 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>
2026-04-11 04:01:26 +02:00
Miguel Ángel 09363f1898 fix(renderer): bridge __player→__hf in SnapDOM source, fix studio worker 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>
2026-04-10 05:43:56 +02:00
Miguel Ángel f9489afb98 feat(renderer): add drawElementImage + tab-capture frame sources, regression 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>
2026-04-10 05:24:45 +02:00
Miguel Ángel f10cd056f7 fix(renderer): fix viewport capture, empty audio, cancel propagation, 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>
2026-04-10 05:24:45 +02:00
Miguel Ángel 76727306c9 fix(renderer): fix iframe polling, add workerUrl config, add QA test 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>
2026-04-10 05:24:45 +02:00
Miguel Ángel 23b5d834a1 fix(renderer): address code review issues
- 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>
2026-04-10 05:24:45 +02:00
Miguel Ángel 18d6a3e9e2 feat(renderer): add progress tracking and ETA estimation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel Ángel 725d9e71b1 feat(renderer): integrate video frame injector into SnapDOM frame source
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:45 +02:00
Miguel Ángel 826179e44f test(renderer): add end-to-end integration test with basic composition
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel Ángel e79f6b02b0 feat(renderer): add main orchestrator and public API (render, createRenderer)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel Ángel 5019fc09b4 feat(renderer): add parallel iframe pool for frame capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel Ángel fc823c572f feat(renderer): add video frame injector for <video> element capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:44 +02:00
Miguel Ángel 05ee085c36 feat(renderer): add OfflineAudioContext-based audio mixer
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>
2026-04-10 05:24:44 +02:00
Miguel Ángel 4b1e46294e feat(renderer): add WebCodecs encoding worker and main-thread wrapper
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>
2026-04-10 05:24:44 +02:00
Miguel Ángel 3112bc8035 feat(renderer): add SnapDOM frame source implementation
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>
2026-04-10 05:24:43 +02:00
Miguel Ángel 408d69b1a0 feat(renderer): add frame timing and distribution utilities
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:24:43 +02:00
Miguel Ángel a00930d952 feat(renderer): scaffold package with types and compat detection
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>
2026-04-10 05:24:43 +02:00