Circumvent flush() being called on sources when cancelling output, surface error before doing cleanup

This commit is contained in:
Vanilagy
2025-08-10 03:24:17 +02:00
parent 64f986bc1b
commit ba0491de3a
7 changed files with 46 additions and 33 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "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.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "0.0.1", "version": "1.6.0",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"types": "./dist/modules/src/index.d.ts", "types": "./dist/modules/src/index.d.ts",
+5 -3
View File
@@ -13,7 +13,7 @@ import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared
import createWorker from './encode.worker'; import createWorker from './encode.worker';
class Mp3Encoder extends CustomAudioEncoder { class Mp3Encoder extends CustomAudioEncoder {
private worker!: Worker; private worker: Worker | null = null;
private nextMessageId = 0; private nextMessageId = 0;
private pendingMessages = new Map<number, { private pendingMessages = new Map<number, {
resolve: (value: WorkerResponseData) => void; resolve: (value: WorkerResponseData) => void;
@@ -23,7 +23,7 @@ class Mp3Encoder extends CustomAudioEncoder {
private buffer = new Uint8Array(2 ** 16); private buffer = new Uint8Array(2 ** 16);
private currentBufferOffset = 0; private currentBufferOffset = 0;
private currentTimestamp = 0; private currentTimestamp = 0;
private chunkMetadata!: EncodedAudioChunkMetadata; private chunkMetadata: EncodedAudioChunkMetadata = {};
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean { static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
return codec === 'mp3' return codec === 'mp3'
@@ -113,7 +113,7 @@ class Mp3Encoder extends CustomAudioEncoder {
} }
close() { close() {
this.worker.terminate(); this.worker?.terminate();
} }
/** /**
@@ -174,6 +174,8 @@ class Mp3Encoder extends CustomAudioEncoder {
const id = this.nextMessageId++; const id = this.nextMessageId++;
this.pendingMessages.set(id, { resolve, reject }); this.pendingMessages.set(id, { resolve, reject });
assert(this.worker);
if (transferables) { if (transferables) {
this.worker.postMessage({ id, command }, transferables); this.worker.postMessage({ id, command }, transferables);
} else { } else {
+1 -1
View File
@@ -410,7 +410,7 @@ export class Conversion {
} catch (error) { } catch (error) {
if (!this._canceled) { if (!this._canceled) {
// Make sure to cancel to stop other encoding processes and clean up resources // Make sure to cancel to stop other encoding processes and clean up resources
await this.cancel(); void this.cancel();
} }
throw error; throw error;
+35 -22
View File
@@ -80,7 +80,8 @@ export abstract class MediaSource {
/** @internal */ /** @internal */
async _start() {} async _start() {}
/** @internal */ /** @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 * 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 () => { this._closingPromise = (async () => {
await this._flushAndClose(); await this._flushAndClose(false);
this._closed = true; this._closed = true;
@@ -116,12 +117,12 @@ export abstract class MediaSource {
} }
/** @internal */ /** @internal */
async _flushOrWaitForClose() { async _flushOrWaitForOngoingClose(forceClose: boolean) {
if (this._closingPromise) { if (this._closingPromise) {
// Since closing also flushes, we don't want to do it twice // Since closing also flushes, we don't want to do it twice
return this._closingPromise; return this._closingPromise;
} else { } else {
return this._flushAndClose(); return this._flushAndClose(forceClose);
} }
} }
} }
@@ -451,14 +452,20 @@ class VideoEncoderWrapper {
})(); })();
} }
async flushAndClose() { async flushAndClose(forceClose: boolean) {
this.checkForEncoderError(); this.checkForEncoderError();
if (this.customEncoder) { 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()); await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
} else if (this.encoder) { } else if (this.encoder) {
await this.encoder.flush(); if (!forceClose) {
await this.encoder.flush();
}
this.encoder.close(); this.encoder.close();
} }
@@ -512,8 +519,8 @@ export class VideoSampleSource extends VideoSource {
} }
/** @internal */ /** @internal */
override _flushAndClose() { override _flushAndClose(forceClose: boolean) {
return this._encoder.flushAndClose(); return this._encoder.flushAndClose(forceClose);
} }
} }
@@ -564,8 +571,8 @@ export class CanvasSource extends VideoSource {
} }
/** @internal */ /** @internal */
override _flushAndClose() { override _flushAndClose(forceClose: boolean) {
return this._encoder.flushAndClose(); return this._encoder.flushAndClose(forceClose);
} }
} }
@@ -714,7 +721,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
} }
/** @internal */ /** @internal */
override async _flushAndClose() { override async _flushAndClose(forceClose: boolean) {
if (this._abortController) { if (this._abortController) {
this._abortController.abort(); this._abortController.abort();
this._abortController = null; 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(); this.checkForEncoderError();
if (this.customEncoder) { 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()); await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
} else if (this.encoder) { } else if (this.encoder) {
await this.encoder.flush(); if (!forceClose) {
await this.encoder.flush();
}
this.encoder.close(); this.encoder.close();
} }
@@ -1289,8 +1302,8 @@ export class AudioSampleSource extends AudioSource {
} }
/** @internal */ /** @internal */
override _flushAndClose() { override _flushAndClose(forceClose: boolean) {
return this._encoder.flushAndClose(); return this._encoder.flushAndClose(forceClose);
} }
} }
@@ -1333,8 +1346,8 @@ export class AudioBufferSource extends AudioSource {
} }
/** @internal */ /** @internal */
override _flushAndClose() { override _flushAndClose(forceClose: boolean) {
return this._encoder.flushAndClose(); return this._encoder.flushAndClose(forceClose);
} }
} }
@@ -1480,7 +1493,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
} }
/** @internal */ /** @internal */
override async _flushAndClose() { override async _flushAndClose(forceClose: boolean) {
if (this._abortController) { if (this._abortController) {
this._abortController.abort(); this._abortController.abort();
this._abortController = null; this._abortController = null;
@@ -1493,7 +1506,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
await this._audioContext.suspend(); await this._audioContext.suspend();
} }
await this._encoder.flushAndClose(); await this._encoder.flushAndClose(forceClose);
} }
} }
+2 -2
View File
@@ -380,7 +380,7 @@ export class Output<
const release = await this._mutex.acquire(); 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 Promise.all(promises);
await this._writer.close(); await this._writer.close();
@@ -410,7 +410,7 @@ export class Output<
const release = await this._mutex.acquire(); 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 Promise.all(promises);
await this._muxer.finalize(); await this._muxer.finalize();
+1 -3
View File
@@ -1,3 +1 @@
SWALLOWED ERRORS FOR CUSTOM ENCODESRS (like in init) - package.json metadata
- mention in the docs how the delcaration file can be used together with the <script> include