[Feature] Cancel a running job
Is your feature request related to a problem? Please describe. I'm making a browser extension that uses this module, and the user might want to cancel an occurring FFmpeg job at any time, which is not currently possible.
Describe the solution you'd like
ffmpeg.abort();
Describe alternatives you've considered My current somewhat-working workaround is:
class CancellablePromise {
private readonly symbolAbort = Symbol("cancelled");
private readonly promiseAbort: Promise<any>;
private resolve!: Function; // Works due to promise init
constructor() {
this.promiseAbort = new Promise(resolve => (this.resolve = resolve));
}
public async wrap<T>(promise: PromiseLike<T>): Promise<T> {
const result = await Promise.race([promise, this.promiseAbort]);
if (result === this.symbolAbort) {
throw new Error("Aborting FFmpeg");
}
return result;
}
public abort() {
this.resolve(this.symbolAbort);
}
}
Then, in the code:
import { createFFmpeg } from "@ffmpeg/ffmpeg";
const ffmpeg = createFFmpeg({ log: true });
chrome.runtime.onConnect.addListener(async port => {
if (port.name === "run-ffmpeg-job") {
if (!ffmoeg.isLoaded()) {
await ffmpeg.load();
}
const promise = new CancellablePromise();
ffmpeg.FS("writeFile", "filename.mp4");
const promiseJob = promise.wrap(ffmpeg.run("-i", "filename.mp4", /*...*/));
port.onDisconnect.addListener(() => {
promise.abort();
});
await promiseJob;
}
});
The problem with promise.abort() is that FFmpeg will still run (output will still flow to the console).
Additional context
might want to sneak a process.exit(0) somewhere
A Chrome extension environment is exactly like a browser environment, plus the chrome namespace, i.e. you don't have access to process
ffmpeg.exit(); may help you.
API: https://github.com/ffmpegwasm/ffmpeg.wasm/blob/master/docs/api.md#ffmpegexit
Interesting, and to reload FFmpeg I just call ffmpeg.load()?
After calling ffmpeg.exit(), any code that was after the ffmpeg.run() isn't being executed, even if I wrap the whole FFmpeg code block in try-catch
Think like:
let ffmpeg;
async function initFFmpeg() {
ffmpeg = createFFmpeg({ log: true });
await ffmpeg.load();
}
initFFmpeg();
const elRun = document.querySelector(".run-job");
elRun.addEventListener("click", async () => {
ffmpeg.F8(...)
await ffmpeg.run(..)
someCode();
});
const elCancel = document.querySelector(".cancel-job");
elCancel.addEventListener("click", () => {
try {
ffmpeg.exit();
} catch {}
});
The someCode() part will not execute if elCancel's callback gets executed
How do I make a code execute afterward?
The
someCode()part will not execute ifelCancel's callback gets executed
Sorry for reviving this old thread, but just in case someone else has this requirement, I thought I’d put it here.
To make this work, you have to first call ffmpeg.exit() to cancel all current operations. This will cause any pending Promises returned by calls to ffmpeg.run to throw an Error with the message ffmpeg has exited, meaning you have to wrap the ffmpeg.run calls in try/catch blocks (this is also the reason why someCode in your example isn’t executed — await ffmpeg.run(..) throws an Error when ffmpeg.exit() is called, and the execution gets stopped). Afterwards, you can just call await ffmpeg.load() again to reinitialize.
The updated code:
let ffmpeg;
async function initFFmpeg() {
ffmpeg = createFFmpeg({ log: true });
await ffmpeg.load();
}
initFFmpeg();
const elRun = document.querySelector(".run-job");
elRun.addEventListener("click", async () => {
try {
ffmpeg.F8(...)
await ffmpeg.run(..)
} catch (e) {
if (e.message === 'ffmpeg has exited') {
console.log('the operation was cancelled!')
} else {
console.log('some other error happened!', e)
}
}
someCode();
});
const elCancel = document.querySelector(".cancel-job");
elCancel.addEventListener("click", () => {
ffmpeg.exit();
await ffmpeg.load()
});