Skip to content

@remotion/media: Resume browser exports after offline fetch failures - #12021

Draft
alec-watts wants to merge 7 commits into
remotion-dev:mainfrom
alec-watts:fix/offline-media-fetch-recovery
Draft

alec-watts wants to merge 7 commits into
remotion-dev:mainfrom
alec-watts:fix/offline-media-fetch-recovery

Conversation

@alec-watts

@alec-watts alec-watts commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Browser exports can fail their media and readiness deadlines during a network outage, preventing the same export from continuing after reconnection.

This revision addresses the maintainer's render-scope wiring, disposal, and package-ownership requests:

  • @remotion/media owns the UrlSource.fetchFn recovery helper. It waits for reconnection only after a fetch rejects with TypeError while the browser explicitly reports offline. HTTP responses and online failures continue through Mediabunny's normal policy.
  • Each input factory owns an abort controller. Disposing the shared input resource or extraction sink aborts its pending fetch/reconnect wait before disposing the Mediabunny input; cleanup does not depend on Mediabunny aborting its request signal.
  • The existing render-local media cache tracks its extraction handles. Offline waits suspend those handles using their actual DelayRenderContextType scope, and unmount, completion, cancellation, and cache disposal release the tracking.
  • Core owns only generic, nested handle-timeout suspension and accumulated suspension time. It has no connectivity policy, offline window globals, or media label-prefix matching. The web renderer excludes that scope's accumulated suspension time from its readiness deadline. Unrelated render scopes and authored handles retain their own timeouts.
  • Current main is merged, resolving the PR's conflict.

Verification

Actual renderMediaOnWeb exports with authored <Video> and <Audio>, native H.264/AAC encoding, public Mediabunny 1.61.3, and hidden Electron:

  • Disable networking through Chromium's network emulation after loading the harness. The browser reports offline, and both media requests fail and wait for reconnection.
  • Set the render timeout to 2.5 seconds (500 ms extraction deadline and 5.5-second outer readiness deadline). After 6.5 seconds offline, the export remains pending with two reconnect listeners. Restoring connectivity completes that same export.
  • FFmpeg independently decodes all 30 requested video frames. Full decoded RGBA bytes and decoded audio PCM are identical to the uninterrupted online control. This checks preserved output, not a new claim about container/AAC padding durations.
  • Cancelling an offline export rejects promptly and leaves zero reconnect listeners. Disposing an extraction input during an offline wait changes its pending-wait count and listener count from one to zero without reconnecting.
  • Disable only generic timeout suspension: the same offline export fails at the 500 ms extraction deadline and cannot resume. Restore it: the same export succeeds again.
  • Separate runtime probes verify render-scope isolation, nested suspension, clear-before-resume cleanup, and resumption of the remaining deadline.
  • Existing tests: 5 core ready-manager tests, 18 media input/bank/iterator browser tests, and 12 web-renderer video/audio/cancellation/range tests pass; two existing browser conditions skip tests.
  • Focused TypeScript check covering core, video/audio producers, and readiness; changed-file formatting; and git diff --check pass. Temporary source aliases and production bundles were used for local tests. No test configuration or dependency changes ship.

Limits

navigator.onLine is a connectivity hint. Outages where it stays true retain the existing retry policy. Recovery covers failures before a response is returned, not failures reading an already-open response body. An offline wait suspends media handles within its own render cache; preview inputs do not extend an export's timeout. Offline exports can wait until reconnection or cancellation. Server-render timeout policy is unchanged. Full upstream CI is a separate check.

File Why it changes
packages/core/src/delay-render.ts Add generic nested suspension to the existing timeout owner, with cleanup and scope-local elapsed accounting.
packages/core/src/internals.ts Expose the two generic timeout operations through the existing internal API.
packages/media/src/offline-media-fetch.ts Own recovery policy and abortable reconnect waits in the media package.
packages/media/src/get-shared-input.ts Attach recovery and cancellation to the existing shared-input lifetime.
packages/media/src/video-extraction/get-frames-since-keyframe.ts Attach the same helper and cancellation to extraction inputs.
packages/media/src/get-sink.ts Forward offline wait transitions from existing input factories.
packages/media/src/caches.ts Attribute offline waits and extraction handles to the existing render-local cache.
packages/media/src/video/video-for-rendering.tsx Register and release the video extraction handle on its actual render scope.
packages/media/src/audio/audio-for-rendering.tsx Register and release the audio extraction handle on its actual render scope.
packages/web-renderer/src/wait-for-ready.ts Account for generic scope-local suspension in the readiness deadline.

