Skip to content

Commit efaae60

Browse files
authored
feat(ffmpeg): abort signal (#573)
* feat(ffmpeg): abort signal * with test
1 parent cf9cf11 commit efaae60

File tree

2 files changed

+134
-45
lines changed

2 files changed

+134
-45
lines changed

packages/ffmpeg/src/classes.ts

Lines changed: 120 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import {
1717
import { getMessageID } from "./utils.js";
1818
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors.js";
1919

20+
type FFMessageOptions = {
21+
signal?: AbortSignal;
22+
};
23+
2024
/**
2125
* Provides APIs to interact with ffmpeg web worker.
2226
*
@@ -85,7 +89,8 @@ export class FFmpeg {
8589
*/
8690
#send = (
8791
{ type, data }: Message,
88-
trans: Transferable[] = []
92+
trans: Transferable[] = [],
93+
signal?: AbortSignal
8994
): Promise<CallbackData> => {
9095
if (!this.#worker) {
9196
return Promise.reject(ERROR_NOT_LOADED);
@@ -96,6 +101,14 @@ export class FFmpeg {
96101
this.#worker && this.#worker.postMessage({ id, type, data }, trans);
97102
this.#resolves[id] = resolve;
98103
this.#rejects[id] = reject;
104+
105+
signal?.addEventListener(
106+
"abort",
107+
() => {
108+
reject(new DOMException(`Message # ${id} was aborted`, "AbortError"));
109+
},
110+
{ once: true }
111+
);
99112
});
100113
};
101114

@@ -148,9 +161,13 @@ export class FFmpeg {
148161
callback: LogEventCallback | ProgressEventCallback
149162
) {
150163
if (event === "log") {
151-
this.#logEventCallbacks = this.#logEventCallbacks.filter((f) => f !== callback);
164+
this.#logEventCallbacks = this.#logEventCallbacks.filter(
165+
(f) => f !== callback
166+
);
152167
} else if (event === "progress") {
153-
this.#progressEventCallbacks = this.#progressEventCallbacks.filter((f) => f !== callback);
168+
this.#progressEventCallbacks = this.#progressEventCallbacks.filter(
169+
(f) => f !== callback
170+
);
154171
}
155172
}
156173

@@ -161,17 +178,24 @@ export class FFmpeg {
161178
* @category FFmpeg
162179
* @returns `true` if ffmpeg core is loaded for the first time.
163180
*/
164-
public load = (config: FFMessageLoadConfig = {}): Promise<IsFirst> => {
181+
public load = (
182+
config: FFMessageLoadConfig = {},
183+
{ signal }: FFMessageOptions = {}
184+
): Promise<IsFirst> => {
165185
if (!this.#worker) {
166186
this.#worker = new Worker(new URL("./worker.js", import.meta.url), {
167187
type: "module",
168188
});
169189
this.#registerHandlers();
170190
}
171-
return this.#send({
172-
type: FFMessageType.LOAD,
173-
data: config,
174-
}) as Promise<IsFirst>;
191+
return this.#send(
192+
{
193+
type: FFMessageType.LOAD,
194+
data: config,
195+
},
196+
undefined,
197+
signal
198+
) as Promise<IsFirst>;
175199
};
176200

177201
/**
@@ -202,12 +226,17 @@ export class FFmpeg {
202226
*
203227
* @defaultValue -1
204228
*/
205-
timeout = -1
229+
timeout = -1,
230+
{ signal }: FFMessageOptions = {}
206231
): Promise<number> =>
207-
this.#send({
208-
type: FFMessageType.EXEC,
209-
data: { args, timeout },
210-
}) as Promise<number>;
232+
this.#send(
233+
{
234+
type: FFMessageType.EXEC,
235+
data: { args, timeout },
236+
},
237+
undefined,
238+
signal
239+
) as Promise<number>;
211240

212241
/**
213242
* Terminate all ongoing API calls and terminate web worker.
@@ -244,7 +273,11 @@ export class FFmpeg {
244273
*
245274
* @category File System
246275
*/
247-
public writeFile = (path: string, data: FileData): Promise<OK> => {
276+
public writeFile = (
277+
path: string,
278+
data: FileData,
279+
{ signal }: FFMessageOptions = {}
280+
): Promise<OK> => {
248281
const trans: Transferable[] = [];
249282
if (data instanceof Uint8Array) {
250283
trans.push(data.buffer);
@@ -254,7 +287,8 @@ export class FFmpeg {
254287
type: FFMessageType.WRITE_FILE,
255288
data: { path, data },
256289
},
257-
trans
290+
trans,
291+
signal
258292
) as Promise<OK>;
259293
};
260294

@@ -279,65 +313,106 @@ export class FFmpeg {
279313
*
280314
* @defaultValue binary
281315
*/
282-
encoding = "binary"
316+
encoding = "binary",
317+
{ signal }: FFMessageOptions = {}
283318
): Promise<FileData> =>
284-
this.#send({
285-
type: FFMessageType.READ_FILE,
286-
data: { path, encoding },
287-
}) as Promise<FileData>;
319+
this.#send(
320+
{
321+
type: FFMessageType.READ_FILE,
322+
data: { path, encoding },
323+
},
324+
undefined,
325+
signal
326+
) as Promise<FileData>;
288327

