{"record":{"id":"cce4eee4c559e918","repo":"remotion-dev/remotion","slug":"rendermediaonweb-was-cancelled","errorCode":null,"errorMessage":"renderMediaOnWeb() was cancelled","messagePattern":"renderMediaOnWeb\\(\\) was cancelled","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/web-renderer/src/render-media-on-web.tsx","lineNumber":513,"sourceCode":"\t\t\t\t: null,\n\t\t\tbitrate: resolvedAudioBitrate,\n\t\t});\n\n\t\tif (audioSampleSource) {\n\t\t\toutputWithCleanup.output.addAudioTrack(\n\t\t\t\taudioSampleSource.audioSampleSource,\n\t\t\t\t{\n\t\t\t\t\t// ~1 packet per 10ms, + 33% buffer\n\t\t\t\t\t// https://mediabunny.dev/api/BaseTrackMetadata#maximumpacketcount\n\t\t\t\t\tmaximumPacketCount: Math.ceil(durationInSeconds * 100 * 1.33),\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tawait outputWithCleanup.output.start();\n\n\t\tif (signal?.aborted) {\n\t\t\tthrow new Error('renderMediaOnWeb() was cancelled');\n\t\t}\n\n\t\tconst audioMixer = createAudioMixer({fps: resolved.fps, sampleRate});\n\t\tconst encodeReadyAudio = async () => {\n\t\t\tif (!audioSampleSource) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet audio: AudioData | null;\n\t\t\twhile ((audio = audioMixer.getReadyAudio())) {\n\t\t\t\ttry {\n\t\t\t\t\tawait addAudioSample(audio, audioSampleSource.audioSampleSource);\n\t\t\t\t} finally {\n\t\t\t\t\taudio.close();\n\t\t\t\t}\n\n\t\t\t\tawait waitForPageResponsiveness();\n\t\t\t}","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/remotion-dev/remotion/blob/46a3a6bf13ca367376ff18bbb06dae2e87e0d068/packages/web-renderer/src/render-media-on-web.tsx#L495-L531","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check why the AbortController was aborted before treating this as a bug — the error confirms a deliberate cancel, not a codec failure.","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.","If you expect cancellations, catch this error specifically and treat it as a normal, expected outcome rather than logging it as a failure.","If you want a fresh attempt after an abort, create a new AbortController and call renderMediaOnWeb() again; the cancelled render cannot be resumed.","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."],"exampleFix":"// before\nuseEffect(() => {\n  renderMediaOnWeb({composition, signal: controller.signal});\n  return () => controller.abort(); // aborts the in-flight render on unmount/StrictMode\n}, []);\n\n// after\nuseEffect(() => {\n  const controller = new AbortController();\n  renderMediaOnWeb({composition, signal: controller.signal})\n    .catch((err) => {\n      if (err?.message === 'renderMediaOnWeb() was cancelled') return;\n      throw err;\n    });\n  return () => controller.abort();\n}, []);","handlingStrategy":"try-catch","validationCode":"// before calling renderMediaOnWeb, ensure the controller is not already aborted\nif (controller.signal.aborted) {\n  throw new Error('Skipping render: signal already aborted');\n}\nrenderMediaOnWeb({composition, signal: controller.signal});","typeGuard":"const isAbortError = (err: unknown): boolean =>\n  err instanceof Error && err.message === 'renderMediaOnWeb() was cancelled';","tryCatchPattern":"try {\n  await renderMediaOnWeb({composition, signal: controller.signal});\n} catch (err) {\n  if (isAbortError(err)) {\n    // expected cancel: clean up and return\n    return null;\n  }\n  throw err;\n}","preventionTips":["Create the AbortController inside the same effect/function that starts the render so its lifecycle matches.","Avoid aborting in React StrictMode double-invoked effect cleanups when the render should survive remounts.","Treat this specific message as an expected outcome and filter it from error reporting/telemetry.","Never reuse an aborted AbortSignal for a new render; create a fresh controller per attempt.","Add a cancellation test: abort mid-render and assert this error surfaces and no partial output is consumed."],"tags":["abort-signal","cancellation","webcodecs","browser"],"backgroundTag":"operation-cancelled","analyzedSha":"46a3a6bf13ca367376ff18bbb06dae2e87e0d068","analyzedAt":"2026-09-18T00:12:20.256Z","contentChangedAt":"2026-09-18T00:12:20.256Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}