remotion-dev/remotion · error

renderMediaOnWeb() was cancelled

Error message

renderMediaOnWeb() was cancelled

What it means

renderMediaOnWeb() checks the AbortSignal passed via the `signal` option right after starting the mediabunny output. If the caller aborted the signal before or during output.start(), the renderer throws this error instead of continuing to encode frames, so an aborted render stops early and never produces a partially-usable output file. It is Remotion's cancellation contract for browser-side (WebCodecs) renders.

Solutions

  1. Check why the AbortController was aborted before treating this as a bug — the error confirms a deliberate cancel, not a codec failure.
  2. If the cancel was unintentional (e.g. React StrictMode double-effect), don't abort in the first effect's cleanup while the render is still needed, or gate the abort on the effect actually being disposed.
  3. If you expect cancellations, catch this error specifically and treat it as a normal, expected outcome rather than logging it as a failure.
  4. If you want a fresh attempt after an abort, create a new AbortController and call renderMediaOnWeb() again; the cancelled render cannot be resumed.
  5. Move the abort later: if you only intend to cancel slow renders, start the abort timer after the first onProgress callback instead of at t=0.

Example fix

// before
useEffect(() => {
  renderMediaOnWeb({composition, signal: controller.signal});
  return () => controller.abort(); // aborts the in-flight render on unmount/StrictMode
}, []);

// after
useEffect(() => {
  const controller = new AbortController();
  renderMediaOnWeb({composition, signal: controller.signal})
    .catch((err) => {
      if (err?.message === 'renderMediaOnWeb() was cancelled') return;
      throw err;
    });
  return () => controller.abort();
}, []);
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling renderMediaOnWeb, ensure the controller is not already aborted
if (controller.signal.aborted) {
  throw new Error('Skipping render: signal already aborted');
}
renderMediaOnWeb({composition, signal: controller.signal});

Type guard

const isAbortError = (err: unknown): boolean =>
  err instanceof Error && err.message === 'renderMediaOnWeb() was cancelled';

Try / catch

try {
  await renderMediaOnWeb({composition, signal: controller.signal});
} catch (err) {
  if (isAbortError(err)) {
    // expected cancel: clean up and return
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling renderMediaOnWeb({..., signal}) and either (a) aborting the AbortController before the call's internal output.start() promise resolves, or (b) calling controller.abort() concurrently very early in the render, racing ahead of the post-start checkpoint at render-media-on-web.tsx:513.

Common situations: UI code that lets the user cancel a browser render and aborts immediately after starting; React effects whose cleanup aborts the controller on unmount/StrictMode double-mount, cancelling an in-flight render; a timeout firing during the output startup phase; a page navigation or component remount tearing down the controller while renderMediaOnWeb is still bootstrapping.

Related errors


AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18). Data as JSON: /api/errors/cce4eee4c559e918. Report an issue: GitHub.

Appendix: source

Thrown at packages/web-renderer/src/render-media-on-web.tsx:513

				: null,
			bitrate: resolvedAudioBitrate,
		});

		if (audioSampleSource) {
			outputWithCleanup.output.addAudioTrack(
				audioSampleSource.audioSampleSource,
				{
					// ~1 packet per 10ms, + 33% buffer
					// https://mediabunny.dev/api/BaseTrackMetadata#maximumpacketcount
					maximumPacketCount: Math.ceil(durationInSeconds * 100 * 1.33),
				},
			);
		}

		await outputWithCleanup.output.start();

		if (signal?.aborted) {
			throw new Error('renderMediaOnWeb() was cancelled');
		}

		const audioMixer = createAudioMixer({fps: resolved.fps, sampleRate});
		const encodeReadyAudio = async () => {
			if (!audioSampleSource) {
				return;
			}

			let audio: AudioData | null;
			while ((audio = audioMixer.getReadyAudio())) {
				try {
					await addAudioSample(audio, audioSampleSource.audioSampleSource);
				} finally {
					audio.close();
				}

				await waitForPageResponsiveness();
			}

View on GitHub (pinned to 46a3a6bf13)