289328
/**
290329
* Delete a file.
291330
*
292331
* @category File System
293332
*/
294-
public deleteFile = (path: string): Promise<OK> =>
295-
this.#send({
296-
type: FFMessageType.DELETE_FILE,
297-
data: { path },
298-
}) as Promise<OK>;
333+
public deleteFile = (
334+
path: string,
335+
{ signal }: FFMessageOptions = {}
336+
): Promise<OK> =>
337+
this.#send(
338+
{
339+
type: FFMessageType.DELETE_FILE,
340+
data: { path },
341+
},
342+
undefined,
343+
signal
344+
) as Promise<OK>;
299345

300346
/**
301347
* Rename a file or directory.
302348
*
303349
* @category File System
304350
*/
305-
public rename = (oldPath: string, newPath: string): Promise<OK> =>
306-
this.#send({
307-
type: FFMessageType.RENAME,
308-
data: { oldPath, newPath },
309-
}) as Promise<OK>;
351+
public rename = (
352+
oldPath: string,
353+
newPath: string,
354+
{ signal }: FFMessageOptions = {}
355+
): Promise<OK> =>
356+
this.#send(
357+
{
358+
type: FFMessageType.RENAME,
359+
data: { oldPath, newPath },
360+
},
361+
undefined,
362+
signal
363+
) as Promise<OK>;
310364

311365
/**
312366
* Create a directory.
313367
*
314368
* @category File System
315369
*/
316-
public createDir = (path: string): Promise<OK> =>
317-
this.#send({
318-
type: FFMessageType.CREATE_DIR,
319-
data: { path },
320-
}) as Promise<OK>;
370+
public createDir = (
371+
path: string,
372+
{ signal }: FFMessageOptions = {}
373+
): Promise<OK> =>
374+
this.#send(
375+
{
376+
type: FFMessageType.CREATE_DIR,
377+
data: { path },
378+
},
379+
undefined,
380+
signal
381+
) as Promise<OK>;
321382

322383
/**
323384
* List directory contents.
324385
*
325386
* @category File System
326387
*/
327-
public listDir = (path: string): Promise<FSNode[]> =>
328-
this.#send({
329-
type: FFMessageType.LIST_DIR,
330-
data: { path },
331-
}) as Promise<FSNode[]>;
388+
public listDir = (
389+
path: string,
390+
{ signal }: FFMessageOptions = {}
391+
): Promise<FSNode[]> =>
392+
this.#send(
393+
{
394+
type: FFMessageType.LIST_DIR,
395+
data: { path },
396+
},
397+
undefined,
398+
signal
399+
) as Promise<FSNode[]>;
332400

333401
/**
334402
* Delete an empty directory.
335403
*
336404
* @category File System
337405
*/
338-
public deleteDir = (path: string): Promise<OK> =>
339-
this.#send({
340-
type: FFMessageType.DELETE_DIR,
341-
data: { path },
342-
}) as Promise<OK>;
406+
public deleteDir = (
407+
path: string,
408+
{ signal }: FFMessageOptions = {}
409+
): Promise<OK> =>
410+
this.#send(
411+
{
412+
type: FFMessageType.DELETE_DIR,
413+
data: { path },
414+
},
415+
undefined,
416+
signal
417+
) as Promise<OK>;
343418
}

tests/ffmpeg.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,18 @@ describe(genName("FFmpeg.exec()"), function () {
135135
const ret = await ffmpeg.exec(["-i", "video.mp4", "video.avi"], 1);
136136
expect(ret).to.equal(1);
137137
});
138+
139+
it("should abort", () => {
140+
const controller = new AbortController();
141+
const { signal } = controller;
142+
143+
const promise = ffmpeg.exec(["-i", "video.mp4", "video.avi"], undefined, {
144+
signal,
145+
});
146+
controller.abort();
147+
148+
return promise.catch((err) => {
149+
expect(err.name).to.equal("AbortError");
150+
});
151+
});
138152
});

0 commit comments

Comments
 (0)