diff --git a/packages/engine/src/services/audioMixer.ts b/packages/engine/src/services/audioMixer.ts index 369562e328..8a2dff3135 100644 --- a/packages/engine/src/services/audioMixer.ts +++ b/packages/engine/src/services/audioMixer.ts @@ -765,6 +765,12 @@ export async function processCompositionAudio( // be able to abort the in-flight ffmpeg runs before the finally-block removes // workDir out from under them. Chained off the caller's signal so external // cancellation still behaves as before. + // + // Every child that can outlive a sibling's failure has to be given THIS + // signal, not the caller's: the trim, the video extract and the download all + // took `signal`, so `internalController.abort()` cancelled nothing and the + // `rmSync(workDir)` on the next line ran while their ffmpeg children were + // still writing into it. const internalController = new AbortController(); const effectiveSignal = internalController.signal; if (signal) { @@ -795,7 +801,7 @@ export async function processCompositionAudio( if (isHttpUrl(srcPath)) { try { - srcPath = await downloadToTemp(srcPath, workDir); + srcPath = await downloadToTemp(srcPath, workDir, undefined, effectiveSignal); } catch (err: unknown) { failures.push( downloadFailure(err instanceof Error ? err.message : String(err), element.id), @@ -842,7 +848,7 @@ export async function processCompositionAudio( startTime: element.mediaStart, duration: element.end - element.start, }, - signal, + effectiveSignal, config, ); if (!extractResult.success) { @@ -868,7 +874,7 @@ export async function processCompositionAudio( trimmedPath, element.mediaStart, element.end - element.start, - signal, + effectiveSignal, config, ); if (!prepResult.success) {