Add explicit disposability to Inputs (#126)

This commit is contained in:
Vanilagy
2025-09-21 20:18:59 +02:00
parent 34c01fbb10
commit 608c769a7e
22 changed files with 479 additions and 116 deletions
+81 -32
View File
@@ -9,6 +9,7 @@
import { parsePcmCodec, PCM_AUDIO_CODECS, PcmAudioCodec, VideoCodec, AudioCodec } from './codec';
import { extractHevcNalUnits, extractNalUnitTypeForHevc, HevcNalUnitType } from './codec-data';
import { CustomVideoDecoder, customVideoDecoders, CustomAudioDecoder, customAudioDecoders } from './custom-coder';
import { InputDisposedError } from './input';
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
import {
AnyIterable,
@@ -123,6 +124,10 @@ export class EncodedPacketSink {
getFirstPacket(options: PacketRetrievalOptions = {}) {
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
return maybeFixPacketType(this._track, this._track._backing.getFirstPacket(options), options);
}
@@ -138,6 +143,10 @@ export class EncodedPacketSink {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
return maybeFixPacketType(this._track, this._track._backing.getPacket(timestamp, options), options);
}
@@ -151,6 +160,10 @@ export class EncodedPacketSink {
}
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
return maybeFixPacketType(this._track, this._track._backing.getNextPacket(packet, options), options);
}
@@ -169,6 +182,10 @@ export class EncodedPacketSink {
validateTimestamp(timestamp);
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
if (!options.verifyKeyPackets) {
return this._track._backing.getKeyPacket(timestamp, options);
}
@@ -199,6 +216,10 @@ export class EncodedPacketSink {
}
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
if (!options.verifyKeyPackets) {
return this._track._backing.getNextKeyPacket(packet, options);
}
@@ -240,6 +261,10 @@ export class EncodedPacketSink {
}
validatePacketRetrievalOptions(options);
if (this._track.input._disposed) {
throw new InputDisposedError();
}
const packetQueue: EncodedPacket[] = [];
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
@@ -260,7 +285,7 @@ export class EncodedPacketSink {
(async () => {
let packet = startPacket ?? await this.getFirstPacket(options);
while (packet && !terminated) {
while (packet && !terminated && !this._track.input._disposed) {
if (endPacket && packet.sequenceNumber >= endPacket?.sequenceNumber) {
break;
}
@@ -288,10 +313,14 @@ export class EncodedPacketSink {
}
});
const track = this._track;
return {
async next() {
while (true) {
if (terminated) {
if (track.input._disposed) {
throw new InputDisposedError();
} else if (terminated) {
return { value: undefined, done: true };
} else if (outOfBandError) {
throw outOfBandError;
@@ -353,6 +382,9 @@ abstract class DecoderWrapper<
export abstract class BaseMediaSampleSink<
MediaSample extends VideoSample | AudioSample,
> {
/** @internal */
abstract _track: InputTrack;
/** @internal */
abstract _createDecoder(
onSample: (sample: MediaSample) => unknown,
@@ -459,7 +491,7 @@ export abstract class BaseMediaSampleSink<
const packets = packetSink.packets(keyPacket, endPacket);
await packets.next(); // Skip the start packet as we already have it
while (currentPacket && !ended) {
while (currentPacket && !ended && !this._track.input._disposed) {
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
if (sampleQueue.length + decoder.getDecodeQueueSize() > maxQueueSize) {
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
@@ -479,7 +511,9 @@ export abstract class BaseMediaSampleSink<
await packets.return();
if (!terminated) await decoder.flush();
if (!terminated && !this._track.input._disposed) {
await decoder.flush();
}
decoder.close();
if (!firstSampleQueued && lastSample) {
@@ -495,12 +529,24 @@ export abstract class BaseMediaSampleSink<
}
});
const track = this._track;
const closeSamples = () => {
lastSample?.close();
for (const sample of sampleQueue) {
sample.close();
}
};
return {
async next() {
while (true) {
if (terminated) {
if (track.input._disposed) {
closeSamples();
throw new InputDisposedError();
} else if (terminated) {
return { value: undefined, done: true };
} else if (outOfBandError) {
closeSamples();
throw outOfBandError;
} else if (sampleQueue.length > 0) {
const value = sampleQueue.shift()!;
@@ -518,12 +564,7 @@ export abstract class BaseMediaSampleSink<
ended = true;
onQueueDequeue();
onQueueNotEmpty();
lastSample?.close();
for (const sample of sampleQueue) {
sample.close();
}
closeSamples();
return { value: undefined, done: true };
},
@@ -647,7 +688,7 @@ export abstract class BaseMediaSampleSink<
for await (const timestamp of timestampIterator) {
validateTimestamp(timestamp);
if (terminated) {
if (terminated || this._track.input._disposed) {
break;
}
@@ -684,7 +725,7 @@ export abstract class BaseMediaSampleSink<
lastKeyPacket = keyPacket;
}
if (!terminated) {
if (!terminated && !this._track.input._disposed) {
if (maxSequenceNumber !== -1) {
// We still need to decode packets
await decodePackets();
@@ -703,12 +744,23 @@ export abstract class BaseMediaSampleSink<
}
});
const track = this._track;
const closeSamples = () => {
for (const sample of sampleQueue) {
sample?.close();
}
};
return {
async next() {
while (true) {
if (terminated) {
if (track.input._disposed) {
closeSamples();
throw new InputDisposedError();
} else if (terminated) {
return { value: undefined, done: true };
} else if (outOfBandError) {
closeSamples();
throw outOfBandError;
} else if (sampleQueue.length > 0) {
const value = sampleQueue.shift();
@@ -726,10 +778,7 @@ export abstract class BaseMediaSampleSink<
terminated = true;
onQueueDequeue();
onQueueNotEmpty();
for (const sample of sampleQueue) {
sample?.close();
}
closeSamples();
return { value: undefined, done: true };
},
@@ -930,7 +979,7 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
*/
export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
/** @internal */
_videoTrack: InputVideoTrack;
_track: InputVideoTrack;
/** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */
constructor(videoTrack: InputVideoTrack) {
@@ -940,7 +989,7 @@ export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
super();
this._videoTrack = videoTrack;
this._track = videoTrack;
}
/** @internal */
@@ -948,17 +997,17 @@ export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
onSample: (sample: VideoSample) => unknown,
onError: (error: DOMException) => unknown,
) {
if (!(await this._videoTrack.canDecode())) {
if (!(await this._track.canDecode())) {
throw new Error(
'This video track cannot be decoded by this browser. Make sure to check decodability before using'
+ ' a track.',
);
}
const codec = this._videoTrack.codec;
const rotation = this._videoTrack.rotation;
const decoderConfig = await this._videoTrack.getDecoderConfig();
const timeResolution = this._videoTrack.timeResolution;
const codec = this._track.codec;
const rotation = this._track.rotation;
const decoderConfig = await this._track.getDecoderConfig();
const timeResolution = this._track.timeResolution;
assert(codec && decoderConfig);
return new VideoDecoderWrapper(onSample, onError, codec, decoderConfig, rotation, timeResolution);
@@ -966,7 +1015,7 @@ export class VideoSampleSink extends BaseMediaSampleSink<VideoSample> {
/** @internal */
_createPacketSink() {
return new EncodedPacketSink(this._videoTrack);
return new EncodedPacketSink(this._track);
}
/**
@@ -1580,7 +1629,7 @@ class PcmAudioDecoderWrapper extends DecoderWrapper<AudioSample> {
*/
export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
/** @internal */
_audioTrack: InputAudioTrack;
_track: InputAudioTrack;
/** Creates a new {@link AudioSampleSink} for the given {@link InputAudioTrack}. */
constructor(audioTrack: InputAudioTrack) {
@@ -1590,7 +1639,7 @@ export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
super();
this._audioTrack = audioTrack;
this._track = audioTrack;
}
/** @internal */
@@ -1598,15 +1647,15 @@ export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
onSample: (sample: AudioSample) => unknown,
onError: (error: DOMException) => unknown,
) {
if (!(await this._audioTrack.canDecode())) {
if (!(await this._track.canDecode())) {
throw new Error(
'This audio track cannot be decoded by this browser. Make sure to check decodability before using'
+ ' a track.',
);
}
const codec = this._audioTrack.codec;
const decoderConfig = await this._audioTrack.getDecoderConfig();
const codec = this._track.codec;
const decoderConfig = await this._track.getDecoderConfig();
assert(codec && decoderConfig);
if ((PCM_AUDIO_CODECS as readonly string[]).includes(decoderConfig.codec)) {
@@ -1618,7 +1667,7 @@ export class AudioSampleSink extends BaseMediaSampleSink<AudioSample> {
/** @internal */
_createPacketSink() {
return new EncodedPacketSink(this._audioTrack);
return new EncodedPacketSink(this._track);
}
/**