mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-09 08:43:49 +02:00
Add AudioSampleCursor, make sample cursor error behavior more explicit, add sample mapping, and a bunch of extra shit I forgot
This commit is contained in:
+559
-469
File diff suppressed because it is too large
Load Diff
+7
-1
@@ -6,6 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { SampleCursor } from './cursors';
|
||||
import { Demuxer } from './demuxer';
|
||||
import { InputFormat } from './input-format';
|
||||
import { assert, polyfillSymbolDispose } from './misc';
|
||||
@@ -44,6 +45,9 @@ export class Input<S extends Source = Source> implements Disposable {
|
||||
_reader: Reader;
|
||||
/** @internal */
|
||||
_disposed = false;
|
||||
/** @internal */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
_openSampleCursors = new Set<SampleCursor<any>>();
|
||||
|
||||
/** True if the input has been disposed. */
|
||||
get disposed() {
|
||||
@@ -181,7 +185,9 @@ export class Input<S extends Source = Source> implements Disposable {
|
||||
this._source._disposed = true;
|
||||
this._source._dispose();
|
||||
|
||||
// TODO this should dispose cursors probably
|
||||
for (const cursor of [...this._openSampleCursors]) {
|
||||
void cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+48
-7
@@ -387,7 +387,7 @@ export class EncodedPacketSink {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DecoderWrapper<
|
||||
export abstract class DecoderWrapper<
|
||||
MediaSample extends VideoSample | AudioSample,
|
||||
> {
|
||||
constructor(
|
||||
@@ -399,6 +399,8 @@ abstract class DecoderWrapper<
|
||||
abstract decode(packet: EncodedPacket): void;
|
||||
abstract flush(): Promise<void>;
|
||||
abstract close(): void;
|
||||
|
||||
abstract get closed(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1749,17 +1751,20 @@ export class CanvasSink {
|
||||
}
|
||||
}
|
||||
|
||||
class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
export class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
decoder: AudioDecoder | null = null;
|
||||
|
||||
customDecoder: CustomAudioDecoder | null = null;
|
||||
customDecoderCallSerializer = new CallSerializer();
|
||||
customDecoderQueueSize = 0;
|
||||
customDecoderClosed = false;
|
||||
|
||||
// Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially
|
||||
// inaccurate) packet timestamps.
|
||||
currentTimestamp: number | null = null;
|
||||
|
||||
onDequeue: (() => unknown) | null = null;
|
||||
|
||||
constructor(
|
||||
onSample: (sample: AudioSample) => unknown,
|
||||
onError: (error: Error) => unknown,
|
||||
@@ -1824,6 +1829,10 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
error: onError,
|
||||
});
|
||||
this.decoder.configure(decoderConfig);
|
||||
|
||||
this.decoder.addEventListener('dequeue', () => {
|
||||
this.onDequeue?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1841,7 +1850,10 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
this.customDecoderQueueSize++;
|
||||
void this.customDecoderCallSerializer
|
||||
.call(() => this.customDecoder!.decode(packet))
|
||||
.then(() => this.customDecoderQueueSize--);
|
||||
.then(() => {
|
||||
this.customDecoderQueueSize--;
|
||||
this.onDequeue?.();
|
||||
});
|
||||
} else {
|
||||
assert(this.decoder);
|
||||
this.decoder.decode(packet.toEncodedAudioChunk());
|
||||
@@ -1859,17 +1871,36 @@ class AudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
|
||||
close() {
|
||||
if (this.customDecoder) {
|
||||
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
||||
if (!this.customDecoderClosed) {
|
||||
this.customDecoderClosed = true;
|
||||
void this.customDecoderCallSerializer.call(() => this.customDecoder!.close());
|
||||
}
|
||||
} else {
|
||||
assert(this.decoder);
|
||||
this.decoder.close();
|
||||
|
||||
if (this.decoder.state !== 'closed') {
|
||||
this.decoder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get closed() {
|
||||
if (this.customDecoder) {
|
||||
if (this.customDecoderClosed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !this.customDecoderCallSerializer.errored;
|
||||
} else {
|
||||
assert(this.decoder);
|
||||
return this.decoder.state === 'closed';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There are a lot of PCM variants not natively supported by the browser and by AudioData. Therefore we need a simple
|
||||
// decoder that maps any input PCM format into a PCM format supported by the browser.
|
||||
class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
export class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
codec: PcmAudioCodec;
|
||||
|
||||
inputSampleSize: 1 | 2 | 3 | 4 | 8;
|
||||
@@ -1883,6 +1914,10 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
// inaccurate) packet timestamps.
|
||||
currentTimestamp: number | null = null;
|
||||
|
||||
isClosed = false;
|
||||
|
||||
onDequeue: (() => unknown) | null = null;
|
||||
|
||||
constructor(
|
||||
onSample: (sample: AudioSample) => unknown,
|
||||
onError: (error: Error) => unknown,
|
||||
@@ -2006,6 +2041,8 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
}
|
||||
|
||||
decode(packet: EncodedPacket) {
|
||||
this.onDequeue?.();
|
||||
|
||||
const inputView = toDataView(packet.data);
|
||||
|
||||
const numberOfFrames = packet.byteLength / this.decoderConfig.numberOfChannels / this.inputSampleSize;
|
||||
@@ -2048,7 +2085,11 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
|
||||
}
|
||||
|
||||
close() {
|
||||
// Do nothing
|
||||
this.isClosed = true;
|
||||
}
|
||||
|
||||
get closed() {
|
||||
return this.isClosed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -835,6 +835,8 @@ export const polyfillSymbolDispose = () => {
|
||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html
|
||||
// @ts-expect-error Readonly
|
||||
Symbol.dispose ??= Symbol('Symbol.dispose');
|
||||
// @ts-expect-error Readonly
|
||||
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose');
|
||||
};
|
||||
|
||||
export const isNumber = (x: unknown) => {
|
||||
|
||||
@@ -921,6 +921,9 @@ export type AudioSampleCopyToOptions = {
|
||||
* @public
|
||||
*/
|
||||
export class AudioSample implements Disposable {
|
||||
/** @internal */
|
||||
static _openSampleCount = 0;
|
||||
|
||||
/** @internal */
|
||||
_data: AudioData | Uint8Array;
|
||||
/** @internal */
|
||||
@@ -1033,6 +1036,7 @@ export class AudioSample implements Disposable {
|
||||
this._data = dataBuffer;
|
||||
}
|
||||
|
||||
AudioSample._openSampleCount++;
|
||||
finalizationRegistry?.register(this, { type: 'audio', data: this._data }, this);
|
||||
}
|
||||
|
||||
@@ -1275,6 +1279,7 @@ export class AudioSample implements Disposable {
|
||||
}
|
||||
|
||||
this._closed = true;
|
||||
AudioSample._openSampleCount--;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user