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(); } }