mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-02 21:33:52 +02:00
Add some more argument validation
This commit is contained in:
+2
-2
@@ -48,8 +48,8 @@ let videoRotation = 0;
|
|||||||
if (!videoTrack) {
|
if (!videoTrack) {
|
||||||
canvas.style.display = 'none';
|
canvas.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
canvas.width = await videoTrack.getRotatedWidth();
|
canvas.width = await videoTrack.getDisplayWidth();
|
||||||
canvas.height = await videoTrack.getRotatedHeight();
|
canvas.height = await videoTrack.getDisplayHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalDuration = await input.computeDuration();
|
const totalDuration = await input.computeDuration();
|
||||||
|
|||||||
Vendored
+8
-7
@@ -4,11 +4,6 @@ export declare const ALL_FORMATS: InputFormat[];
|
|||||||
/** @public */
|
/** @public */
|
||||||
export declare type AnyIterable<T> = Iterable<T> | AsyncIterable<T>;
|
export declare type AnyIterable<T> = Iterable<T> | AsyncIterable<T>;
|
||||||
|
|
||||||
/** @public */
|
|
||||||
export declare class ArrayBufferSource extends Source {
|
|
||||||
constructor(buffer: ArrayBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export declare class ArrayBufferTarget extends Target {
|
export declare class ArrayBufferTarget extends Target {
|
||||||
buffer: ArrayBuffer | null;
|
buffer: ArrayBuffer | null;
|
||||||
@@ -83,6 +78,12 @@ export declare class BlobSource extends Source {
|
|||||||
constructor(blob: Blob);
|
constructor(blob: Blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @public */
|
||||||
|
declare class BufferSource_2 extends Source {
|
||||||
|
constructor(buffer: ArrayBuffer | Uint8Array);
|
||||||
|
}
|
||||||
|
export { BufferSource_2 as BufferSource }
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export declare class CanvasDrain {
|
export declare class CanvasDrain {
|
||||||
constructor(videoTrack: InputVideoTrack, dimensions?: {
|
constructor(videoTrack: InputVideoTrack, dimensions?: {
|
||||||
@@ -152,7 +153,7 @@ export declare class Input {
|
|||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export declare class InputAudioTrack extends InputTrack {
|
export declare class InputAudioTrack extends InputTrack {
|
||||||
getCodec(): Promise<"aac" | "opus">;
|
getCodec(): Promise<AudioCodec>;
|
||||||
getNumberOfChannels(): Promise<number>;
|
getNumberOfChannels(): Promise<number>;
|
||||||
getSampleRate(): Promise<number>;
|
getSampleRate(): Promise<number>;
|
||||||
getDecoderConfig(): Promise<AudioDecoderConfig>;
|
getDecoderConfig(): Promise<AudioDecoderConfig>;
|
||||||
@@ -180,7 +181,7 @@ export declare abstract class InputTrack {
|
|||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export declare class InputVideoTrack extends InputTrack {
|
export declare class InputVideoTrack extends InputTrack {
|
||||||
getCodec(): Promise<"avc" | "hevc" | "vp8" | "vp9" | "av1">;
|
getCodec(): Promise<VideoCodec>;
|
||||||
getCodedWidth(): Promise<number>;
|
getCodedWidth(): Promise<number>;
|
||||||
getCodedHeight(): Promise<number>;
|
getCodedHeight(): Promise<number>;
|
||||||
getRotation(): Promise<Rotation>;
|
getRotation(): Promise<Rotation>;
|
||||||
|
|||||||
Vendored
+99
-5
@@ -23,7 +23,6 @@ var Metamuxer = (() => {
|
|||||||
__export(src_exports, {
|
__export(src_exports, {
|
||||||
ALL_FORMATS: () => ALL_FORMATS,
|
ALL_FORMATS: () => ALL_FORMATS,
|
||||||
AUDIO_CODECS: () => AUDIO_CODECS,
|
AUDIO_CODECS: () => AUDIO_CODECS,
|
||||||
ArrayBufferSource: () => ArrayBufferSource,
|
|
||||||
ArrayBufferTarget: () => ArrayBufferTarget,
|
ArrayBufferTarget: () => ArrayBufferTarget,
|
||||||
AudioBufferDrain: () => AudioBufferDrain,
|
AudioBufferDrain: () => AudioBufferDrain,
|
||||||
AudioBufferSource: () => AudioBufferSource,
|
AudioBufferSource: () => AudioBufferSource,
|
||||||
@@ -33,6 +32,7 @@ var Metamuxer = (() => {
|
|||||||
BaseChunkDrain: () => BaseChunkDrain,
|
BaseChunkDrain: () => BaseChunkDrain,
|
||||||
BaseMediaFrameDrain: () => BaseMediaFrameDrain,
|
BaseMediaFrameDrain: () => BaseMediaFrameDrain,
|
||||||
BlobSource: () => BlobSource,
|
BlobSource: () => BlobSource,
|
||||||
|
BufferSource: () => BufferSource,
|
||||||
CanvasDrain: () => CanvasDrain,
|
CanvasDrain: () => CanvasDrain,
|
||||||
CanvasSource: () => CanvasSource,
|
CanvasSource: () => CanvasSource,
|
||||||
EncodedAudioChunkDrain: () => EncodedAudioChunkDrain,
|
EncodedAudioChunkDrain: () => EncodedAudioChunkDrain,
|
||||||
@@ -259,6 +259,11 @@ var Metamuxer = (() => {
|
|||||||
yield* source[Symbol.asyncIterator]();
|
yield* source[Symbol.asyncIterator]();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var validateAnyIterable = (iterable) => {
|
||||||
|
if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) {
|
||||||
|
throw new TypeError("Argument must be an iterable or async iterable.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/subtitles.ts
|
// src/subtitles.ts
|
||||||
var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
|
var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
|
||||||
@@ -4079,22 +4084,28 @@ ${cue.notes ?? ""}`;
|
|||||||
return this._sizePromise ??= this._retrieveSize();
|
return this._sizePromise ??= this._retrieveSize();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var ArrayBufferSource = class extends Source {
|
var BufferSource = class extends Source {
|
||||||
constructor(buffer) {
|
constructor(buffer) {
|
||||||
|
if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) {
|
||||||
|
throw new TypeError("buffer must be an ArrayBuffer or Uint8Array.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._buffer = buffer;
|
this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _read(start, end) {
|
async _read(start, end) {
|
||||||
return new Uint8Array(this._buffer, start, end - start);
|
return this._bytes.subarray(start, end);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _retrieveSize() {
|
async _retrieveSize() {
|
||||||
return this._buffer.byteLength;
|
return this._bytes.byteLength;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BlobSource = class extends Source {
|
var BlobSource = class extends Source {
|
||||||
constructor(blob) {
|
constructor(blob) {
|
||||||
|
if (!(blob instanceof Blob)) {
|
||||||
|
throw new TypeError("blob must be a Blob.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._blob = blob;
|
this._blob = blob;
|
||||||
}
|
}
|
||||||
@@ -5907,6 +5918,15 @@ ${cue.notes ?? ""}`;
|
|||||||
this._demuxerPromise = null;
|
this._demuxerPromise = null;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._format = null;
|
this._format = null;
|
||||||
|
if (!options || typeof options !== "object") {
|
||||||
|
throw new TypeError("options must be an object.");
|
||||||
|
}
|
||||||
|
if (!Array.isArray(options.formats) || options.formats.some((x) => !(x instanceof InputFormat))) {
|
||||||
|
throw new TypeError("options.formats must be an array of InputFormat.");
|
||||||
|
}
|
||||||
|
if (!(options.source instanceof Source)) {
|
||||||
|
throw new TypeError("options.source must be a Source.");
|
||||||
|
}
|
||||||
this._formats = options.formats;
|
this._formats = options.formats;
|
||||||
this._source = options.source;
|
this._source = options.source;
|
||||||
this._mainReader = new Reader(options.source);
|
this._mainReader = new Reader(options.source);
|
||||||
@@ -5961,6 +5981,19 @@ ${cue.notes ?? ""}`;
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/media-drain.ts
|
// src/media-drain.ts
|
||||||
|
var validateChunkRetrievalOptions = (options) => {
|
||||||
|
if (!options || typeof options !== "object") {
|
||||||
|
throw new TypeError("options must be an object.");
|
||||||
|
}
|
||||||
|
if (options.metadataOnly !== void 0 && typeof options.metadataOnly !== "boolean") {
|
||||||
|
throw new TypeError("options.metadataOnly, when defined, must be a boolean.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var validateTimestamp = (timestamp) => {
|
||||||
|
if (typeof timestamp !== "number" || Number.isNaN(timestamp)) {
|
||||||
|
throw new TypeError("timestamp must be a number.");
|
||||||
|
}
|
||||||
|
};
|
||||||
var BaseChunkDrain = class {
|
var BaseChunkDrain = class {
|
||||||
async *chunks(startChunk, endTimestamp = Infinity) {
|
async *chunks(startChunk, endTimestamp = Infinity) {
|
||||||
const chunkQueue = [];
|
const chunkQueue = [];
|
||||||
@@ -6016,6 +6049,7 @@ ${cue.notes ?? ""}`;
|
|||||||
return structuredClone(frame);
|
return structuredClone(frame);
|
||||||
}
|
}
|
||||||
async *mediaFramesAtTimestamps(timestamps) {
|
async *mediaFramesAtTimestamps(timestamps) {
|
||||||
|
validateAnyIterable(timestamps);
|
||||||
const timestampIterator = toAsyncIterator(timestamps);
|
const timestampIterator = toAsyncIterator(timestamps);
|
||||||
const timestampsOfInterest = [];
|
const timestampsOfInterest = [];
|
||||||
const frameQueue = [];
|
const frameQueue = [];
|
||||||
@@ -6054,6 +6088,7 @@ ${cue.notes ?? ""}`;
|
|||||||
let lastKeyChunk = null;
|
let lastKeyChunk = null;
|
||||||
let lastChunk = null;
|
let lastChunk = null;
|
||||||
for await (const timestamp of timestampIterator) {
|
for await (const timestamp of timestampIterator) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
||||||
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
||||||
await queueDequeue;
|
await queueDequeue;
|
||||||
@@ -6123,6 +6158,8 @@ ${cue.notes ?? ""}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
||||||
|
validateTimestamp(startTimestamp);
|
||||||
|
validateTimestamp(endTimestamp);
|
||||||
const frameQueue = [];
|
const frameQueue = [];
|
||||||
let firstFrameQueued = false;
|
let firstFrameQueued = false;
|
||||||
let lastFrame = null;
|
let lastFrame = null;
|
||||||
@@ -6220,27 +6257,46 @@ ${cue.notes ?? ""}`;
|
|||||||
};
|
};
|
||||||
var EncodedVideoChunkDrain = class extends BaseChunkDrain {
|
var EncodedVideoChunkDrain = class extends BaseChunkDrain {
|
||||||
constructor(videoTrack) {
|
constructor(videoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
}
|
}
|
||||||
getFirstChunk(options = {}) {
|
getFirstChunk(options = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getFirstChunk(options);
|
return this._videoTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
getChunk(timestamp, options = {}) {
|
getChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getChunk(timestamp, options);
|
return this._videoTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextChunk(chunk, options = {}) {
|
getNextChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedVideoChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextChunk(chunk, options);
|
return this._videoTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
getKeyChunk(timestamp, options = {}) {
|
getKeyChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextKeyChunk(chunk, options = {}) {
|
getNextKeyChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedVideoChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
||||||
constructor(videoTrack) {
|
constructor(videoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._decoderConfig = null;
|
this._decoderConfig = null;
|
||||||
@@ -6261,6 +6317,7 @@ ${cue.notes ?? ""}`;
|
|||||||
return new EncodedVideoChunkDrain(this._videoTrack);
|
return new EncodedVideoChunkDrain(this._videoTrack);
|
||||||
}
|
}
|
||||||
async getFrame(timestamp) {
|
async getFrame(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
@@ -6275,6 +6332,18 @@ ${cue.notes ?? ""}`;
|
|||||||
};
|
};
|
||||||
var CanvasDrain = class {
|
var CanvasDrain = class {
|
||||||
constructor(videoTrack, dimensions) {
|
constructor(videoTrack, dimensions) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
|
if (dimensions && typeof dimensions !== "object") {
|
||||||
|
throw new TypeError("dimensions, when defined, must be an object.");
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.width) || dimensions.width <= 0)) {
|
||||||
|
throw new TypeError("dimensions.width must be a positive integer.");
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.height) || dimensions.height <= 0)) {
|
||||||
|
throw new TypeError("dimensions.height must be a positive integer.");
|
||||||
|
}
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
this._dimensions = dimensions;
|
this._dimensions = dimensions;
|
||||||
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
||||||
@@ -6303,6 +6372,7 @@ ${cue.notes ?? ""}`;
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async getCanvas(timestamp) {
|
async getCanvas(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
||||||
return frame && this._videoFrameToWrappedCanvas(frame);
|
return frame && this._videoFrameToWrappedCanvas(frame);
|
||||||
}
|
}
|
||||||
@@ -6319,27 +6389,46 @@ ${cue.notes ?? ""}`;
|
|||||||
};
|
};
|
||||||
var EncodedAudioChunkDrain = class extends BaseChunkDrain {
|
var EncodedAudioChunkDrain = class extends BaseChunkDrain {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._audioTrack = audioTrack;
|
this._audioTrack = audioTrack;
|
||||||
}
|
}
|
||||||
getFirstChunk(options = {}) {
|
getFirstChunk(options = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getFirstChunk(options);
|
return this._audioTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
getChunk(timestamp, options = {}) {
|
getChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getChunk(timestamp, options);
|
return this._audioTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextChunk(chunk, options = {}) {
|
getNextChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedAudioChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextChunk(chunk, options);
|
return this._audioTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
getKeyChunk(timestamp, options = {}) {
|
getKeyChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextKeyChunk(chunk, options = {}) {
|
getNextKeyChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedAudioChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var AudioDataDrain = class extends BaseMediaFrameDrain {
|
var AudioDataDrain = class extends BaseMediaFrameDrain {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._decoderConfig = null;
|
this._decoderConfig = null;
|
||||||
@@ -6360,6 +6449,7 @@ ${cue.notes ?? ""}`;
|
|||||||
return new EncodedAudioChunkDrain(this._audioTrack);
|
return new EncodedAudioChunkDrain(this._audioTrack);
|
||||||
}
|
}
|
||||||
async getData(timestamp) {
|
async getData(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -6374,6 +6464,9 @@ ${cue.notes ?? ""}`;
|
|||||||
};
|
};
|
||||||
var AudioBufferDrain = class {
|
var AudioBufferDrain = class {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -6398,6 +6491,7 @@ ${cue.notes ?? ""}`;
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async getBuffer(timestamp) {
|
async getBuffer(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
const data = await this._audioDataDrain.getData(timestamp);
|
const data = await this._audioDataDrain.getData(timestamp);
|
||||||
return data && this._audioDataToWrappedArrayBuffer(data);
|
return data && this._audioDataToWrappedArrayBuffer(data);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
-5
File diff suppressed because one or more lines are too long
Vendored
+5
-5
File diff suppressed because one or more lines are too long
Vendored
+99
-5
@@ -185,6 +185,11 @@ var toAsyncIterator = async function* (source) {
|
|||||||
yield* source[Symbol.asyncIterator]();
|
yield* source[Symbol.asyncIterator]();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var validateAnyIterable = (iterable) => {
|
||||||
|
if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) {
|
||||||
|
throw new TypeError("Argument must be an iterable or async iterable.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/subtitles.ts
|
// src/subtitles.ts
|
||||||
var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
|
var cueBlockHeaderRegex = /(?:(.+?)\n)?((?:\d{2}:)?\d{2}:\d{2}.\d{3})\s+-->\s+((?:\d{2}:)?\d{2}:\d{2}.\d{3})/g;
|
||||||
@@ -4005,22 +4010,28 @@ var Source = class {
|
|||||||
return this._sizePromise ??= this._retrieveSize();
|
return this._sizePromise ??= this._retrieveSize();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var ArrayBufferSource = class extends Source {
|
var BufferSource = class extends Source {
|
||||||
constructor(buffer) {
|
constructor(buffer) {
|
||||||
|
if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) {
|
||||||
|
throw new TypeError("buffer must be an ArrayBuffer or Uint8Array.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._buffer = buffer;
|
this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _read(start, end) {
|
async _read(start, end) {
|
||||||
return new Uint8Array(this._buffer, start, end - start);
|
return this._bytes.subarray(start, end);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async _retrieveSize() {
|
async _retrieveSize() {
|
||||||
return this._buffer.byteLength;
|
return this._bytes.byteLength;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var BlobSource = class extends Source {
|
var BlobSource = class extends Source {
|
||||||
constructor(blob) {
|
constructor(blob) {
|
||||||
|
if (!(blob instanceof Blob)) {
|
||||||
|
throw new TypeError("blob must be a Blob.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._blob = blob;
|
this._blob = blob;
|
||||||
}
|
}
|
||||||
@@ -5833,6 +5844,15 @@ var Input = class {
|
|||||||
this._demuxerPromise = null;
|
this._demuxerPromise = null;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._format = null;
|
this._format = null;
|
||||||
|
if (!options || typeof options !== "object") {
|
||||||
|
throw new TypeError("options must be an object.");
|
||||||
|
}
|
||||||
|
if (!Array.isArray(options.formats) || options.formats.some((x) => !(x instanceof InputFormat))) {
|
||||||
|
throw new TypeError("options.formats must be an array of InputFormat.");
|
||||||
|
}
|
||||||
|
if (!(options.source instanceof Source)) {
|
||||||
|
throw new TypeError("options.source must be a Source.");
|
||||||
|
}
|
||||||
this._formats = options.formats;
|
this._formats = options.formats;
|
||||||
this._source = options.source;
|
this._source = options.source;
|
||||||
this._mainReader = new Reader(options.source);
|
this._mainReader = new Reader(options.source);
|
||||||
@@ -5887,6 +5907,19 @@ var Input = class {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/media-drain.ts
|
// src/media-drain.ts
|
||||||
|
var validateChunkRetrievalOptions = (options) => {
|
||||||
|
if (!options || typeof options !== "object") {
|
||||||
|
throw new TypeError("options must be an object.");
|
||||||
|
}
|
||||||
|
if (options.metadataOnly !== void 0 && typeof options.metadataOnly !== "boolean") {
|
||||||
|
throw new TypeError("options.metadataOnly, when defined, must be a boolean.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var validateTimestamp = (timestamp) => {
|
||||||
|
if (typeof timestamp !== "number" || Number.isNaN(timestamp)) {
|
||||||
|
throw new TypeError("timestamp must be a number.");
|
||||||
|
}
|
||||||
|
};
|
||||||
var BaseChunkDrain = class {
|
var BaseChunkDrain = class {
|
||||||
async *chunks(startChunk, endTimestamp = Infinity) {
|
async *chunks(startChunk, endTimestamp = Infinity) {
|
||||||
const chunkQueue = [];
|
const chunkQueue = [];
|
||||||
@@ -5942,6 +5975,7 @@ var BaseMediaFrameDrain = class {
|
|||||||
return structuredClone(frame);
|
return structuredClone(frame);
|
||||||
}
|
}
|
||||||
async *mediaFramesAtTimestamps(timestamps) {
|
async *mediaFramesAtTimestamps(timestamps) {
|
||||||
|
validateAnyIterable(timestamps);
|
||||||
const timestampIterator = toAsyncIterator(timestamps);
|
const timestampIterator = toAsyncIterator(timestamps);
|
||||||
const timestampsOfInterest = [];
|
const timestampsOfInterest = [];
|
||||||
const frameQueue = [];
|
const frameQueue = [];
|
||||||
@@ -5980,6 +6014,7 @@ var BaseMediaFrameDrain = class {
|
|||||||
let lastKeyChunk = null;
|
let lastKeyChunk = null;
|
||||||
let lastChunk = null;
|
let lastChunk = null;
|
||||||
for await (const timestamp of timestampIterator) {
|
for await (const timestamp of timestampIterator) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
||||||
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
||||||
await queueDequeue;
|
await queueDequeue;
|
||||||
@@ -6049,6 +6084,8 @@ var BaseMediaFrameDrain = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
async *mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
||||||
|
validateTimestamp(startTimestamp);
|
||||||
|
validateTimestamp(endTimestamp);
|
||||||
const frameQueue = [];
|
const frameQueue = [];
|
||||||
let firstFrameQueued = false;
|
let firstFrameQueued = false;
|
||||||
let lastFrame = null;
|
let lastFrame = null;
|
||||||
@@ -6146,27 +6183,46 @@ var BaseMediaFrameDrain = class {
|
|||||||
};
|
};
|
||||||
var EncodedVideoChunkDrain = class extends BaseChunkDrain {
|
var EncodedVideoChunkDrain = class extends BaseChunkDrain {
|
||||||
constructor(videoTrack) {
|
constructor(videoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
}
|
}
|
||||||
getFirstChunk(options = {}) {
|
getFirstChunk(options = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getFirstChunk(options);
|
return this._videoTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
getChunk(timestamp, options = {}) {
|
getChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getChunk(timestamp, options);
|
return this._videoTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextChunk(chunk, options = {}) {
|
getNextChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedVideoChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextChunk(chunk, options);
|
return this._videoTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
getKeyChunk(timestamp, options = {}) {
|
getKeyChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextKeyChunk(chunk, options = {}) {
|
getNextKeyChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedVideoChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
||||||
constructor(videoTrack) {
|
constructor(videoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._decoderConfig = null;
|
this._decoderConfig = null;
|
||||||
@@ -6187,6 +6243,7 @@ var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
|||||||
return new EncodedVideoChunkDrain(this._videoTrack);
|
return new EncodedVideoChunkDrain(this._videoTrack);
|
||||||
}
|
}
|
||||||
async getFrame(timestamp) {
|
async getFrame(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
@@ -6201,6 +6258,18 @@ var VideoFrameDrain = class extends BaseMediaFrameDrain {
|
|||||||
};
|
};
|
||||||
var CanvasDrain = class {
|
var CanvasDrain = class {
|
||||||
constructor(videoTrack, dimensions) {
|
constructor(videoTrack, dimensions) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError("videoTrack must be an InputVideoTrack.");
|
||||||
|
}
|
||||||
|
if (dimensions && typeof dimensions !== "object") {
|
||||||
|
throw new TypeError("dimensions, when defined, must be an object.");
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.width) || dimensions.width <= 0)) {
|
||||||
|
throw new TypeError("dimensions.width must be a positive integer.");
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.height) || dimensions.height <= 0)) {
|
||||||
|
throw new TypeError("dimensions.height must be a positive integer.");
|
||||||
|
}
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
this._dimensions = dimensions;
|
this._dimensions = dimensions;
|
||||||
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
||||||
@@ -6229,6 +6298,7 @@ var CanvasDrain = class {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async getCanvas(timestamp) {
|
async getCanvas(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
||||||
return frame && this._videoFrameToWrappedCanvas(frame);
|
return frame && this._videoFrameToWrappedCanvas(frame);
|
||||||
}
|
}
|
||||||
@@ -6245,27 +6315,46 @@ var CanvasDrain = class {
|
|||||||
};
|
};
|
||||||
var EncodedAudioChunkDrain = class extends BaseChunkDrain {
|
var EncodedAudioChunkDrain = class extends BaseChunkDrain {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
this._audioTrack = audioTrack;
|
this._audioTrack = audioTrack;
|
||||||
}
|
}
|
||||||
getFirstChunk(options = {}) {
|
getFirstChunk(options = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getFirstChunk(options);
|
return this._audioTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
getChunk(timestamp, options = {}) {
|
getChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getChunk(timestamp, options);
|
return this._audioTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextChunk(chunk, options = {}) {
|
getNextChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedAudioChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextChunk(chunk, options);
|
return this._audioTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
getKeyChunk(timestamp, options = {}) {
|
getKeyChunk(timestamp, options = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
getNextKeyChunk(chunk, options = {}) {
|
getNextKeyChunk(chunk, options = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError("chunk must be an EncodedAudioChunk.");
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var AudioDataDrain = class extends BaseMediaFrameDrain {
|
var AudioDataDrain = class extends BaseMediaFrameDrain {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
super();
|
super();
|
||||||
/** @internal */
|
/** @internal */
|
||||||
this._decoderConfig = null;
|
this._decoderConfig = null;
|
||||||
@@ -6286,6 +6375,7 @@ var AudioDataDrain = class extends BaseMediaFrameDrain {
|
|||||||
return new EncodedAudioChunkDrain(this._audioTrack);
|
return new EncodedAudioChunkDrain(this._audioTrack);
|
||||||
}
|
}
|
||||||
async getData(timestamp) {
|
async getData(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -6300,6 +6390,9 @@ var AudioDataDrain = class extends BaseMediaFrameDrain {
|
|||||||
};
|
};
|
||||||
var AudioBufferDrain = class {
|
var AudioBufferDrain = class {
|
||||||
constructor(audioTrack) {
|
constructor(audioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError("audioTrack must be an InputAudioTrack.");
|
||||||
|
}
|
||||||
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
||||||
}
|
}
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@@ -6324,6 +6417,7 @@ var AudioBufferDrain = class {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async getBuffer(timestamp) {
|
async getBuffer(timestamp) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
const data = await this._audioDataDrain.getData(timestamp);
|
const data = await this._audioDataDrain.getData(timestamp);
|
||||||
return data && this._audioDataToWrappedArrayBuffer(data);
|
return data && this._audioDataToWrappedArrayBuffer(data);
|
||||||
}
|
}
|
||||||
@@ -6341,7 +6435,6 @@ var AudioBufferDrain = class {
|
|||||||
export {
|
export {
|
||||||
ALL_FORMATS,
|
ALL_FORMATS,
|
||||||
AUDIO_CODECS,
|
AUDIO_CODECS,
|
||||||
ArrayBufferSource,
|
|
||||||
ArrayBufferTarget,
|
ArrayBufferTarget,
|
||||||
AudioBufferDrain,
|
AudioBufferDrain,
|
||||||
AudioBufferSource,
|
AudioBufferSource,
|
||||||
@@ -6351,6 +6444,7 @@ export {
|
|||||||
BaseChunkDrain,
|
BaseChunkDrain,
|
||||||
BaseMediaFrameDrain,
|
BaseMediaFrameDrain,
|
||||||
BlobSource,
|
BlobSource,
|
||||||
|
BufferSource,
|
||||||
CanvasDrain,
|
CanvasDrain,
|
||||||
CanvasSource,
|
CanvasSource,
|
||||||
EncodedAudioChunkDrain,
|
EncodedAudioChunkDrain,
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ export {
|
|||||||
} from './codec';
|
} from './codec';
|
||||||
export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target';
|
export { Target, ArrayBufferTarget, StreamTarget, StreamTargetChunk, StreamTargetOptions } from './target';
|
||||||
export { Rotation, TransformationMatrix, AnyIterable } from './misc';
|
export { Rotation, TransformationMatrix, AnyIterable } from './misc';
|
||||||
export { Source, ArrayBufferSource, BlobSource } from './source';
|
export { Source, BufferSource, BlobSource } from './source';
|
||||||
export {
|
export {
|
||||||
InputFormat,
|
InputFormat,
|
||||||
IsobmffInputFormat,
|
IsobmffInputFormat,
|
||||||
|
|||||||
+2
-2
@@ -58,7 +58,7 @@ export class InputVideoTrack extends InputTrack {
|
|||||||
this._backing = backing;
|
this._backing = backing;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodec() {
|
getCodec(): Promise<VideoCodec> {
|
||||||
return this._backing.getCodec();
|
return this._backing.getCodec();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ export class InputAudioTrack extends InputTrack {
|
|||||||
this._backing = backing;
|
this._backing = backing;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodec() {
|
getCodec(): Promise<AudioCodec> {
|
||||||
return this._backing.getCodec();
|
return this._backing.getCodec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ export class Input {
|
|||||||
_format: InputFormat | null = null;
|
_format: InputFormat | null = null;
|
||||||
|
|
||||||
constructor(options: InputOptions) {
|
constructor(options: InputOptions) {
|
||||||
|
if (!options || typeof options !== 'object') {
|
||||||
|
throw new TypeError('options must be an object.');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(options.formats) || options.formats.some(x => !(x instanceof InputFormat))) {
|
||||||
|
throw new TypeError('options.formats must be an array of InputFormat.');
|
||||||
|
}
|
||||||
|
if (!(options.source instanceof Source)) {
|
||||||
|
throw new TypeError('options.source must be a Source.');
|
||||||
|
}
|
||||||
|
|
||||||
this._formats = options.formats;
|
this._formats = options.formats;
|
||||||
this._source = options.source;
|
this._source = options.source;
|
||||||
this._mainReader = new Reader(options.source);
|
this._mainReader = new Reader(options.source);
|
||||||
|
|||||||
+89
-1
@@ -1,11 +1,26 @@
|
|||||||
import { InputAudioTrack, InputVideoTrack } from './input-track';
|
import { InputAudioTrack, InputVideoTrack } from './input-track';
|
||||||
import { AnyIterable, assert, promiseWithResolvers, toAsyncIterator } from './misc';
|
import { AnyIterable, assert, promiseWithResolvers, toAsyncIterator, validateAnyIterable } from './misc';
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export type ChunkRetrievalOptions = {
|
export type ChunkRetrievalOptions = {
|
||||||
metadataOnly?: boolean;
|
metadataOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateChunkRetrievalOptions = (options: ChunkRetrievalOptions) => {
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateTimestamp = (timestamp: number) => {
|
||||||
|
if (typeof timestamp !== 'number' || Number.isNaN(timestamp)) {
|
||||||
|
throw new TypeError('timestamp must be a number.'); // It can be non-finite, that's fine
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export abstract class BaseChunkDrain<Chunk extends EncodedVideoChunk | EncodedAudioChunk> {
|
export abstract class BaseChunkDrain<Chunk extends EncodedVideoChunk | EncodedAudioChunk> {
|
||||||
abstract getFirstChunk(options?: ChunkRetrievalOptions): Promise<Chunk | null>;
|
abstract getFirstChunk(options?: ChunkRetrievalOptions): Promise<Chunk | null>;
|
||||||
@@ -93,6 +108,7 @@ export abstract class BaseMediaFrameDrain<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async* mediaFramesAtTimestamps(timestamps: AnyIterable<number>) {
|
protected async* mediaFramesAtTimestamps(timestamps: AnyIterable<number>) {
|
||||||
|
validateAnyIterable(timestamps);
|
||||||
const timestampIterator = toAsyncIterator(timestamps);
|
const timestampIterator = toAsyncIterator(timestamps);
|
||||||
const timestampsOfInterest: number[] = [];
|
const timestampsOfInterest: number[] = [];
|
||||||
|
|
||||||
@@ -141,6 +157,8 @@ export abstract class BaseMediaFrameDrain<
|
|||||||
let lastChunk: Chunk | null = null;
|
let lastChunk: Chunk | null = null;
|
||||||
|
|
||||||
for await (const timestamp of timestampIterator) {
|
for await (const timestamp of timestampIterator) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
while (frameQueue.length + decoder.decodeQueueSize > MAX_QUEUE_SIZE) {
|
||||||
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
({ promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers());
|
||||||
await queueDequeue;
|
await queueDequeue;
|
||||||
@@ -230,6 +248,9 @@ export abstract class BaseMediaFrameDrain<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async* mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
protected async* mediaFramesInRange(startTimestamp = 0, endTimestamp = Infinity) {
|
||||||
|
validateTimestamp(startTimestamp);
|
||||||
|
validateTimestamp(endTimestamp);
|
||||||
|
|
||||||
const frameQueue: MediaFrame[] = [];
|
const frameQueue: MediaFrame[] = [];
|
||||||
let firstFrameQueued = false;
|
let firstFrameQueued = false;
|
||||||
let lastFrame: MediaFrame | null = null;
|
let lastFrame: MediaFrame | null = null;
|
||||||
@@ -368,28 +389,45 @@ export class EncodedVideoChunkDrain extends BaseChunkDrain<EncodedVideoChunk> {
|
|||||||
_videoTrack: InputVideoTrack;
|
_videoTrack: InputVideoTrack;
|
||||||
|
|
||||||
constructor(videoTrack: InputVideoTrack) {
|
constructor(videoTrack: InputVideoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError('videoTrack must be an InputVideoTrack.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstChunk(options: ChunkRetrievalOptions = {}) {
|
getFirstChunk(options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getFirstChunk(options);
|
return this._videoTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
getChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getChunk(timestamp, options);
|
return this._videoTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextChunk(chunk: EncodedVideoChunk, options: ChunkRetrievalOptions = {}) {
|
getNextChunk(chunk: EncodedVideoChunk, options: ChunkRetrievalOptions = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError('chunk must be an EncodedVideoChunk.');
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextChunk(chunk, options);
|
return this._videoTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
getKeyChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
return this._videoTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextKeyChunk(chunk: EncodedVideoChunk, options: ChunkRetrievalOptions = {}) {
|
getNextKeyChunk(chunk: EncodedVideoChunk, options: ChunkRetrievalOptions = {}) {
|
||||||
|
if (!(chunk instanceof EncodedVideoChunk)) {
|
||||||
|
throw new TypeError('chunk must be an EncodedVideoChunk.');
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
return this._videoTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,6 +440,10 @@ export class VideoFrameDrain extends BaseMediaFrameDrain<EncodedVideoChunk, Vide
|
|||||||
_decoderConfig: VideoDecoderConfig | null = null;
|
_decoderConfig: VideoDecoderConfig | null = null;
|
||||||
|
|
||||||
constructor(videoTrack: InputVideoTrack) {
|
constructor(videoTrack: InputVideoTrack) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError('videoTrack must be an InputVideoTrack.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
@@ -426,6 +468,8 @@ export class VideoFrameDrain extends BaseMediaFrameDrain<EncodedVideoChunk, Vide
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getFrame(timestamp: number) {
|
async getFrame(timestamp: number) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const frame of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
@@ -458,6 +502,19 @@ export class CanvasDrain {
|
|||||||
_videoFrameDrain: VideoFrameDrain;
|
_videoFrameDrain: VideoFrameDrain;
|
||||||
|
|
||||||
constructor(videoTrack: InputVideoTrack, dimensions?: { width: number; height: number }) {
|
constructor(videoTrack: InputVideoTrack, dimensions?: { width: number; height: number }) {
|
||||||
|
if (!(videoTrack instanceof InputVideoTrack)) {
|
||||||
|
throw new TypeError('videoTrack must be an InputVideoTrack.');
|
||||||
|
}
|
||||||
|
if (dimensions && typeof dimensions !== 'object') {
|
||||||
|
throw new TypeError('dimensions, when defined, must be an object.');
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.width) || dimensions.width <= 0)) {
|
||||||
|
throw new TypeError('dimensions.width must be a positive integer.');
|
||||||
|
}
|
||||||
|
if (dimensions && (!Number.isInteger(dimensions.height) || dimensions.height <= 0)) {
|
||||||
|
throw new TypeError('dimensions.height must be a positive integer.');
|
||||||
|
}
|
||||||
|
|
||||||
this._videoTrack = videoTrack;
|
this._videoTrack = videoTrack;
|
||||||
this._dimensions = dimensions;
|
this._dimensions = dimensions;
|
||||||
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
this._videoFrameDrain = new VideoFrameDrain(videoTrack);
|
||||||
@@ -495,6 +552,8 @@ export class CanvasDrain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCanvas(timestamp: number) {
|
async getCanvas(timestamp: number) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
const frame = await this._videoFrameDrain.getFrame(timestamp);
|
||||||
return frame && this._videoFrameToWrappedCanvas(frame);
|
return frame && this._videoFrameToWrappedCanvas(frame);
|
||||||
}
|
}
|
||||||
@@ -518,28 +577,45 @@ export class EncodedAudioChunkDrain extends BaseChunkDrain<EncodedAudioChunk> {
|
|||||||
_audioTrack: InputAudioTrack;
|
_audioTrack: InputAudioTrack;
|
||||||
|
|
||||||
constructor(audioTrack: InputAudioTrack) {
|
constructor(audioTrack: InputAudioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError('audioTrack must be an InputAudioTrack.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._audioTrack = audioTrack;
|
this._audioTrack = audioTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstChunk(options: ChunkRetrievalOptions = {}) {
|
getFirstChunk(options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getFirstChunk(options);
|
return this._audioTrack._backing.getFirstChunk(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
getChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getChunk(timestamp, options);
|
return this._audioTrack._backing.getChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextChunk(chunk: EncodedAudioChunk, options: ChunkRetrievalOptions = {}) {
|
getNextChunk(chunk: EncodedAudioChunk, options: ChunkRetrievalOptions = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError('chunk must be an EncodedAudioChunk.');
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextChunk(chunk, options);
|
return this._audioTrack._backing.getNextChunk(chunk, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
getKeyChunk(timestamp: number, options: ChunkRetrievalOptions = {}) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
return this._audioTrack._backing.getKeyChunk(timestamp, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextKeyChunk(chunk: EncodedAudioChunk, options: ChunkRetrievalOptions = {}) {
|
getNextKeyChunk(chunk: EncodedAudioChunk, options: ChunkRetrievalOptions = {}) {
|
||||||
|
if (!(chunk instanceof EncodedAudioChunk)) {
|
||||||
|
throw new TypeError('chunk must be an EncodedAudioChunk.');
|
||||||
|
}
|
||||||
|
validateChunkRetrievalOptions(options);
|
||||||
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
return this._audioTrack._backing.getNextKeyChunk(chunk, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,6 +628,10 @@ export class AudioDataDrain extends BaseMediaFrameDrain<EncodedAudioChunk, Audio
|
|||||||
_decoderConfig: AudioDecoderConfig | null = null;
|
_decoderConfig: AudioDecoderConfig | null = null;
|
||||||
|
|
||||||
constructor(audioTrack: InputAudioTrack) {
|
constructor(audioTrack: InputAudioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError('audioTrack must be an InputAudioTrack.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._audioTrack = audioTrack;
|
this._audioTrack = audioTrack;
|
||||||
@@ -576,6 +656,8 @@ export class AudioDataDrain extends BaseMediaFrameDrain<EncodedAudioChunk, Audio
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getData(timestamp: number) {
|
async getData(timestamp: number) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
for await (const data of this.mediaFramesAtTimestamps([timestamp])) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -603,6 +685,10 @@ export class AudioBufferDrain {
|
|||||||
_audioDataDrain: AudioDataDrain;
|
_audioDataDrain: AudioDataDrain;
|
||||||
|
|
||||||
constructor(audioTrack: InputAudioTrack) {
|
constructor(audioTrack: InputAudioTrack) {
|
||||||
|
if (!(audioTrack instanceof InputAudioTrack)) {
|
||||||
|
throw new TypeError('audioTrack must be an InputAudioTrack.');
|
||||||
|
}
|
||||||
|
|
||||||
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
this._audioDataDrain = new AudioDataDrain(audioTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,6 +721,8 @@ export class AudioBufferDrain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getBuffer(timestamp: number) {
|
async getBuffer(timestamp: number) {
|
||||||
|
validateTimestamp(timestamp);
|
||||||
|
|
||||||
const data = await this._audioDataDrain.getData(timestamp);
|
const data = await this._audioDataDrain.getData(timestamp);
|
||||||
return data && this._audioDataToWrappedArrayBuffer(data);
|
return data && this._audioDataToWrappedArrayBuffer(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,3 +239,9 @@ export const toAsyncIterator = async function* <T>(source: AnyIterable<T>): Asyn
|
|||||||
yield* source[Symbol.asyncIterator]();
|
yield* source[Symbol.asyncIterator]();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateAnyIterable = (iterable: AnyIterable<unknown>) => {
|
||||||
|
if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) {
|
||||||
|
throw new TypeError('Argument must be an iterable or async iterable.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
+14
-6
@@ -15,24 +15,28 @@ export abstract class Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
export class ArrayBufferSource extends Source {
|
export class BufferSource extends Source {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_buffer: ArrayBuffer;
|
_bytes: Uint8Array;
|
||||||
|
|
||||||
|
constructor(buffer: ArrayBuffer | Uint8Array) {
|
||||||
|
if (!(buffer instanceof ArrayBuffer) && !(buffer instanceof Uint8Array)) {
|
||||||
|
throw new TypeError('buffer must be an ArrayBuffer or Uint8Array.');
|
||||||
|
}
|
||||||
|
|
||||||
constructor(buffer: ArrayBuffer) {
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._buffer = buffer;
|
this._bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override async _read(start: number, end: number) {
|
override async _read(start: number, end: number) {
|
||||||
return new Uint8Array(this._buffer, start, end - start);
|
return this._bytes.subarray(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
override async _retrieveSize() {
|
override async _retrieveSize() {
|
||||||
return this._buffer.byteLength;
|
return this._bytes.byteLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +46,10 @@ export class BlobSource extends Source {
|
|||||||
_blob: Blob;
|
_blob: Blob;
|
||||||
|
|
||||||
constructor(blob: Blob) {
|
constructor(blob: Blob) {
|
||||||
|
if (!(blob instanceof Blob)) {
|
||||||
|
throw new TypeError('blob must be a Blob.');
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._blob = blob;
|
this._blob = blob;
|
||||||
|
|||||||
Reference in New Issue
Block a user