mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-03 05:43:50 +02:00
Validate types returned by custom coders, refactor custom coder call serialization logic, improve metadata validation
This commit is contained in:
+2
-5
@@ -21,10 +21,7 @@
|
|||||||
chunked: true,
|
chunked: true,
|
||||||
chunkSize: 2**20
|
chunkSize: 2**20
|
||||||
});
|
});
|
||||||
const outputFormat = new Metamuxer.Mp4OutputFormat({
|
const outputFormat = new Metamuxer.OggOutputFormat();
|
||||||
//streamable: true
|
|
||||||
//fastStart: 'fragmented'
|
|
||||||
});
|
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
button.textContent = 'Cancel';
|
button.textContent = 'Cancel';
|
||||||
@@ -41,7 +38,7 @@
|
|||||||
target
|
target
|
||||||
}),
|
}),
|
||||||
audio: {
|
audio: {
|
||||||
discard: true
|
//discard: true
|
||||||
//forceReencode: true,
|
//forceReencode: true,
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
|
|||||||
+4
-4
@@ -22,7 +22,7 @@ export abstract class CustomVideoDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Called after decoder creation; can be used for custom initialization logic. */
|
/** Called after decoder creation; can be used for custom initialization logic. */
|
||||||
abstract init(): void;
|
abstract init(): Promise<void> | void;
|
||||||
/** Decodes the provided encoded packet. */
|
/** Decodes the provided encoded packet. */
|
||||||
abstract decode(packet: EncodedPacket): Promise<void> | void;
|
abstract decode(packet: EncodedPacket): Promise<void> | void;
|
||||||
/** Decodes all remaining packets and then resolves. */
|
/** Decodes all remaining packets and then resolves. */
|
||||||
@@ -51,7 +51,7 @@ export abstract class CustomAudioDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Called after decoder creation; can be used for custom initialization logic. */
|
/** Called after decoder creation; can be used for custom initialization logic. */
|
||||||
abstract init(): void;
|
abstract init(): Promise<void> | void;
|
||||||
/** Decodes the provided encoded packet. */
|
/** Decodes the provided encoded packet. */
|
||||||
abstract decode(packet: EncodedPacket): Promise<void> | void;
|
abstract decode(packet: EncodedPacket): Promise<void> | void;
|
||||||
/** Decodes all remaining packets and then resolves. */
|
/** Decodes all remaining packets and then resolves. */
|
||||||
@@ -80,7 +80,7 @@ export abstract class CustomVideoEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Called after encoder creation; can be used for custom initialization logic. */
|
/** Called after encoder creation; can be used for custom initialization logic. */
|
||||||
abstract init(): void;
|
abstract init(): Promise<void> | void;
|
||||||
/** Encodes the provided video sample. */
|
/** Encodes the provided video sample. */
|
||||||
abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): Promise<void> | void;
|
abstract encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): Promise<void> | void;
|
||||||
/** Encodes all remaining video samples and then resolves. */
|
/** Encodes all remaining video samples and then resolves. */
|
||||||
@@ -109,7 +109,7 @@ export abstract class CustomAudioEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Called after encoder creation; can be used for custom initialization logic. */
|
/** Called after encoder creation; can be used for custom initialization logic. */
|
||||||
abstract init(): void;
|
abstract init(): Promise<void> | void;
|
||||||
/** Encodes the provided audio sample. */
|
/** Encodes the provided audio sample. */
|
||||||
abstract encode(audioSample: AudioSample): Promise<void> | void;
|
abstract encode(audioSample: AudioSample): Promise<void> | void;
|
||||||
/** Encodes all remaining audio samples and then resolves. */
|
/** Encodes all remaining audio samples and then resolves. */
|
||||||
|
|||||||
+29
-20
@@ -5,6 +5,7 @@ import {
|
|||||||
AnyIterable,
|
AnyIterable,
|
||||||
assert,
|
assert,
|
||||||
binarySearchLessOrEqual,
|
binarySearchLessOrEqual,
|
||||||
|
CallSerializer,
|
||||||
getInt24,
|
getInt24,
|
||||||
getUint24,
|
getUint24,
|
||||||
last,
|
last,
|
||||||
@@ -640,7 +641,7 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|||||||
decoder: VideoDecoder | null = null;
|
decoder: VideoDecoder | null = null;
|
||||||
|
|
||||||
customDecoder: CustomVideoDecoder | null = null;
|
customDecoder: CustomVideoDecoder | null = null;
|
||||||
lastCustomDecoderPromise = Promise.resolve();
|
customDecoderCallSerializer = new CallSerializer();
|
||||||
customDecoderQueueSize = 0;
|
customDecoderQueueSize = 0;
|
||||||
|
|
||||||
sampleQueue: VideoSample[] = [];
|
sampleQueue: VideoSample[] = [];
|
||||||
@@ -683,9 +684,15 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|||||||
this.customDecoder = new MatchingCustomDecoder() as CustomVideoDecoder;
|
this.customDecoder = new MatchingCustomDecoder() as CustomVideoDecoder;
|
||||||
this.customDecoder.codec = codec;
|
this.customDecoder.codec = codec;
|
||||||
this.customDecoder.config = decoderConfig;
|
this.customDecoder.config = decoderConfig;
|
||||||
this.customDecoder.onSample = sampleHandler;
|
this.customDecoder.onSample = (sample) => {
|
||||||
|
if (!(sample instanceof VideoSample)) {
|
||||||
|
throw new TypeError('The argument passed to onSample must be a VideoSample.');
|
||||||
|
}
|
||||||
|
|
||||||
this.customDecoder.init();
|
sampleHandler(sample);
|
||||||
|
};
|
||||||
|
|
||||||
|
void this.customDecoderCallSerializer.call(() => this.customDecoder!.init());
|
||||||
} else {
|
} else {
|
||||||
this.decoder = new VideoDecoder({
|
this.decoder = new VideoDecoder({
|
||||||
output: frame => sampleHandler(new VideoSample(frame)),
|
output: frame => sampleHandler(new VideoSample(frame)),
|
||||||
@@ -715,11 +722,9 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|||||||
decode(packet: EncodedPacket) {
|
decode(packet: EncodedPacket) {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
this.customDecoderQueueSize++;
|
this.customDecoderQueueSize++;
|
||||||
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
void this.customDecoderCallSerializer
|
||||||
return this.customDecoder!.decode(packet);
|
.call(() => this.customDecoder!.decode(packet))
|
||||||
});
|
.then(() => this.customDecoderQueueSize--);
|
||||||
|
|
||||||
void this.lastCustomDecoderPromise.then(() => this.customDecoderQueueSize--);
|
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
this.decoder.decode(packet.toEncodedVideoChunk());
|
this.decoder.decode(packet.toEncodedVideoChunk());
|
||||||
@@ -728,7 +733,7 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|||||||
|
|
||||||
async flush() {
|
async flush() {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
await this.lastCustomDecoderPromise.then(() => this.customDecoder!.flush());
|
await this.customDecoderCallSerializer.call(() => this.customDecoder!.flush());
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
await this.decoder.flush();
|
await this.decoder.flush();
|
||||||
@@ -742,7 +747,7 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
|
|||||||
|
|
||||||
close() {
|
close() {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
void this.lastCustomDecoderPromise.then(() => this.customDecoder!.close());
|
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
this.decoder.close();
|
this.decoder.close();
|
||||||
@@ -1097,7 +1102,7 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
decoder: AudioDecoder | null = null;
|
decoder: AudioDecoder | null = null;
|
||||||
|
|
||||||
customDecoder: CustomAudioDecoder | null = null;
|
customDecoder: CustomAudioDecoder | null = null;
|
||||||
lastCustomDecoderPromise = Promise.resolve();
|
customDecoderCallSerializer = new CallSerializer();
|
||||||
customDecoderQueueSize = 0;
|
customDecoderQueueSize = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -1123,9 +1128,15 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
this.customDecoder = new MatchingCustomDecoder() as CustomAudioDecoder;
|
this.customDecoder = new MatchingCustomDecoder() as CustomAudioDecoder;
|
||||||
this.customDecoder.codec = codec;
|
this.customDecoder.codec = codec;
|
||||||
this.customDecoder.config = decoderConfig;
|
this.customDecoder.config = decoderConfig;
|
||||||
this.customDecoder.onSample = sampleHandler;
|
this.customDecoder.onSample = (sample) => {
|
||||||
|
if (!(sample instanceof AudioSample)) {
|
||||||
|
throw new TypeError('The argument passed to onSample must be an AudioSample.');
|
||||||
|
}
|
||||||
|
|
||||||
this.customDecoder.init();
|
sampleHandler(sample);
|
||||||
|
};
|
||||||
|
|
||||||
|
void this.customDecoderCallSerializer.call(() => this.customDecoder!.init());
|
||||||
} else {
|
} else {
|
||||||
this.decoder = new AudioDecoder({
|
this.decoder = new AudioDecoder({
|
||||||
output: data => sampleHandler(new AudioSample(data)),
|
output: data => sampleHandler(new AudioSample(data)),
|
||||||
@@ -1147,11 +1158,9 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
decode(packet: EncodedPacket) {
|
decode(packet: EncodedPacket) {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
this.customDecoderQueueSize++;
|
this.customDecoderQueueSize++;
|
||||||
this.lastCustomDecoderPromise = this.lastCustomDecoderPromise.then(() => {
|
void this.customDecoderCallSerializer
|
||||||
return this.customDecoder!.decode(packet);
|
.call(() => this.customDecoder!.decode(packet))
|
||||||
});
|
.then(() => this.customDecoderQueueSize--);
|
||||||
|
|
||||||
void this.lastCustomDecoderPromise.then(() => this.customDecoderQueueSize--);
|
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
this.decoder.decode(packet.toEncodedAudioChunk());
|
this.decoder.decode(packet.toEncodedAudioChunk());
|
||||||
@@ -1160,7 +1169,7 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
|
|
||||||
flush() {
|
flush() {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
return this.lastCustomDecoderPromise.then(() => this.customDecoder!.flush());
|
return this.customDecoderCallSerializer.call(() => this.customDecoder!.flush());
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
return this.decoder.flush();
|
return this.decoder.flush();
|
||||||
@@ -1169,7 +1178,7 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
|||||||
|
|
||||||
close() {
|
close() {
|
||||||
if (this.customDecoder) {
|
if (this.customDecoder) {
|
||||||
void this.lastCustomDecoderPromise.then(() => this.customDecoder!.close());
|
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
||||||
} else {
|
} else {
|
||||||
assert(this.decoder);
|
assert(this.decoder);
|
||||||
this.decoder.close();
|
this.decoder.close();
|
||||||
|
|||||||
+64
-46
@@ -16,7 +16,7 @@ import {
|
|||||||
VideoCodec,
|
VideoCodec,
|
||||||
} from './codec';
|
} from './codec';
|
||||||
import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
|
import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack } from './output';
|
||||||
import { assert, clamp, promiseWithResolvers, setInt24, setUint24 } from './misc';
|
import { assert, CallSerializer, clamp, promiseWithResolvers, setInt24, setUint24 } from './misc';
|
||||||
import { Muxer } from './muxer';
|
import { Muxer } from './muxer';
|
||||||
import { SubtitleParser } from './subtitles';
|
import { SubtitleParser } from './subtitles';
|
||||||
import { toAlaw, toUlaw } from './pcm';
|
import { toAlaw, toUlaw } from './pcm';
|
||||||
@@ -69,7 +69,7 @@ export abstract class MediaSource {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_start() {}
|
_start() {}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _flush() {}
|
async _flushAndClose() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -92,7 +92,7 @@ export abstract class MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._closingPromise = (async () => {
|
return this._closingPromise = (async () => {
|
||||||
await this._flush();
|
await this._flushAndClose();
|
||||||
|
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ export abstract class MediaSource {
|
|||||||
// 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._flush();
|
return this._flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +161,9 @@ export class EncodedVideoPacketSource extends VideoSource {
|
|||||||
if (packet.isMetadataOnly) {
|
if (packet.isMetadataOnly) {
|
||||||
throw new TypeError('Metadata-only packets cannot be added.');
|
throw new TypeError('Metadata-only packets cannot be added.');
|
||||||
}
|
}
|
||||||
|
if (meta !== undefined && (!meta || typeof meta !== 'object')) {
|
||||||
|
throw new TypeError('meta, when provided, must be an object.');
|
||||||
|
}
|
||||||
|
|
||||||
this._ensureValidAdd();
|
this._ensureValidAdd();
|
||||||
return this._connectedTrack!.output._muxer.addEncodedVideoPacket(this._connectedTrack!, packet, meta);
|
return this._connectedTrack!.output._muxer.addEncodedVideoPacket(this._connectedTrack!, packet, meta);
|
||||||
@@ -250,7 +253,7 @@ class VideoEncoderWrapper {
|
|||||||
private lastHeight: number | null = null;
|
private lastHeight: number | null = null;
|
||||||
|
|
||||||
private customEncoder: CustomVideoEncoder | null = null;
|
private customEncoder: CustomVideoEncoder | null = null;
|
||||||
private lastCustomEncoderPromise = Promise.resolve();
|
private customEncoderCallSerializer = new CallSerializer();
|
||||||
private customEncoderQueueSize = 0;
|
private customEncoderQueueSize = 0;
|
||||||
|
|
||||||
constructor(private source: VideoSource, private encodingConfig: VideoEncodingConfig) {}
|
constructor(private source: VideoSource, private encodingConfig: VideoEncodingConfig) {}
|
||||||
@@ -296,20 +299,18 @@ class VideoEncoderWrapper {
|
|||||||
|
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
this.customEncoderQueueSize++;
|
this.customEncoderQueueSize++;
|
||||||
this.lastCustomEncoderPromise = this.lastCustomEncoderPromise.then(() => {
|
const promise = this.customEncoderCallSerializer
|
||||||
return this.customEncoder!.encode(videoSample, finalEncodeOptions);
|
.call(() => this.customEncoder!.encode(videoSample, finalEncodeOptions))
|
||||||
});
|
.then(() => {
|
||||||
|
this.customEncoderQueueSize--;
|
||||||
|
|
||||||
void this.lastCustomEncoderPromise.then(() => {
|
if (shouldClose) {
|
||||||
this.customEncoderQueueSize--;
|
videoSample.close();
|
||||||
|
}
|
||||||
if (shouldClose) {
|
});
|
||||||
videoSample.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.customEncoderQueueSize >= 4) {
|
if (this.customEncoderQueueSize >= 4) {
|
||||||
await this.lastCustomEncoderPromise;
|
await promise;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assert(this.encoder);
|
assert(this.encoder);
|
||||||
@@ -371,11 +372,18 @@ class VideoEncoderWrapper {
|
|||||||
this.customEncoder.codec = this.encodingConfig.codec;
|
this.customEncoder.codec = this.encodingConfig.codec;
|
||||||
this.customEncoder.config = encoderConfig;
|
this.customEncoder.config = encoderConfig;
|
||||||
this.customEncoder.onPacket = (packet, meta) => {
|
this.customEncoder.onPacket = (packet, meta) => {
|
||||||
|
if (!(packet instanceof EncodedPacket)) {
|
||||||
|
throw new TypeError('The first argument passed to onPacket must be an EncodedPacket.');
|
||||||
|
}
|
||||||
|
if (meta !== undefined && (!meta || typeof meta !== 'object')) {
|
||||||
|
throw new TypeError('The second argument passed to onPacket must be an object or undefined.');
|
||||||
|
}
|
||||||
|
|
||||||
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
||||||
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.customEncoder.init();
|
await this.customEncoder.init();
|
||||||
} else {
|
} else {
|
||||||
if (typeof VideoEncoder === 'undefined') {
|
if (typeof VideoEncoder === 'undefined') {
|
||||||
throw new Error('VideoEncoder is not supported by this browser.');
|
throw new Error('VideoEncoder is not supported by this browser.');
|
||||||
@@ -409,9 +417,10 @@ class VideoEncoderWrapper {
|
|||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async flush() {
|
async flushAndClose() {
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
await this.lastCustomEncoderPromise.then(() => this.customEncoder!.flush());
|
void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
|
||||||
|
await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
|
||||||
} else if (this.encoder) {
|
} else if (this.encoder) {
|
||||||
await this.encoder.flush();
|
await this.encoder.flush();
|
||||||
this.encoder.close();
|
this.encoder.close();
|
||||||
@@ -459,8 +468,8 @@ export class VideoSampleSource extends VideoSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override _flush() {
|
override _flushAndClose() {
|
||||||
return this._encoder.flush();
|
return this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,8 +520,8 @@ export class CanvasSource extends VideoSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override _flush() {
|
override _flushAndClose() {
|
||||||
return this._encoder.flush();
|
return this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,13 +587,13 @@ export class MediaStreamVideoTrackSource extends VideoSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override async _flush() {
|
override async _flushAndClose() {
|
||||||
if (this._abortController) {
|
if (this._abortController) {
|
||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
this._abortController = null;
|
this._abortController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._encoder.flush();
|
await this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,6 +643,9 @@ export class EncodedAudioPacketSource extends AudioSource {
|
|||||||
if (packet.isMetadataOnly) {
|
if (packet.isMetadataOnly) {
|
||||||
throw new TypeError('Metadata-only packets cannot be added.');
|
throw new TypeError('Metadata-only packets cannot be added.');
|
||||||
}
|
}
|
||||||
|
if (meta !== undefined && (!meta || typeof meta !== 'object')) {
|
||||||
|
throw new TypeError('meta, when provided, must be an object.');
|
||||||
|
}
|
||||||
|
|
||||||
this._ensureValidAdd();
|
this._ensureValidAdd();
|
||||||
return this._connectedTrack!.output._muxer.addEncodedAudioPacket(this._connectedTrack!, packet, meta);
|
return this._connectedTrack!.output._muxer.addEncodedAudioPacket(this._connectedTrack!, packet, meta);
|
||||||
@@ -719,7 +731,7 @@ class AudioEncoderWrapper {
|
|||||||
private writeOutputValue: ((view: DataView, byteOffset: number, value: number) => void) | null = null;
|
private writeOutputValue: ((view: DataView, byteOffset: number, value: number) => void) | null = null;
|
||||||
|
|
||||||
private customEncoder: CustomAudioEncoder | null = null;
|
private customEncoder: CustomAudioEncoder | null = null;
|
||||||
private lastCustomEncoderPromise = Promise.resolve();
|
private customEncoderCallSerializer = new CallSerializer();
|
||||||
private customEncoderQueueSize = 0;
|
private customEncoderQueueSize = 0;
|
||||||
|
|
||||||
constructor(private source: AudioSource, private encodingConfig: AudioEncodingConfig) {}
|
constructor(private source: AudioSource, private encodingConfig: AudioEncodingConfig) {}
|
||||||
@@ -755,20 +767,18 @@ class AudioEncoderWrapper {
|
|||||||
|
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
this.customEncoderQueueSize++;
|
this.customEncoderQueueSize++;
|
||||||
this.lastCustomEncoderPromise = this.lastCustomEncoderPromise.then(() => {
|
const promise = this.customEncoderCallSerializer
|
||||||
return this.customEncoder!.encode(audioSample);
|
.call(() => this.customEncoder!.encode(audioSample))
|
||||||
});
|
.then(() => {
|
||||||
|
this.customEncoderQueueSize--;
|
||||||
|
|
||||||
void this.lastCustomEncoderPromise.then(() => {
|
if (shouldClose) {
|
||||||
this.customEncoderQueueSize--;
|
audioSample.close();
|
||||||
|
}
|
||||||
if (shouldClose) {
|
});
|
||||||
audioSample.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.customEncoderQueueSize >= 4) {
|
if (this.customEncoderQueueSize >= 4) {
|
||||||
await this.lastCustomEncoderPromise;
|
await promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure
|
await this.muxer!.mutex.currentPromise; // Allow the writer to apply backpressure
|
||||||
@@ -900,11 +910,18 @@ class AudioEncoderWrapper {
|
|||||||
this.customEncoder.codec = this.encodingConfig.codec;
|
this.customEncoder.codec = this.encodingConfig.codec;
|
||||||
this.customEncoder.config = encoderConfig;
|
this.customEncoder.config = encoderConfig;
|
||||||
this.customEncoder.onPacket = (packet, meta) => {
|
this.customEncoder.onPacket = (packet, meta) => {
|
||||||
|
if (!(packet instanceof EncodedPacket)) {
|
||||||
|
throw new TypeError('The first argument passed to onPacket must be an EncodedPacket.');
|
||||||
|
}
|
||||||
|
if (meta !== undefined && (!meta || typeof meta !== 'object')) {
|
||||||
|
throw new TypeError('The second argument passed to onPacket must be an object or undefined.');
|
||||||
|
}
|
||||||
|
|
||||||
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
||||||
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.customEncoder.init();
|
await this.customEncoder.init();
|
||||||
} else if ((PCM_AUDIO_CODECS as readonly string[]).includes(this.encodingConfig.codec)) {
|
} else if ((PCM_AUDIO_CODECS as readonly string[]).includes(this.encodingConfig.codec)) {
|
||||||
this.initPcmEncoder();
|
this.initPcmEncoder();
|
||||||
} else {
|
} else {
|
||||||
@@ -1020,9 +1037,10 @@ class AudioEncoderWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async flush() {
|
async flushAndClose() {
|
||||||
if (this.customEncoder) {
|
if (this.customEncoder) {
|
||||||
await this.lastCustomEncoderPromise.then(() => this.customEncoder!.flush());
|
void this.customEncoderCallSerializer.call(() => this.customEncoder!.flush());
|
||||||
|
await this.customEncoderCallSerializer.call(() => this.customEncoder!.close());
|
||||||
} else if (this.encoder) {
|
} else if (this.encoder) {
|
||||||
await this.encoder.flush();
|
await this.encoder.flush();
|
||||||
this.encoder.close();
|
this.encoder.close();
|
||||||
@@ -1072,8 +1090,8 @@ export class AudioSampleSource extends AudioSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override _flush() {
|
override _flushAndClose() {
|
||||||
return this._encoder.flush();
|
return this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1153,8 +1171,8 @@ export class AudioBufferSource extends AudioSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override _flush() {
|
override _flushAndClose() {
|
||||||
return this._encoder.flush();
|
return this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1215,13 +1233,13 @@ export class MediaStreamAudioTrackSource extends AudioSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override async _flush() {
|
override async _flushAndClose() {
|
||||||
if (this._abortController) {
|
if (this._abortController) {
|
||||||
this._abortController.abort();
|
this._abortController.abort();
|
||||||
this._abortController = null;
|
this._abortController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._encoder.flush();
|
await this._encoder.flushAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -494,3 +494,11 @@ export const computeRationalApproximation = (x: number, maxDenominator: number)
|
|||||||
denominator: currDenominator,
|
denominator: currDenominator,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export class CallSerializer {
|
||||||
|
currentPromise = Promise.resolve();
|
||||||
|
|
||||||
|
call(fn: () => Promise<void> | void) {
|
||||||
|
return this.currentPromise = this.currentPromise.then(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OPUS_INTERNAL_SAMPLE_RATE, parseOpusIdentificationHeader } from '../codec';
|
import { OPUS_INTERNAL_SAMPLE_RATE, parseOpusIdentificationHeader, validateAudioChunkMetadata } from '../codec';
|
||||||
import { assert, setInt64, toDataView, toUint8Array } from '../misc';
|
import { assert, setInt64, toDataView, toUint8Array } from '../misc';
|
||||||
import { Muxer } from '../muxer';
|
import { Muxer } from '../muxer';
|
||||||
import { Output, OutputAudioTrack } from '../output';
|
import { Output, OutputAudioTrack } from '../output';
|
||||||
@@ -81,9 +81,10 @@ export class OggMuxer extends Muxer {
|
|||||||
|
|
||||||
assert(track.source._codec === 'vorbis' || track.source._codec === 'opus');
|
assert(track.source._codec === 'vorbis' || track.source._codec === 'opus');
|
||||||
|
|
||||||
|
validateAudioChunkMetadata(meta);
|
||||||
|
|
||||||
assert(meta);
|
assert(meta);
|
||||||
assert(meta.decoderConfig);
|
assert(meta.decoderConfig);
|
||||||
assert(meta.decoderConfig.sampleRate);
|
|
||||||
|
|
||||||
const newTrackData: OggTrackData = {
|
const newTrackData: OggTrackData = {
|
||||||
track,
|
track,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Muxer } from '../muxer';
|
import { Muxer } from '../muxer';
|
||||||
import { Output, OutputAudioTrack } from '../output';
|
import { Output, OutputAudioTrack } from '../output';
|
||||||
import { parsePcmCodec, PcmAudioCodec } from '../codec';
|
import { parsePcmCodec, PcmAudioCodec, validateAudioChunkMetadata } from '../codec';
|
||||||
import { WaveFormat } from './wave-demuxer';
|
import { WaveFormat } from './wave-demuxer';
|
||||||
import { RiffWriter } from './riff-writer';
|
import { RiffWriter } from './riff-writer';
|
||||||
import { Writer } from '../writer';
|
import { Writer } from '../writer';
|
||||||
import { EncodedPacket } from '../packet';
|
import { EncodedPacket } from '../packet';
|
||||||
import { WavOutputFormat } from '../output-format';
|
import { WavOutputFormat } from '../output-format';
|
||||||
|
import { assert } from '../misc';
|
||||||
|
|
||||||
export class WaveMuxer extends Muxer {
|
export class WaveMuxer extends Muxer {
|
||||||
private format: WavOutputFormat;
|
private format: WavOutputFormat;
|
||||||
@@ -39,9 +40,10 @@ export class WaveMuxer extends Muxer {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!this.headerWritten) {
|
if (!this.headerWritten) {
|
||||||
if (!meta?.decoderConfig) {
|
validateAudioChunkMetadata(meta);
|
||||||
throw new Error('Decoder config is required for first audio sample.');
|
|
||||||
}
|
assert(meta);
|
||||||
|
assert(meta.decoderConfig);
|
||||||
|
|
||||||
this.writeHeader(track, meta.decoderConfig);
|
this.writeHeader(track, meta.decoderConfig);
|
||||||
this.headerWritten = true;
|
this.headerWritten = true;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
|
- https://github.com/Vanilagy/mp4-muxer/issues/83 tell him it's possible now
|
||||||
- cross-track offset for streaming sources
|
- cross-track offset for streaming sources
|
||||||
- textsubtitlesource, chunked piping
|
- textsubtitlesource, chunked piping
|
||||||
- More efficient MP3 loading when reading sequentially
|
- More efficient MP3 loading when reading sequentially
|
||||||
- Validate types returned by custom coders
|
|
||||||
Reference in New Issue
Block a user