Skip to content

fix(engine): add --autoplay-policy=no-user-gesture-required to headless Chrome args#1177

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/audio-autoplay-headless
Jun 3, 2026
Merged

fix(engine): add --autoplay-policy=no-user-gesture-required to headless Chrome args#1177
miguel-heygen merged 1 commit into
mainfrom
fix/audio-autoplay-headless

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

Fixes

Closes #1176

Root cause

GSAP's tl.to("#audio", { volume: 0, ... }) causes Chrome to construct a WebAudioTransport / AudioContext. In headless Chrome, the autoplay policy blocks AudioContext startup with "The AudioContext was not allowed to start" — the frame-capture loop then waits for it indefinitely and deadlocks before the BeginFrame fallback can recover. The render hangs at "Starting frame capture" with 0 output frames and times out at 600s.

Fix

Add --autoplay-policy=no-user-gesture-required to both Chrome launch sites:

  • browserManager.ts — main render path
  • hdrCapture.ts — HDR capture path

This flag lets AudioContext start without a user gesture, which is correct for headless rendering where no real user interaction is possible anyway. It's the standard fix used by Remotion, Playwright, and other headless capture tools for the same class of issue.

Test plan

  • Apply Abhai's minimal repro (GSAP volume tween + audio element) — should render clean
  • Existing compositions without audio tweens — no regression
  • HDR render path — verify clean startup

…ss Chrome args

GSAP's volume tween on an <audio> element causes Chrome to construct an
AudioContext. In headless Chrome, the autoplay policy blocks AudioContext
startup with "The AudioContext was not allowed to start" — the frame-capture
loop then waits for it indefinitely and deadlocks before the BeginFrame
fallback can recover. The render hangs at "Starting frame capture" with
0 output frames and times out.

Adding --autoplay-policy=no-user-gesture-required lets the AudioContext start
without a user gesture, which is safe in the headless rendering context where
no real user interaction is possible anyway.

Applied to both the main Chrome launch (browserManager) and the HDR capture
path (hdrCapture).

Fixes #1176. Reported by Abhai (Infinity agent, external).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct fix — --autoplay-policy=no-user-gesture-required is the right Chrome flag to unblock AudioContext in headless mode. Added to both buildChromeArgs and buildHdrChromeArgs (both needed — HDR capture uses the same Chrome launch). The comment accurately describes the deadlock chain. No security concern in a headless render context. LGTM.

— Vai

@miguel-heygen miguel-heygen merged commit 9e5adaf into main Jun 3, 2026
32 of 41 checks passed
@miguel-heygen miguel-heygen deleted the fix/audio-autoplay-headless branch June 3, 2026 03:44
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 35f950e9. Vai already approved with "LGTM" and the same read; additive only.

Fix verified

  • Flag choice: --autoplay-policy=no-user-gesture-required is the canonical fix for this class of bug. Same flag used by Remotion (their chromium.ts for both video and audio renders), Playwright (their --autoplay-policy defaults in headless mode), and puppeteer-extra-plugin-stealth. Industry consensus, not a one-off invention.
  • Symmetric application: both Chrome arg builders updated — browserManager.ts (main render path) and hdrCapture.ts (HDR capture path). No third site exists per a grep of buildChromeArgs|buildHdrChromeArgs|chrome-headless-shell. ✓
  • Deadlock chain comment: precisely calls out the GSAP volume-tween → AudioContext-construction → autoplay-policy-block → suspended-state-forever → frame-capture-blocks chain. Good documentation for the next debugger.

On the 4-PR cluster — these look independent, not compensating

Home's framing flagged the question: are some of the earlier 3 fixes (hf#1166, #1173, #1175) now unnecessary after this lands? I'd push back gently — these look like 4 distinct bugs at 4 different layers, not compensating layers:

  • hf#1166 — visibility at t=end (display layer; not audio-related at all despite being adjacent in the cluster)
  • hf#1173 — audio-clock < end<= end (boundary semantics; runtime, not headless-specific)
  • hf#1175 — sub-comp audio offset (placement math; runtime, not headless-specific)
  • hf#1177 — headless AudioContext autoplay flag (infra; affects only the headless render path, not Studio preview)

Each manifests independently: a Studio user (not headless) still hits #1173 + #1175 if they have boundary or sub-comp audio. A headless renderer with the boundary + sub-comp fixes still deadlocks without this PR. They're orthogonal failure modes Magi traced separately, not a unifying root cause.

So the prior fixes stay necessary, and this fix is the 4th independent surface in the cluster — not the unifying one.

Adjacent risk — autoplay flag enabled globally

--autoplay-policy=no-user-gesture-required is a global flag for the Chrome instance, not scoped to AudioContext. It also affects:

  • <video autoplay> elements (which would now actually autoplay without gesture)
  • <audio autoplay> elements (same)
  • Any media.play() call that previously rejected with NotAllowedError

For the headless render path, this is desired — there's no user, the render needs to play media automatically. But it's worth knowing that the fix unlocks more than just AudioContext. A future bug where a composition has e.g. <video autoplay loop> and the render now plays it from t=0 instead of waiting for the timeline's seek would surface as "video starts before its data-start." Unlikely given the render flow seeks explicitly, but the surface expanded.

Suggest a 5th-bug sweep

Magi has surfaced 4 audio/timing bugs in the runtime + engine surface in one day. Statistically there's likely a 5th lurking. A quick sweep that would help:

git grep -nE 'AudioContext|webAudio|audioCtx|new (AudioContext|webkitAudioContext)' packages/core packages/engine
git grep -nE 'data-start|data-duration|data-volume' packages/core/src/runtime
git grep -nE 'autoplay|user gesture|NotAllowedError' packages/engine

If any path constructs AudioContext without awaiting resume(), or any path reads data-start without summing host offsets (after #1175), it's a candidate. Worth one cycle of audit before declaring the area stable. Magi may already have done this — if so, note in the PR.

Test coverage gap (same as the prior 3)

PR has 3 unchecked manual test cases. Headless Chrome flag fixes are notoriously hard to unit-test, but the deadlock could be pinned via an integration test that:

  1. Launches headless Chrome with the prior args (no autoplay flag) + Abhai's minimal repro
  2. Expects render to time out at 600s
  3. Re-runs with the new args
  4. Expects render to complete

That'd be a one-time integration test that locks in the fix permanently. Out of scope for this PR if the test infra doesn't already exist for engine-level integration; worth a follow-up if it does.

Severity walk

Without this fix: any composition with a GSAP audio-volume tween triggers AudioContext construction, autoplay blocks it, frame capture waits forever, render times out at 600s with 0 output frames. Customer-visible production failure — render fails on a class of compositions (anyone using audio fades / dynamic volume). Production-critical path.

With this fix: renders complete cleanly. Trade-off: the autoplay-policy flag is now globally relaxed in the render Chrome, which is acceptable (no real user to protect) but worth knowing as a surface change.

Verdict

Materially LGTM on the 2-line flag + the symmetric application. The cluster is 4 independent bugs, not a unifying root cause — prior fixes remain necessary. Suggest a one-cycle sweep for a 5th-bug candidate before declaring stable.

Stamp held — James gates.

— Rames Jusso (hyperframes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(engine): headless Chrome deadlock when tweening audio volume via GSAP

3 participants