Drain -> Sink

This commit is contained in:
David Payr
2025-01-08 14:26:49 +01:00
parent dc81afa1d9
commit b6b82084d4
7 changed files with 56 additions and 56 deletions
+4 -4
View File
@@ -55,7 +55,7 @@
const audioTrack = await input.getPrimaryAudioTrack(); const audioTrack = await input.getPrimaryAudioTrack();
const videoTrack = await input.getPrimaryVideoTrack(); const videoTrack = await input.getPrimaryVideoTrack();
const drain = new Metamuxer.EncodedAudioSampleDrain(audioTrack); const sink = new Metamuxer.EncodedAudioSampleSink(audioTrack);
const decoderConfig = await audioTrack.getDecoderConfig(); const decoderConfig = await audioTrack.getDecoderConfig();
const sampleSource = new Metamuxer.EncodedAudioSampleSource(await audioTrack.getCodec()); const sampleSource = new Metamuxer.EncodedAudioSampleSource(await audioTrack.getCodec());
@@ -72,7 +72,7 @@
/* /*
for await (const sample of drain.samples()) { for await (const sample of sink.samples()) {
//console.log(sample.timestamp); //console.log(sample.timestamp);
//sample.timestamp *= 2; //sample.timestamp *= 2;
//console.log(sample); //console.log(sample);
@@ -80,13 +80,13 @@
await sampleSource.digest(sample, { decoderConfig }); await sampleSource.digest(sample, { decoderConfig });
} }
*/ */
for await (const { data, timestamp } of new Metamuxer.AudioDataDrain(audioTrack).data()) { for await (const { data, timestamp } of new Metamuxer.AudioDataSink(audioTrack).data()) {
//console.log("wow", data.timestamp); //console.log("wow", data.timestamp);
//console.log(sample) //console.log(sample)
await audioDataSource.digest(data); await audioDataSource.digest(data);
} }
/* /*
for await (const sample of new Metamuxer.EncodedVideoSampleDrain(videoTrack).samples()) { for await (const sample of new Metamuxer.EncodedVideoSampleSink(videoTrack).samples()) {
//console.log(sample) //console.log(sample)
await videoSampleSource.digest(sample, { decoderConfig: videoDecoderConfig }); await videoSampleSource.digest(sample, { decoderConfig: videoDecoderConfig });
} }
+4 -4
View File
@@ -68,8 +68,8 @@ durationElement.textContent = formatSeconds(totalDuration);
playerDiv.style.display = 'flex'; playerDiv.style.display = 'flex';
const videoDrain = videoTrack && new Metamuxer.CanvasDrain(videoTrack); const videoSink = videoTrack && new Metamuxer.CanvasSink(videoTrack);
const audioDrain = audioTrack && new Metamuxer.AudioBufferDrain(audioTrack); const audioSink = audioTrack && new Metamuxer.AudioBufferSink(audioTrack);
let startTime = null; let startTime = null;
let playing = false; let playing = false;
@@ -92,7 +92,7 @@ function getPlaybackTime() {
function play() { function play() {
audioBufferIterator?.return(); audioBufferIterator?.return();
audioBufferIterator = audioDrain?.buffers(getPlaybackTime()); audioBufferIterator = audioSink?.buffers(getPlaybackTime());
startTime = audioContext.currentTime; startTime = audioContext.currentTime;
playing = true; playing = true;
runAudioIterator(); runAudioIterator();
@@ -135,7 +135,7 @@ async function seek() {
await videoFrameIterator?.return(); await videoFrameIterator?.return();
videoFrameIterator = videoDrain.canvases(getPlaybackTime()); videoFrameIterator = videoSink.canvases(getPlaybackTime());
const newCurrentFrame = (await videoFrameIterator.next()).value; const newCurrentFrame = (await videoFrameIterator.next()).value;
const newNextFrame = (await videoFrameIterator.next()).value; const newNextFrame = (await videoFrameIterator.next()).value;
+9 -9
View File
@@ -71,18 +71,18 @@ export {
} from './sample'; } from './sample';
export { export {
SampleRetrievalOptions, SampleRetrievalOptions,
BaseSampleDrain, BaseSampleSink,
WrappedMediaFrame, WrappedMediaFrame,
BaseMediaFrameDrain, BaseMediaFrameSink,
EncodedVideoSampleDrain, EncodedVideoSampleSink,
VideoFrameDrain, VideoFrameSink,
EncodedAudioSampleDrain, EncodedAudioSampleSink,
CanvasDrain, CanvasSink,
WrappedCanvas, WrappedCanvas,
AudioDataDrain, AudioDataSink,
WrappedAudioData, WrappedAudioData,
AudioBufferDrain, AudioBufferSink,
WrappedAudioBuffer, WrappedAudioBuffer,
} from './media-drain'; } from './media-sink';
// 🐡🦔 // 🐡🦔
+5 -5
View File
@@ -1,5 +1,5 @@
import { AudioCodec, MediaCodec, VideoCodec } from './codec'; import { AudioCodec, MediaCodec, VideoCodec } from './codec';
import { EncodedAudioSampleDrain, EncodedVideoSampleDrain, SampleRetrievalOptions } from './media-drain'; import { EncodedAudioSampleSink, EncodedVideoSampleSink, SampleRetrievalOptions } from './media-sink';
import { Rotation } from './misc'; import { Rotation } from './misc';
import { EncodedAudioSample, EncodedVideoSample } from './sample'; import { EncodedAudioSample, EncodedVideoSample } from './sample';
@@ -116,7 +116,7 @@ export class InputVideoTrack extends InputTrack {
} }
computeSampleStats() { computeSampleStats() {
return computeSampleStats(new EncodedVideoSampleDrain(this)); return computeSampleStats(new EncodedVideoSampleSink(this));
} }
} }
@@ -185,7 +185,7 @@ export class InputAudioTrack extends InputTrack {
} }
computeSampleStats() { computeSampleStats() {
return computeSampleStats(new EncodedAudioSampleDrain(this)); return computeSampleStats(new EncodedAudioSampleSink(this));
} }
} }
@@ -196,13 +196,13 @@ export type SampleStats = {
averageBitrate: number; averageBitrate: number;
}; };
const computeSampleStats = async (drain: EncodedVideoSampleDrain | EncodedAudioSampleDrain): Promise<SampleStats> => { const computeSampleStats = async (sink: EncodedVideoSampleSink | EncodedAudioSampleSink): Promise<SampleStats> => {
let startTimestamp = Infinity; let startTimestamp = Infinity;
let endTimestamp = -Infinity; let endTimestamp = -Infinity;
let sampleCount = 0; let sampleCount = 0;
let totalSampleBytes = 0; let totalSampleBytes = 0;
for await (const sample of drain.samples(undefined, undefined, { metadataOnly: true })) { for await (const sample of sink.samples(undefined, undefined, { metadataOnly: true })) {
startTimestamp = Math.min(startTimestamp, sample.timestamp); startTimestamp = Math.min(startTimestamp, sample.timestamp);
endTimestamp = Math.max(endTimestamp, sample.timestamp + sample.duration); endTimestamp = Math.max(endTimestamp, sample.timestamp + sample.duration);
+1 -1
View File
@@ -20,7 +20,7 @@ import {
InputVideoTrack, InputVideoTrack,
InputVideoTrackBacking, InputVideoTrackBacking,
} from '../input-track'; } from '../input-track';
import { SampleRetrievalOptions } from '../media-drain'; import { SampleRetrievalOptions } from '../media-sink';
import { import {
assert, assert,
COLOR_PRIMARIES_MAP_INVERSE, COLOR_PRIMARIES_MAP_INVERSE,
+32 -32
View File
@@ -35,7 +35,7 @@ const validateTimestamp = (timestamp: number) => {
}; };
/** @public */ /** @public */
export abstract class BaseSampleDrain<Sample extends EncodedVideoSample | EncodedAudioSample> { export abstract class BaseSampleSink<Sample extends EncodedVideoSample | EncodedAudioSample> {
abstract getFirstSample(options?: SampleRetrievalOptions): Promise<Sample | null>; abstract getFirstSample(options?: SampleRetrievalOptions): Promise<Sample | null>;
abstract getSample(timestamp: number, options?: SampleRetrievalOptions): Promise<Sample | null>; abstract getSample(timestamp: number, options?: SampleRetrievalOptions): Promise<Sample | null>;
abstract getNextSample(sample: Sample, options?: SampleRetrievalOptions): Promise<Sample | null>; abstract getNextSample(sample: Sample, options?: SampleRetrievalOptions): Promise<Sample | null>;
@@ -162,7 +162,7 @@ abstract class DecoderWrapper<
} }
/** @public */ /** @public */
export abstract class BaseMediaFrameDrain< export abstract class BaseMediaFrameSink<
Sample extends EncodedVideoSample | EncodedAudioSample, Sample extends EncodedVideoSample | EncodedAudioSample,
MediaFrame extends VideoFrame | AudioData, MediaFrame extends VideoFrame | AudioData,
WrappedFrame extends WrappedMediaFrame<MediaFrame> = WrappedMediaFrame<MediaFrame>, WrappedFrame extends WrappedMediaFrame<MediaFrame> = WrappedMediaFrame<MediaFrame>,
@@ -173,7 +173,7 @@ export abstract class BaseMediaFrameDrain<
onError: (error: DOMException) => unknown onError: (error: DOMException) => unknown
): Promise<DecoderWrapper<Sample, MediaFrame>>; ): Promise<DecoderWrapper<Sample, MediaFrame>>;
/** @internal */ /** @internal */
abstract _createSampleDrain(): BaseSampleDrain<Sample>; abstract _createSampleSink(): BaseSampleSink<Sample>;
/** @internal */ /** @internal */
private _duplicateFrame(frame: WrappedFrame) { private _duplicateFrame(frame: WrappedFrame) {
@@ -248,8 +248,8 @@ export abstract class BaseMediaFrameDrain<
} }
}); });
const sampleDrain = this._createSampleDrain(); const sampleSink = this._createSampleSink();
const keySample = await sampleDrain.getKeySample(startTimestamp) ?? await sampleDrain.getFirstSample(); const keySample = await sampleSink.getKeySample(startTimestamp) ?? await sampleSink.getFirstSample();
if (!keySample) { if (!keySample) {
return; return;
} }
@@ -262,19 +262,19 @@ export abstract class BaseMediaFrameDrain<
// frames (B-frames). Instead, we'll need to keep decoding samples until we get a frame that exceeds // frames (B-frames). Instead, we'll need to keep decoding samples until we get a frame that exceeds
// this end time. However, we can still put a bound on it: Since key frames are by definition never // this end time. However, we can still put a bound on it: Since key frames are by definition never
// out of order, we can stop at the first key frame after the end timestamp. // out of order, we can stop at the first key frame after the end timestamp.
const endSample = await sampleDrain.getSample(endTimestamp); const endSample = await sampleSink.getSample(endTimestamp);
const endKeySample = !endSample const endKeySample = !endSample
? null ? null
: endSample.type === 'key' && endSample.timestamp === endTimestamp : endSample.type === 'key' && endSample.timestamp === endTimestamp
? endSample ? endSample
: await sampleDrain.getNextKeySample(endSample); : await sampleSink.getNextKeySample(endSample);
if (endKeySample) { if (endKeySample) {
samplesEndTimestamp = endKeySample.timestamp; samplesEndTimestamp = endKeySample.timestamp;
} }
} }
const samples = sampleDrain.samples(keySample, samplesEndTimestamp); const samples = sampleSink.samples(keySample, samplesEndTimestamp);
await samples.next(); // Skip the start sample as we already have it await samples.next(); // Skip the start sample as we already have it
while (currentSample && !ended) { while (currentSample && !ended) {
@@ -409,7 +409,7 @@ export abstract class BaseMediaFrameDrain<
} }
}); });
const sampleDrain = this._createSampleDrain(); const sampleSink = this._createSampleSink();
let lastKeySample: Sample | null = null; let lastKeySample: Sample | null = null;
let lastSample: Sample | null = null; let lastSample: Sample | null = null;
@@ -425,13 +425,13 @@ export abstract class BaseMediaFrameDrain<
break; break;
} }
const targetSample = await sampleDrain.getSample(timestamp); const targetSample = await sampleSink.getSample(timestamp);
if (!targetSample) { if (!targetSample) {
pushToQueue(null); pushToQueue(null);
continue; continue;
} }
const keySample = await sampleDrain.getKeySample(timestamp); const keySample = await sampleSink.getKeySample(timestamp);
if (!keySample) { if (!keySample) {
pushToQueue(null); pushToQueue(null);
continue; continue;
@@ -461,7 +461,7 @@ export abstract class BaseMediaFrameDrain<
} }
while (lastSample.timestamp !== targetSample.timestamp) { while (lastSample.timestamp !== targetSample.timestamp) {
const nextSample = await sampleDrain.getNextSample(lastSample); const nextSample = await sampleSink.getNextSample(lastSample);
assert(nextSample); assert(nextSample);
lastSample = nextSample; lastSample = nextSample;
@@ -523,7 +523,7 @@ export abstract class BaseMediaFrameDrain<
} }
/** @public */ /** @public */
export class EncodedVideoSampleDrain extends BaseSampleDrain<EncodedVideoSample> { export class EncodedVideoSampleSink extends BaseSampleSink<EncodedVideoSample> {
/** @internal */ /** @internal */
_videoTrack: InputVideoTrack; _videoTrack: InputVideoTrack;
@@ -620,7 +620,7 @@ class VideoDecoderWrapper extends DecoderWrapper<EncodedVideoSample, VideoFrame>
} }
/** @public */ /** @public */
export class VideoFrameDrain extends BaseMediaFrameDrain<EncodedVideoSample, VideoFrame> { export class VideoFrameSink extends BaseMediaFrameSink<EncodedVideoSample, VideoFrame> {
/** @internal */ /** @internal */
_videoTrack: InputVideoTrack; _videoTrack: InputVideoTrack;
@@ -653,8 +653,8 @@ export class VideoFrameDrain extends BaseMediaFrameDrain<EncodedVideoSample, Vid
} }
/** @internal */ /** @internal */
_createSampleDrain() { _createSampleSink() {
return new EncodedVideoSampleDrain(this._videoTrack); return new EncodedVideoSampleSink(this._videoTrack);
} }
async getFrame(timestamp: number) { async getFrame(timestamp: number) {
@@ -683,13 +683,13 @@ export type WrappedCanvas = {
}; };
/** @public */ /** @public */
export class CanvasDrain { export class CanvasSink {
/** @internal */ /** @internal */
_videoTrack: InputVideoTrack; _videoTrack: InputVideoTrack;
/** @internal */ /** @internal */
_dimensions?: { width: number; height: number }; _dimensions?: { width: number; height: number };
/** @internal */ /** @internal */
_videoFrameDrain: VideoFrameDrain; _videoFrameSink: VideoFrameSink;
constructor(videoTrack: InputVideoTrack, dimensions?: { width: number; height: number }) { constructor(videoTrack: InputVideoTrack, dimensions?: { width: number; height: number }) {
if (!(videoTrack instanceof InputVideoTrack)) { if (!(videoTrack instanceof InputVideoTrack)) {
@@ -707,7 +707,7 @@ export class CanvasDrain {
this._videoTrack = videoTrack; this._videoTrack = videoTrack;
this._dimensions = dimensions; this._dimensions = dimensions;
this._videoFrameDrain = new VideoFrameDrain(videoTrack); this._videoFrameSink = new VideoFrameSink(videoTrack);
} }
/** @internal */ /** @internal */
@@ -746,27 +746,27 @@ export class CanvasDrain {
async getCanvas(timestamp: number) { async getCanvas(timestamp: number) {
validateTimestamp(timestamp); validateTimestamp(timestamp);
const frame = await this._videoFrameDrain.getFrame(timestamp); const frame = await this._videoFrameSink.getFrame(timestamp);
return frame && this._videoFrameToWrappedCanvas(frame); return frame && this._videoFrameToWrappedCanvas(frame);
} }
canvases(startTimestamp = 0, endTimestamp = Infinity) { canvases(startTimestamp = 0, endTimestamp = Infinity) {
return mapAsyncGenerator( return mapAsyncGenerator(
this._videoFrameDrain.frames(startTimestamp, endTimestamp), this._videoFrameSink.frames(startTimestamp, endTimestamp),
frame => this._videoFrameToWrappedCanvas(frame), frame => this._videoFrameToWrappedCanvas(frame),
); );
} }
canvasesAtTimestamps(timestamps: AnyIterable<number>) { canvasesAtTimestamps(timestamps: AnyIterable<number>) {
return mapAsyncGenerator( return mapAsyncGenerator(
this._videoFrameDrain.framesAtTimestamps(timestamps), this._videoFrameSink.framesAtTimestamps(timestamps),
async frame => frame && this._videoFrameToWrappedCanvas(frame), async frame => frame && this._videoFrameToWrappedCanvas(frame),
); );
} }
} }
/** @public */ /** @public */
export class EncodedAudioSampleDrain extends BaseSampleDrain<EncodedAudioSample> { export class EncodedAudioSampleSink extends BaseSampleSink<EncodedAudioSample> {
/** @internal */ /** @internal */
_audioTrack: InputAudioTrack; _audioTrack: InputAudioTrack;
@@ -1023,7 +1023,7 @@ export type WrappedAudioData = {
}; };
/** @public */ /** @public */
export class AudioDataDrain extends BaseMediaFrameDrain<EncodedAudioSample, AudioData> { export class AudioDataSink extends BaseMediaFrameSink<EncodedAudioSample, AudioData> {
/** @internal */ /** @internal */
_audioTrack: InputAudioTrack; _audioTrack: InputAudioTrack;
@@ -1069,8 +1069,8 @@ export class AudioDataDrain extends BaseMediaFrameDrain<EncodedAudioSample, Audi
} }
/** @internal */ /** @internal */
_createSampleDrain() { _createSampleSink() {
return new EncodedAudioSampleDrain(this._audioTrack); return new EncodedAudioSampleSink(this._audioTrack);
} }
async getData(timestamp: number) { async getData(timestamp: number) {
@@ -1105,16 +1105,16 @@ export type WrappedAudioBuffer = {
}; };
/** @public */ /** @public */
export class AudioBufferDrain { export class AudioBufferSink {
/** @internal */ /** @internal */
_audioDataDrain: AudioDataDrain; _audioDataSink: AudioDataSink;
constructor(audioTrack: InputAudioTrack) { constructor(audioTrack: InputAudioTrack) {
if (!(audioTrack instanceof InputAudioTrack)) { if (!(audioTrack instanceof InputAudioTrack)) {
throw new TypeError('audioTrack must be an InputAudioTrack.'); throw new TypeError('audioTrack must be an InputAudioTrack.');
} }
this._audioDataDrain = new AudioDataDrain(audioTrack); this._audioDataSink = new AudioDataSink(audioTrack);
} }
/** @internal */ /** @internal */
@@ -1146,20 +1146,20 @@ export class AudioBufferDrain {
async getBuffer(timestamp: number) { async getBuffer(timestamp: number) {
validateTimestamp(timestamp); validateTimestamp(timestamp);
const data = await this._audioDataDrain.getData(timestamp); const data = await this._audioDataSink.getData(timestamp);
return data && this._audioDataToWrappedArrayBuffer(data); return data && this._audioDataToWrappedArrayBuffer(data);
} }
buffers(startTimestamp = 0, endTimestamp = Infinity) { buffers(startTimestamp = 0, endTimestamp = Infinity) {
return mapAsyncGenerator( return mapAsyncGenerator(
this._audioDataDrain.data(startTimestamp, endTimestamp), this._audioDataSink.data(startTimestamp, endTimestamp),
async data => this._audioDataToWrappedArrayBuffer(data), async data => this._audioDataToWrappedArrayBuffer(data),
); );
} }
buffersAtTimestamps(timestamps: AnyIterable<number>) { buffersAtTimestamps(timestamps: AnyIterable<number>) {
return mapAsyncGenerator( return mapAsyncGenerator(
this._audioDataDrain.dataAtTimestamps(timestamps), this._audioDataSink.dataAtTimestamps(timestamps),
async data => data && this._audioDataToWrappedArrayBuffer(data), async data => data && this._audioDataToWrappedArrayBuffer(data),
); );
} }
+1 -1
View File
@@ -2,7 +2,7 @@ import { AudioCodec } from '../codec';
import { Demuxer } from '../demuxer'; import { Demuxer } from '../demuxer';
import { Input } from '../input'; import { Input } from '../input';
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track'; import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
import { SampleRetrievalOptions } from '../media-drain'; import { SampleRetrievalOptions } from '../media-sink';
import { assert } from '../misc'; import { assert } from '../misc';
import { Reader } from '../reader'; import { Reader } from '../reader';
import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample'; import { EncodedAudioSample, PLACEHOLDER_DATA } from '../sample';