From ba0491de3ad8e4a17c6455e9da10e1fbc4879016 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Sun, 10 Aug 2025 03:24:17 +0200 Subject: [PATCH] Circumvent flush() being called on sources when cancelling output, surface error before doing cleanup --- package.json | 2 +- packages/mp3-encoder/package.json | 2 +- packages/mp3-encoder/src/index.ts | 8 +++-- src/conversion.ts | 2 +- src/media-source.ts | 57 +++++++++++++++++++------------ src/output.ts | 4 +-- todo.txt | 4 +-- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 7a2f721..ddd8b0e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mediabunny", "author": "Vanilagy", - "version": "1.5.0", + "version": "1.6.0", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "type": "module", "workspaces": [ diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json index 5337f04..044d0ee 100644 --- a/packages/mp3-encoder/package.json +++ b/packages/mp3-encoder/package.json @@ -1,6 +1,6 @@ { "name": "@mediabunny/mp3-encoder", - "version": "0.0.1", + "version": "1.6.0", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs", "types": "./dist/modules/src/index.d.ts", diff --git a/packages/mp3-encoder/src/index.ts b/packages/mp3-encoder/src/index.ts index bb85cc6..40c1a3d 100644 --- a/packages/mp3-encoder/src/index.ts +++ b/packages/mp3-encoder/src/index.ts @@ -13,7 +13,7 @@ import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared import createWorker from './encode.worker'; class Mp3Encoder extends CustomAudioEncoder { - private worker!: Worker; + private worker: Worker | null = null; private nextMessageId = 0; private pendingMessages = new Map void; @@ -23,7 +23,7 @@ class Mp3Encoder extends CustomAudioEncoder { private buffer = new Uint8Array(2 ** 16); private currentBufferOffset = 0; private currentTimestamp = 0; - private chunkMetadata!: EncodedAudioChunkMetadata; + private chunkMetadata: EncodedAudioChunkMetadata = {}; static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean { return codec === 'mp3' @@ -113,7 +113,7 @@ class Mp3Encoder extends CustomAudioEncoder { } close() { - this.worker.terminate(); + this.worker?.terminate(); } /** @@ -174,6 +174,8 @@ class Mp3Encoder extends CustomAudioEncoder { const id = this.nextMessageId++; this.pendingMessages.set(id, { resolve, reject }); + assert(this.worker); + if (transferables) { this.worker.postMessage({ id, command }, transferables); } else { diff --git a/src/conversion.ts b/src/conversion.ts index 7f4e156..c9a9b04 100644 --- a/src/conversion.ts +++ b/src/conversion.ts @@ -410,7 +410,7 @@ export class Conversion { } catch (error) { if (!this._canceled) { // Make sure to cancel to stop other encoding processes and clean up resources - await this.cancel(); + void this.cancel(); } throw error; diff --git a/src/media-source.ts b/src/media-source.ts index 72dd9b4..22ff4e7 100644 --- a/src/media-source.ts +++ b/src/media-source.ts @@ -80,7 +80,8 @@ export abstract class MediaSource { /** @internal */ async _start() {} /** @internal */ - async _flushAndClose() {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async _flushAndClose(forceClose: boolean) {} /** * Closes this source. This prevents future samples from being added and signals to the output file that no further @@ -103,7 +104,7 @@ export abstract class MediaSource { } this._closingPromise = (async () => { - await this._flushAndClose(); + await this._flushAndClose(false); this._closed = true; @@ -116,12 +117,12 @@ export abstract class MediaSource { } /** @internal */ - async _flushOrWaitForClose() { + async _flushOrWaitForOngoingClose(forceClose: boolean) { if (this._closingPromise) { // Since closing also flushes, we don't want to do it twice return this._closingPromise; } else { - return this._flushAndClose(); + return this._flushAndClose(forceClose); } } } @@ -451,14 +452,20 @@ class VideoEncoderWrapper { })(); } - async flushAndClose() { + async flushAndClose(forceClose: boolean) { this.checkForEncoderError(); if (this.customEncoder) { - void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush()); + if (!forceClose) { + void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush()); + } + await this.customEncoderCallSerializer.call(() => this.customEncoder!.close()); } else if (this.encoder) { - await this.encoder.flush(); + if (!forceClose) { + await this.encoder.flush(); + } + this.encoder.close(); } @@ -512,8 +519,8 @@ export class VideoSampleSource extends VideoSource { } /** @internal */ - override _flushAndClose() { - return this._encoder.flushAndClose(); + override _flushAndClose(forceClose: boolean) { + return this._encoder.flushAndClose(forceClose); } } @@ -564,8 +571,8 @@ export class CanvasSource extends VideoSource { } /** @internal */ - override _flushAndClose() { - return this._encoder.flushAndClose(); + override _flushAndClose(forceClose: boolean) { + return this._encoder.flushAndClose(forceClose); } } @@ -714,7 +721,7 @@ export class MediaStreamVideoTrackSource extends VideoSource { } /** @internal */ - override async _flushAndClose() { + override async _flushAndClose(forceClose: boolean) { if (this._abortController) { this._abortController.abort(); this._abortController = null; @@ -746,7 +753,7 @@ export class MediaStreamVideoTrackSource extends VideoSource { }); } - await this._encoder.flushAndClose(); + await this._encoder.flushAndClose(forceClose); } } @@ -1226,14 +1233,20 @@ class AudioEncoderWrapper { } } - async flushAndClose() { + async flushAndClose(forceClose: boolean) { this.checkForEncoderError(); if (this.customEncoder) { - void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush()); + if (!forceClose) { + void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush()); + } + await this.customEncoderCallSerializer.call(() => this.customEncoder!.close()); } else if (this.encoder) { - await this.encoder.flush(); + if (!forceClose) { + await this.encoder.flush(); + } + this.encoder.close(); } @@ -1289,8 +1302,8 @@ export class AudioSampleSource extends AudioSource { } /** @internal */ - override _flushAndClose() { - return this._encoder.flushAndClose(); + override _flushAndClose(forceClose: boolean) { + return this._encoder.flushAndClose(forceClose); } } @@ -1333,8 +1346,8 @@ export class AudioBufferSource extends AudioSource { } /** @internal */ - override _flushAndClose() { - return this._encoder.flushAndClose(); + override _flushAndClose(forceClose: boolean) { + return this._encoder.flushAndClose(forceClose); } } @@ -1480,7 +1493,7 @@ export class MediaStreamAudioTrackSource extends AudioSource { } /** @internal */ - override async _flushAndClose() { + override async _flushAndClose(forceClose: boolean) { if (this._abortController) { this._abortController.abort(); this._abortController = null; @@ -1493,7 +1506,7 @@ export class MediaStreamAudioTrackSource extends AudioSource { await this._audioContext.suspend(); } - await this._encoder.flushAndClose(); + await this._encoder.flushAndClose(forceClose); } } diff --git a/src/output.ts b/src/output.ts index 31e1153..e45b9b79 100644 --- a/src/output.ts +++ b/src/output.ts @@ -380,7 +380,7 @@ export class Output< const release = await this._mutex.acquire(); - const promises = this._tracks.map(x => x.source._flushOrWaitForClose()); + const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(true)); // Force close await Promise.all(promises); await this._writer.close(); @@ -410,7 +410,7 @@ export class Output< const release = await this._mutex.acquire(); - const promises = this._tracks.map(x => x.source._flushOrWaitForClose()); + const promises = this._tracks.map(x => x.source._flushOrWaitForOngoingClose(false)); await Promise.all(promises); await this._muxer.finalize(); diff --git a/todo.txt b/todo.txt index 8c5b634..5c0532a 100644 --- a/todo.txt +++ b/todo.txt @@ -1,3 +1 @@ -SWALLOWED ERRORS FOR CUSTOM ENCODESRS (like in init) - -- mention in the docs how the delcaration file can be used together with the