ffmpeg.wasm icon indicating copy to clipboard operation
ffmpeg.wasm copied to clipboard

[Feature] Cancel a running job

Open avi12 opened this issue 4 years ago • 7 comments

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

avi12 avatar Apr 29 '21 13:04 avi12

might want to sneak a process.exit(0) somewhere

goatandsheep avatar May 01 '21 04:05 goatandsheep

A Chrome extension environment is exactly like a browser environment, plus the chrome namespace, i.e. you don't have access to process

avi12 avatar May 01 '21 06:05 avi12

ffmpeg.exit(); may help you.

API: https://github.com/ffmpegwasm/ffmpeg.wasm/blob/master/docs/api.md#ffmpegexit

selma-li avatar Jul 20 '21 08:07 selma-li

Interesting, and to reload FFmpeg I just call ffmpeg.load()?

avi12 avatar Jul 20 '21 15:07 avi12

Interesting, and to reload FFmpeg I just call ffmpeg.load()?

createFFmpeg()and ffmpeg.load()

selma-li avatar Jul 22 '21 09:07 selma-li

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?

avi12 avatar Aug 10 '21 21:08 avi12

The someCode() part will not execute if elCancel'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()
});

arnold-graf avatar Jul 18 '24 13:07 arnold-graf