Diff cleanup removes the old core recovery helper, offline window state, and label matching. No parallel input, decoder, request queue, or global fetch override is introduced.

Shared CI test repair

Includes the one-line caption-inspector track selector fix from #12179. Current main fails this test before it can exercise caption persistence. Both caption-inspector E2E cases pass in the actual Studio with this repair; restoring the old selector reproduces the failure.

File Reason
packages/example/e2e/captions-inspector.test.mts Repair the existing main-branch selector so shared CI can exercise caption persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
bugs Ready Ready Preview Oct 9, 2026 9:11pm UTC
remotion Ready Ready Preview Oct 9, 2026 9:11pm UTC

Request Review

Co-authored-by: Cursor <cursoragent@cursor.com>
@alec-watts

Copy link
Copy Markdown
Contributor Author

Follow-up from downstream review in https://github.com/kino-ai/kino/pull/3936:

  • Added a cleared-handle guard at the start of the delayRender timeout poll. A queued callback must not reschedule or cancel a render after continueRender has removed its timeout entry. An external probe executing the actual callback verified both positive and exhausted budgets are no-ops for cleared handles, while an active handle still reschedules.
  • Keep retry budgets owned by Mediabunny per request. A previous downstream implementation tracked attempt counts in a callback shared by UrlSource; interleaved concurrent reads could reset that shared budget. This upstream PR already avoids that design: recovery lives within each fetchFn invocation and leaves Mediabunny’s default online retry policy unchanged.
  • Prefer behavioral fetch recovery/abort coverage over mocked UI transport assertions. The existing external helper probe covers reconnecting the original request, abort cleanup, and online-error propagation; no Kino-only tests or UI changes were copied here.

The downstream PR is now merge-ready with unit CI, Vercel, and Bugbot passing. That is not a substitute for an end-to-end browser disconnect/reconnect render in this upstream checkout, which remains unverified.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alec-watts

Copy link
Copy Markdown
Contributor Author

Fixed and pushed the CI failures caused by this change:

  • Avoid reading global fetch at module initialization when it is absent (Node 16 / SSR). Preserve Bun fetch properties when fetch is available.
  • Resolve declaration-order lint by grouping temporary event listeners under an AbortController, with cleanup on reconnect or request abort.
  • Match the internal blank-line rules in delay-render and wait-for-ready.

Local verification: core/media/web-renderer lint and formatting passed; core built and all 717 tests passed; the full SSR suite passed; built CJS and ESM imports passed on actual Node 16.20.2 without global fetch; recovery/reconnect/abort and cleared-timeout probes passed. Full local monorepo integration was blocked by missing language tooling (python, PHP, Go and Ruby bundle setup), but monorepo CI now passes.

The new CI run has passed Linting + Formatting, SSR tests, and Monorepo tests. A remaining Windows Node 16 job failed on the unrelated bundler test "emits a zero-delay Rspack React Refresh runtime" hitting its 5000 ms timeout; no fetch import error remains in that job. Other integration jobs are still running.

@JonnyBurger JonnyBurger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The UrlSource.fetchFn integration is a sensible direction, but I am requesting changes for the timeout scope wiring, disposal cleanup, and package ownership described below.

I reproduced both behavioral issues with targeted runtime probes against this head, using the pinned Mediabunny 1.56.1. A complete browser disconnect/reconnect export has not been verified.

Comment thread packages/core/src/delay-render.ts Outdated
Comment thread packages/core/src/offline-media-fetch.ts Outdated
Comment thread packages/core/src/internals.ts

This branch was successfully deployed

2 active deployments
Preview – remotion — 51199a56 Deployed Oct 9, 2026 by vercel[bot]
Preview – bugs — 51199a56 Deployed Oct 9, 2026 by vercel[bot]
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.

3 participants