diff --git a/dev/convert.html b/dev/convert.html
index 697c30c..b7efa6b 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -18,6 +18,12 @@
const target = new Metamuxer.BufferTarget();
const outputFormat = new Metamuxer.Mp4OutputFormat()
+ const abortController = new AbortController();
+ const button = document.createElement('button');
+ button.textContent = 'Cancel';
+ button.onclick = () => abortController.abort();
+ document.body.append(button);
+
console.time()
const res = await Metamuxer.convert({
input: new Metamuxer.Input({
@@ -61,7 +67,8 @@
},
onProgress: (event) => {
progress.value = event.completion;
- }
+ },
+ abortSignal: abortController.signal
});
console.timeEnd()
console.log(res)
diff --git a/src/conversion.ts b/src/conversion.ts
index c7c7057..af31880 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -65,6 +65,7 @@ export type ConversionOptions = {
};
onProgress?: (event: { completion: number }) => unknown;
+ abortSignal?: AbortSignal;
};
/** @public */
@@ -227,6 +228,9 @@ class Conversion {
if (options.onProgress !== undefined && typeof options.onProgress !== 'function') {
throw new TypeError('options.onProgress, when provided, must be a function.');
}
+ if (options.abortSignal !== undefined && !(options.abortSignal instanceof AbortSignal)) {
+ throw new TypeError('options.abortSignal, when provided, must be an AbortSignal.');
+ }
this.input = options.input;
this.output = options.output;
@@ -242,6 +246,12 @@ class Conversion {
utilizedTracks: [],
discardedTracks: [],
};
+
+ options.abortSignal?.addEventListener('abort', () => {
+ if (!this.output.isFinalizing) {
+ void this.output.cancel();
+ }
+ });
}
async execute() {
@@ -370,6 +380,10 @@ class Conversion {
await this.synchronizer.wait(sample.timestamp);
}
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(sample, meta);
this.reportProgress(track.id, sample.timestamp + sample.duration);
}
@@ -452,6 +466,10 @@ class Conversion {
context.drawImage(canvas, 0, 0, canvas.width, canvas.height, dx, dy, newWidth, newHeight);
}
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(Math.max(timestamp - this.startTimestamp, 0), duration);
}
@@ -476,6 +494,10 @@ class Conversion {
timestamp: Math.max(timestamp - this.startTimestamp, 0),
});
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(clone);
clone.close();
}
@@ -548,6 +570,10 @@ class Conversion {
await this.synchronizer.wait(sample.timestamp);
}
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(sample, meta);
this.reportProgress(track.id, sample.timestamp + sample.duration);
}
@@ -633,6 +659,10 @@ class Conversion {
await this.synchronizer.wait(timestamp);
}
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(data);
data.close();
}
@@ -716,6 +746,11 @@ class Conversion {
if (endTimestamp >= currentContextEndTime) {
// Render the audio
const renderedBuffer = await currentContext.startRendering();
+
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(renderedBuffer);
currentContextStartFrame += currentContext.length;
@@ -739,6 +774,11 @@ class Conversion {
if (currentContext) {
const renderedBuffer = await currentContext.startRendering();
+
+ if (this.options.abortSignal?.aborted) {
+ return;
+ }
+
await source.digest(renderedBuffer);
}
diff --git a/src/output.ts b/src/output.ts
index 11162a3..302deca 100644
--- a/src/output.ts
+++ b/src/output.ts
@@ -86,8 +86,26 @@ export class Output<
/** @internal */
_finalizing = false;
/** @internal */
+ _finalized = false;
+ /** @internal */
_mutex = new AsyncMutex();
+ get isStarted() {
+ return this._started;
+ }
+
+ get isCanceled() {
+ return this._canceled;
+ }
+
+ get isFinalizing() {
+ return this._finalizing;
+ }
+
+ get isFinalized() {
+ return this._finalized;
+ }
+
constructor(options: OutputOptions) {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
@@ -334,6 +352,8 @@ export class Output<
await this._writer.flush();
await this._writer.finalize();
+ this._finalized = true;
+
release();
}
}