mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-28 19:33:51 +02:00
Move PacketReader elsewhere
This commit is contained in:
@@ -19,11 +19,10 @@ import {
|
||||
ResultValue,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import { readBytes, Reader } from '../reader';
|
||||
import { DEFAULT_TRACK_DISPOSITION } from '../metadata';
|
||||
import { FrameHeader, MAX_FRAME_HEADER_SIZE, MIN_FRAME_HEADER_SIZE, readFrameHeader } from './adts-reader';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
const SAMPLES_PER_AAC_FRAME = 1024;
|
||||
|
||||
|
||||
+7
-245
@@ -8,7 +8,6 @@
|
||||
|
||||
import { PCM_AUDIO_CODECS } from './codec';
|
||||
import { AudioDecoderWrapper, DecoderWrapper, PcmAudioDecoderWrapper, VideoDecoderWrapper } from './decode';
|
||||
import { InputDisposedError } from './input';
|
||||
import { InputAudioTrack, InputTrack, InputVideoTrack } from './input-track';
|
||||
import {
|
||||
assert,
|
||||
@@ -24,230 +23,19 @@ import {
|
||||
ResultValue,
|
||||
Rotation,
|
||||
AsyncGate,
|
||||
isNumber,
|
||||
MaybeRelevantPromise,
|
||||
} from './misc';
|
||||
import { EncodedPacket } from './packet';
|
||||
import {
|
||||
EncodedPacket,
|
||||
PacketReader,
|
||||
PacketRetrievalOptions,
|
||||
validatePacketRetrievalOptions,
|
||||
validateTimestamp,
|
||||
} from './packet';
|
||||
import { AudioSample, clampCropRectangle, CropRectangle, validateCropRectangle, VideoSample } from './sample';
|
||||
|
||||
polyfillSymbolDispose();
|
||||
|
||||
/**
|
||||
* Additional options for controlling packet retrieval.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type PacketRetrievalOptions = {
|
||||
/**
|
||||
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
|
||||
* be loaded.
|
||||
*/
|
||||
metadataOnly?: boolean;
|
||||
|
||||
/**
|
||||
* When set to true, key packets will be verified upon retrieval by looking into the packet's bitstream.
|
||||
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
|
||||
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
|
||||
* its data, this option cannot be enabled together with `metadataOnly`.
|
||||
*/
|
||||
verifyKeyPackets?: boolean;
|
||||
};
|
||||
|
||||
export const validatePacketRetrievalOptions = (options: PacketRetrievalOptions) => {
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object.');
|
||||
}
|
||||
if (options.metadataOnly !== undefined && typeof options.metadataOnly !== 'boolean') {
|
||||
throw new TypeError('options.metadataOnly, when defined, must be a boolean.');
|
||||
}
|
||||
if (options.verifyKeyPackets !== undefined && typeof options.verifyKeyPackets !== 'boolean') {
|
||||
throw new TypeError('options.verifyKeyPackets, when defined, must be a boolean.');
|
||||
}
|
||||
if (options.verifyKeyPackets && options.metadataOnly) {
|
||||
throw new TypeError('options.verifyKeyPackets and options.metadataOnly cannot be enabled together.');
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTimestamp = (timestamp: number) => {
|
||||
if (!isNumber(timestamp)) {
|
||||
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
|
||||
}
|
||||
};
|
||||
|
||||
export class PacketReader<T extends InputTrack = InputTrack> {
|
||||
track: T;
|
||||
|
||||
constructor(track: T) {
|
||||
if (!(track instanceof InputTrack)) {
|
||||
throw new TypeError('track must be an InputTrack.');
|
||||
}
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
private _maybeVerifyPacketType(
|
||||
packet: EncodedPacket | null,
|
||||
options: PacketRetrievalOptions,
|
||||
): MaybePromise<EncodedPacket | null> {
|
||||
if (!options.verifyKeyPackets || !packet || packet.type === 'delta') {
|
||||
return packet;
|
||||
}
|
||||
|
||||
return this.track.determinePacketType(packet).then((determinedType) => {
|
||||
if (determinedType) {
|
||||
// @ts-expect-error Technically readonly
|
||||
packet.type = determinedType;
|
||||
}
|
||||
|
||||
return packet;
|
||||
});
|
||||
}
|
||||
|
||||
getFirst(options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getFirstPacket(result, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validateTimestamp(timestamp);
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getPacket(result, timestamp, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getKeyAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validateTimestamp(timestamp);
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
if (options.verifyKeyPackets) {
|
||||
return this._readKeyAtVerified(timestamp, options);
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => result.value);
|
||||
} else {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
private async _readKeyAtVerified(
|
||||
timestamp: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
|
||||
if (result.pending) await promise;
|
||||
|
||||
const packet = result.value;
|
||||
if (!packet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const determinedType = await this.track.determinePacketType(packet);
|
||||
if (determinedType === 'delta') {
|
||||
// Try returning the previous key packet (in hopes that it's actually a key packet)
|
||||
return this._readKeyAtVerified(packet.timestamp - 1 / this.track.timeResolution, options);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
getNext(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
if (!(from instanceof EncodedPacket)) {
|
||||
throw new TypeError('from must be an EncodedPacket.');
|
||||
}
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextPacket(result, from, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getNextKey(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
if (!(from instanceof EncodedPacket)) {
|
||||
throw new TypeError('from must be an EncodedPacket.');
|
||||
}
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
if (options.verifyKeyPackets) {
|
||||
return this._getNextKeyVerified(from, options);
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextKeyPacket(result, from, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => result.value);
|
||||
} else {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
private async _getNextKeyVerified(
|
||||
from: EncodedPacket,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextKeyPacket(result, from, options);
|
||||
if (result.pending) await promise;
|
||||
|
||||
const nextPacket = result.value;
|
||||
if (!nextPacket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const determinedType = await this.track.determinePacketType(nextPacket);
|
||||
if (determinedType === 'delta') {
|
||||
// Try returning the next key packet (in hopes that it's actually a key packet)
|
||||
return this._getNextKeyVerified(nextPacket, options);
|
||||
}
|
||||
|
||||
return nextPacket;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketCursor<T extends InputTrack = InputTrack> {
|
||||
track: T;
|
||||
current: EncodedPacket | null = null;
|
||||
@@ -1640,29 +1428,3 @@ export const canvasTransformer = (
|
||||
return new WrappedCanvas(canvas, sample.timestamp, sample.duration);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* An AudioBuffer with additional timing information (timestamp & duration).
|
||||
* @public
|
||||
*/
|
||||
export type WrappedAudioBuffer = {
|
||||
/** An AudioBuffer. */
|
||||
buffer: AudioBuffer;
|
||||
/** The timestamp of the corresponding audio sample, in seconds. */
|
||||
timestamp: number;
|
||||
/** The duration of the corresponding audio sample, in seconds. */
|
||||
duration: number;
|
||||
};
|
||||
|
||||
export const audioBufferTransformer = (): SampleTransformer<AudioSample, WrappedAudioBuffer> => {
|
||||
return (sample) => {
|
||||
const result: WrappedAudioBuffer = {
|
||||
buffer: sample.toAudioBuffer(),
|
||||
timestamp: sample.timestamp,
|
||||
duration: sample.duration,
|
||||
};
|
||||
|
||||
sample.close();
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
textDecoder,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import {
|
||||
FileSlice,
|
||||
readBytes,
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
readSampleRate,
|
||||
getSampleRateOrUncommon,
|
||||
} from './flac-misc';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
type FlacAudioInfo = {
|
||||
numberOfChannels: number;
|
||||
|
||||
+1
-1
@@ -161,6 +161,7 @@ export {
|
||||
EncodedPacket,
|
||||
EncodedPacketSideData,
|
||||
PacketType,
|
||||
PacketReader,
|
||||
} from './packet';
|
||||
export {
|
||||
AudioSample,
|
||||
@@ -174,7 +175,6 @@ export {
|
||||
VIDEO_SAMPLE_PIXEL_FORMATS,
|
||||
} from './sample';
|
||||
export {
|
||||
PacketReader,
|
||||
PacketCursor,
|
||||
SampleCursor,
|
||||
VideoSampleCursor,
|
||||
|
||||
+2
-2
@@ -12,9 +12,9 @@ import { customAudioDecoders, customVideoDecoders } from './custom-coder';
|
||||
import { Input } from './input';
|
||||
import { assert, MaybeRelevantPromise, ResultValue, Rotation } from './misc';
|
||||
import { TrackType } from './output';
|
||||
import { EncodedPacket, PacketType } from './packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PacketType } from './packet';
|
||||
import { TrackDisposition } from './metadata';
|
||||
import { PacketCursor, PacketRetrievalOptions } from './cursors';
|
||||
import { PacketCursor } from './cursors';
|
||||
|
||||
/**
|
||||
* Contains aggregate statistics about the encoded packets of a track.
|
||||
|
||||
@@ -60,7 +60,7 @@ import {
|
||||
ResultValue,
|
||||
MaybeRelevantPromise,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import { buildIsobmffMimeType } from './isobmff-misc';
|
||||
import {
|
||||
MAX_BOX_HEADER_SIZE,
|
||||
@@ -88,7 +88,6 @@ import {
|
||||
readAscii,
|
||||
} from '../reader';
|
||||
import { DEFAULT_TRACK_DISPOSITION, MetadataTags, RichImageData, TrackDisposition } from '../metadata';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
type InternalTrack = {
|
||||
id: number;
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, EncodedPacketSideData, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, EncodedPacketSideData, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import {
|
||||
assertDefinedSize,
|
||||
CODEC_STRING_MAP,
|
||||
@@ -70,7 +70,6 @@ import {
|
||||
} from './ebml';
|
||||
import { buildMatroskaMimeType } from './matroska-misc';
|
||||
import { FileSlice, readBytes, Reader, readI16Be, readU8 } from '../reader';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
type Segment = {
|
||||
seekHeadSeen: boolean;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
ResultValue,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import { FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
|
||||
import {
|
||||
ID3_V1_TAG_SIZE,
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
} from '../id3';
|
||||
import { readNextFrameHeader } from './mp3-reader';
|
||||
import { readAscii, readBytes, Reader, readU32Be } from '../reader';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
type Sample = {
|
||||
timestamp: number;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { OPUS_SAMPLE_RATE } from '../codec';
|
||||
import { parseModesFromVorbisSetupPacket, parseOpusIdentificationHeader, readVorbisComments } from '../codec-data';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
import { Demuxer } from '../demuxer';
|
||||
import { Input } from '../input';
|
||||
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
toDataView,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import { readBytes, Reader } from '../reader';
|
||||
import { buildOggMimeType, computeOggPageCrc, extractSampleMetadata, OggCodecInfo } from './ogg-misc';
|
||||
import {
|
||||
|
||||
+219
-1
@@ -6,7 +6,9 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { SECOND_TO_MICROSECOND_FACTOR } from './misc';
|
||||
import { InputDisposedError } from './input';
|
||||
import { InputTrack } from './input-track';
|
||||
import { isNumber, MaybePromise, ResultValue, SECOND_TO_MICROSECOND_FACTOR } from './misc';
|
||||
|
||||
export const PLACEHOLDER_DATA = /* #__PURE__ */ new Uint8Array(0);
|
||||
|
||||
@@ -267,3 +269,219 @@ export class EncodedPacket {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional options for controlling packet retrieval.
|
||||
* @group Media sinks
|
||||
* @public
|
||||
*/
|
||||
export type PacketRetrievalOptions = {
|
||||
/**
|
||||
* When set to `true`, only packet metadata (like timestamp) will be retrieved - the actual packet data will not
|
||||
* be loaded.
|
||||
*/
|
||||
metadataOnly?: boolean;
|
||||
|
||||
/**
|
||||
* When set to true, key packets will be verified upon retrieval by looking into the packet's bitstream.
|
||||
* If not enabled, the packet types will be determined solely by what's stored in the containing file and may be
|
||||
* incorrect, potentially leading to decoder errors. Since determining a packet's actual type requires looking into
|
||||
* its data, this option cannot be enabled together with `metadataOnly`.
|
||||
*/
|
||||
verifyKeyPackets?: boolean;
|
||||
};
|
||||
|
||||
export const validatePacketRetrievalOptions = (options: PacketRetrievalOptions) => {
|
||||
if (!options || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object.');
|
||||
}
|
||||
if (options.metadataOnly !== undefined && typeof options.metadataOnly !== 'boolean') {
|
||||
throw new TypeError('options.metadataOnly, when defined, must be a boolean.');
|
||||
}
|
||||
if (options.verifyKeyPackets !== undefined && typeof options.verifyKeyPackets !== 'boolean') {
|
||||
throw new TypeError('options.verifyKeyPackets, when defined, must be a boolean.');
|
||||
}
|
||||
if (options.verifyKeyPackets && options.metadataOnly) {
|
||||
throw new TypeError('options.verifyKeyPackets and options.metadataOnly cannot be enabled together.');
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTimestamp = (timestamp: number) => {
|
||||
if (!isNumber(timestamp)) {
|
||||
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
|
||||
}
|
||||
};
|
||||
|
||||
export class PacketReader<T extends InputTrack = InputTrack> {
|
||||
track: T;
|
||||
|
||||
constructor(track: T) {
|
||||
if (!(track instanceof InputTrack)) {
|
||||
throw new TypeError('track must be an InputTrack.');
|
||||
}
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
private _maybeVerifyPacketType(
|
||||
packet: EncodedPacket | null,
|
||||
options: PacketRetrievalOptions,
|
||||
): MaybePromise<EncodedPacket | null> {
|
||||
if (!options.verifyKeyPackets || !packet || packet.type === 'delta') {
|
||||
return packet;
|
||||
}
|
||||
|
||||
return this.track.determinePacketType(packet).then((determinedType) => {
|
||||
if (determinedType) {
|
||||
// @ts-expect-error Technically readonly
|
||||
packet.type = determinedType;
|
||||
}
|
||||
|
||||
return packet;
|
||||
});
|
||||
}
|
||||
|
||||
getFirst(options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getFirstPacket(result, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validateTimestamp(timestamp);
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getPacket(result, timestamp, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getKeyAt(timestamp: number, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
validateTimestamp(timestamp);
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
if (options.verifyKeyPackets) {
|
||||
return this._readKeyAtVerified(timestamp, options);
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => result.value);
|
||||
} else {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
private async _readKeyAtVerified(
|
||||
timestamp: number,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getKeyPacket(result, timestamp, options);
|
||||
if (result.pending) await promise;
|
||||
|
||||
const packet = result.value;
|
||||
if (!packet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const determinedType = await this.track.determinePacketType(packet);
|
||||
if (determinedType === 'delta') {
|
||||
// Try returning the previous key packet (in hopes that it's actually a key packet)
|
||||
return this._readKeyAtVerified(packet.timestamp - 1 / this.track.timeResolution, options);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
getNext(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
if (!(from instanceof EncodedPacket)) {
|
||||
throw new TypeError('from must be an EncodedPacket.');
|
||||
}
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextPacket(result, from, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => this._maybeVerifyPacketType(result.value, options));
|
||||
} else {
|
||||
return this._maybeVerifyPacketType(result.value, options);
|
||||
}
|
||||
}
|
||||
|
||||
getNextKey(from: EncodedPacket, options: PacketRetrievalOptions = {}): MaybePromise<EncodedPacket | null> {
|
||||
if (!(from instanceof EncodedPacket)) {
|
||||
throw new TypeError('from must be an EncodedPacket.');
|
||||
}
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
if (this.track.input._disposed) {
|
||||
throw new InputDisposedError();
|
||||
}
|
||||
|
||||
if (options.verifyKeyPackets) {
|
||||
return this._getNextKeyVerified(from, options);
|
||||
}
|
||||
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextKeyPacket(result, from, options);
|
||||
|
||||
if (result.pending) {
|
||||
return promise.then(() => result.value);
|
||||
} else {
|
||||
return result.value;
|
||||
}
|
||||
}
|
||||
|
||||
private async _getNextKeyVerified(
|
||||
from: EncodedPacket,
|
||||
options: PacketRetrievalOptions,
|
||||
): Promise<EncodedPacket | null> {
|
||||
const result = new ResultValue<EncodedPacket | null>();
|
||||
const promise = this.track._backing.getNextKeyPacket(result, from, options);
|
||||
if (result.pending) await promise;
|
||||
|
||||
const nextPacket = result.value;
|
||||
if (!nextPacket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const determinedType = await this.track.determinePacketType(nextPacket);
|
||||
if (determinedType === 'delta') {
|
||||
// Try returning the next key packet (in hopes that it's actually a key packet)
|
||||
return this._getNextKeyVerified(nextPacket, options);
|
||||
}
|
||||
|
||||
return nextPacket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@ import { Input } from '../input';
|
||||
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
|
||||
import { assert, MaybeRelevantPromise, ResultValue, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { EncodedPacket, PacketRetrievalOptions, PLACEHOLDER_DATA } from '../packet';
|
||||
import { readAscii, readBytes, Reader, readU16, readU32, readU64 } from '../reader';
|
||||
import { parseId3V2Tag, readId3V2Header } from '../id3';
|
||||
import { PacketRetrievalOptions } from '../cursors';
|
||||
|
||||
export enum WaveFormat {
|
||||
PCM = 0x0001,
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Input } from '../../src/input.js';
|
||||
import { BufferSource, UrlSource } from '../../src/source.js';
|
||||
import { ALL_FORMATS } from '../../src/input-format.js';
|
||||
import {
|
||||
audioBufferTransformer,
|
||||
AudioSampleCursor,
|
||||
canvasTransformer,
|
||||
VideoSampleCursor,
|
||||
@@ -1115,33 +1114,6 @@ test('Canvas transformer', async () => {
|
||||
await cursor2.close();
|
||||
});
|
||||
|
||||
test('AudioBuffer transformer', async () => {
|
||||
using input = new Input({
|
||||
source: new UrlSource('/trim-buck-bunny.mov'),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTrack = (await input.getPrimaryAudioTrack())!;
|
||||
|
||||
const cursor = new AudioSampleCursor(audioTrack, {
|
||||
transform: audioBufferTransformer(),
|
||||
});
|
||||
|
||||
const firstSample = (await cursor.seekToFirst())!;
|
||||
expect(firstSample.buffer).toBeInstanceOf(AudioBuffer);
|
||||
expect(firstSample.timestamp).toBe(0);
|
||||
expect(firstSample.duration).toBeGreaterThan(0);
|
||||
expect(firstSample.buffer.duration).toBe(firstSample.duration);
|
||||
|
||||
const nextSample = (await cursor.next())!;
|
||||
expect(nextSample.buffer).toBeInstanceOf(AudioBuffer);
|
||||
expect(nextSample.timestamp).toBeGreaterThan(firstSample.timestamp);
|
||||
expect(nextSample.buffer).not.toBe(firstSample.buffer);
|
||||
expect(nextSample.buffer.duration).toBe(nextSample.duration);
|
||||
|
||||
await cursor.close();
|
||||
});
|
||||
|
||||
test('Unthrottled decoder', async () => {
|
||||
// While the decoder is throttled in practice, these tests simulate what happens when the decoder naturally reaches
|
||||
// the end of the encodable packets without having been stopped; in this case, we still expect the cursor to keep
|
||||
|
||||
@@ -9,7 +9,8 @@ import { CanvasSource, VideoSampleSource } from '../../src/media-source.js';
|
||||
import { canEncodeVideo, QUALITY_HIGH } from '../../src/encode.js';
|
||||
import { VideoSample } from '../../src/sample.js';
|
||||
import { Conversion } from '../../src/conversion.js';
|
||||
import { canvasTransformer, PacketCursor, PacketReader, VideoSampleCursor } from '../../src/cursors.js';
|
||||
import { canvasTransformer, PacketCursor, VideoSampleCursor } from '../../src/cursors.js';
|
||||
import { PacketReader } from '../../src/packet.js';
|
||||
|
||||
test('Can decode transparent video', async () => {
|
||||
using input = new Input({
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Mp4OutputFormat } from '../../src/output-format.js';
|
||||
import { BufferTarget } from '../../src/target.js';
|
||||
import { Conversion } from '../../src/conversion.js';
|
||||
import { extractAvcNalUnits } from '../../src/codec-data.js';
|
||||
import { PacketReader } from '../../src/cursors.js';
|
||||
import { PacketReader } from '../../src/packet.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ import { BufferSource, FilePathSource } from '../../src/source.js';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { ADTS, ALL_FORMATS, FLAC, MP3, MP4, OGG, QTFF, WAVE, WEBM } from '../../src/input-format.js';
|
||||
import { PacketReader } from '../../src/cursors.js';
|
||||
import { InputAudioTrack, InputTrack } from '../../src/input-track.js';
|
||||
import { assert } from '../../src/misc.js';
|
||||
import { EncodedPacket } from '../../src/packet.js';
|
||||
import { EncodedPacket, PacketReader } from '../../src/packet.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import { Output } from '../../src/output.js';
|
||||
import { BufferTarget } from '../../src/target.js';
|
||||
import { FlacOutputFormat } from '../../src/output-format.js';
|
||||
import { Conversion } from '../../src/conversion.js';
|
||||
import { PacketCursor, PacketReader } from '../../src/cursors.js';
|
||||
import { PacketCursor } from '../../src/cursors.js';
|
||||
import { PacketReader } from '../../src/packet.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { BufferSource, FilePathSource } from '../../src/source.js';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { ALL_FORMATS } from '../../src/input-format.js';
|
||||
import { PacketCursor, PacketReader } from '../../src/cursors.js';
|
||||
import { PacketCursor } from '../../src/cursors.js';
|
||||
import { promiseAllEnsureOrder } from '../../src/misc.js';
|
||||
import { PacketReader } from '../../src/packet.js';
|
||||
|
||||
const __dirname = new URL('.', import.meta.url).pathname;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user