diff --git a/packages/ffmpeg/src/classes.ts b/packages/ffmpeg/src/classes.ts index 48d9e2c4a4..a476c8dabe 100644 --- a/packages/ffmpeg/src/classes.ts +++ b/packages/ffmpeg/src/classes.ts @@ -112,6 +112,20 @@ export class FFmpeg { "abort", () => { reject(new DOMException(`Message # ${id} was aborted`, "AbortError")); + // If we are aborting an exec or ffprobe, send a CANCEL message so + // the worker's ffmpeg timeout is set to 1 ms, causing the running + // command to stop as quickly as possible. For other message types + // the cancel is a no-op on the worker side. + if ( + (type === FFMessageType.EXEC || + type === FFMessageType.FFPROBE) && + this.#worker + ) { + this.#worker.postMessage({ + id: getMessageID(), + type: FFMessageType.CANCEL, + }); + } }, { once: true } ); diff --git a/packages/ffmpeg/src/const.ts b/packages/ffmpeg/src/const.ts index 5ced388c06..0f7129cb27 100644 --- a/packages/ffmpeg/src/const.ts +++ b/packages/ffmpeg/src/const.ts @@ -17,6 +17,7 @@ export enum FFMessageType { DELETE_DIR = "DELETE_DIR", ERROR = "ERROR", + CANCEL = "CANCEL", DOWNLOAD = "DOWNLOAD", PROGRESS = "PROGRESS", LOG = "LOG", diff --git a/packages/ffmpeg/src/worker.ts b/packages/ffmpeg/src/worker.ts index cf8b6c5aac..33ffbd293e 100644 --- a/packages/ffmpeg/src/worker.ts +++ b/packages/ffmpeg/src/worker.ts @@ -108,6 +108,19 @@ const ffprobe = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => { return ret; }; +/** + * Interrupt any running exec/ffprobe by setting the ffmpeg timeout to 1 ms. + * This causes the WASM-side watchdog to fire almost immediately, stopping + * the current command and returning exit-code 1 (timeout). The exec() + * call on the main thread has already been rejected with an AbortError by + * the time this message arrives, so the exit-code reply is simply discarded. + */ +const cancel = (): void => { + if (ffmpeg) { + ffmpeg.setTimeout(1); + } +}; + const writeFile = ({ path, data }: FFMessageWriteFileData): OK => { ffmpeg.FS.writeFile(path, data); return true; @@ -181,6 +194,10 @@ self.onmessage = async ({ case FFMessageType.FFPROBE: data = ffprobe(_data as FFMessageExecData); break; + case FFMessageType.CANCEL: + cancel(); + // CANCEL is fire-and-forget: no reply needed. + return; case FFMessageType.WRITE_FILE: data = writeFile(_data as FFMessageWriteFileData); break;