diff --git a/dev/convert.html b/dev/convert.html
index 3c6e148..f0bfebe 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -15,7 +15,7 @@
const file = fileInput.files[0];
const source = new Metamuxer.BlobSource(file);
- const target = new Metamuxer.StreamTarget(new WritableStream({
+ const target = new Metamuxer.BufferTarget() ?? new Metamuxer.StreamTarget(new WritableStream({
write: console.log
}), {
chunked: true,
@@ -41,8 +41,8 @@
target
}),
audio: {
- //discard: true
- forceReencode: true,
+ discard: true
+ //forceReencode: true,
},
/*
video: {
@@ -73,16 +73,16 @@
//rotate: 90
//width: 720 ?? 2160,
//height: 1280 ?? 3840,
- fit: 'contain',
- rotate: 90,
- width: 512,
- height: 512,
+ //fit: 'contain',
+ //rotate: 90,
+ //width: 512,
+ //height: 512,
//width: 200,
//height: 100,
},
trim: {
start: 0,
- //end: 60
+ end: 30
},
computeProgress: true
});
diff --git a/dev/player.html b/dev/player.html
index 1752009..9659ac6 100644
--- a/dev/player.html
+++ b/dev/player.html
@@ -50,7 +50,7 @@ if (!(await videoTrack?.canDecode())) {
videoTrack = null;
}
if (!(await audioTrack?.canDecode())) {
- audioTrack = null;
+ audioTrack = null;
}
const canvas = document.querySelector('canvas');
diff --git a/src/codec.ts b/src/codec.ts
index 29d22c1..5cb92db 100644
--- a/src/codec.ts
+++ b/src/codec.ts
@@ -1303,7 +1303,7 @@ export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata |
if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
throw new TypeError(
'Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
- + ' the WebCodecs codec registry.',
+ + ' the WebCodecs Codec Registry.',
);
}
if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth! <= 0) {
@@ -1444,7 +1444,7 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
throw new TypeError(
'Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
- + ' the WebCodecs codec registry.',
+ + ' the WebCodecs Codec Registry.',
);
}
if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
diff --git a/src/conversion.ts b/src/conversion.ts
index de07c12..1bf4052 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -52,13 +52,13 @@ export type ConversionOptions = {
/** The desired bitrate of the output video. */
bitrate?: VideoEncodingConfig['bitrate'];
/**
- * The desired width of the output video, defaulting to the video's natural display width. If height is not set,
- * it will be deduced automatically based on aspect ratio.
+ * The desired width of the output video in pixels, defaulting to the video's natural display width. If height
+ * is not set, it will be deduced automatically based on aspect ratio.
*/
width?: number;
/**
- * The desired height of the output video, defaulting to the video's natural display height. If width is not
- * set, it will be deduced automatically based on aspect ratio.
+ * The desired height of the output video in pixels, defaulting to the video's natural display height. If width
+ * is not set, it will be deduced automatically based on aspect ratio.
*/
height?: number;
/**
@@ -492,8 +492,11 @@ export class Conversion {
const sink = new EncodedPacketSink(track);
const decoderConfig = await track.getDecoderConfig();
const meta: EncodedVideoChunkMetadata = { decoderConfig: decoderConfig ?? undefined };
+ const endPacket = Number.isFinite(this._endTimestamp)
+ ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? undefined
+ : undefined;
- for await (const packet of sink.packets(undefined, this._endTimestamp)) {
+ for await (const packet of sink.packets(undefined, endPacket)) {
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
await this._synchronizer.wait(packet.timestamp);
}
@@ -658,8 +661,11 @@ export class Conversion {
const sink = new EncodedPacketSink(track);
const decoderConfig = await track.getDecoderConfig();
const meta: EncodedAudioChunkMetadata = { decoderConfig: decoderConfig ?? undefined };
+ const endPacket = Number.isFinite(this._endTimestamp)
+ ? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? undefined
+ : undefined;
- for await (const packet of sink.packets(undefined, this._endTimestamp)) {
+ for await (const packet of sink.packets(undefined, endPacket)) {
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
await this._synchronizer.wait(packet.timestamp);
}
diff --git a/src/input-track.ts b/src/input-track.ts
index c135f9a..009f45f 100644
--- a/src/input-track.ts
+++ b/src/input-track.ts
@@ -158,12 +158,12 @@ export class InputVideoTrack extends InputTrack {
return this._backing.getCodec();
}
- /** The width of the track's coded samples, before any transformations or rotations. */
+ /** The width in pixels of the track's coded samples, before any transformations or rotations. */
get codedWidth() {
return this._backing.getCodedWidth();
}
- /** The height of the track's coded samples, before any transformations or rotations. */
+ /** The height in pixels of the track's coded samples, before any transformations or rotations. */
get codedHeight() {
return this._backing.getCodedHeight();
}
@@ -173,13 +173,13 @@ export class InputVideoTrack extends InputTrack {
return this._backing.getRotation();
}
- /** The width of the track's frames after rotation. */
+ /** The width in pixels of the track's frames after rotation. */
get displayWidth() {
const rotation = this._backing.getRotation();
return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight();
}
- /** The height of the track's frames after rotation. */
+ /** The height in pixels of the track's frames after rotation. */
get displayHeight() {
const rotation = this._backing.getRotation();
return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth();
@@ -190,7 +190,7 @@ export class InputVideoTrack extends InputTrack {
return this._backing.getColorSpace();
}
- /** Returns true iff the track's samples use a high dynamic range (HDR). */
+ /** If this method returns true, the track's samples use a high dynamic range (HDR). */
async hasHighDynamicRange() {
const colorSpace = await this._backing.getColorSpace();
diff --git a/src/matroska/ebml.ts b/src/matroska/ebml.ts
index ff9d0c9..6b95f56 100644
--- a/src/matroska/ebml.ts
+++ b/src/matroska/ebml.ts
@@ -393,15 +393,14 @@ export class EBMLReader {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
const firstByte = view.getUint8(offset);
- // Find the position of the first set bit, which determines the width
+ // Find the position of VINT_MARKER, which determines the width
let width = 1;
- let mask = 0x80;
+ let mask = 1 << 7;
while ((firstByte & mask) === 0 && width < MAX_VAR_INT_SIZE) {
width++;
mask >>= 1;
}
- // Read all bytes
const { view: fullView, offset: fullOffset } = this.reader.getViewAndOffset(this.pos, this.pos + width);
// First byte's value needs the marker bit cleared
@@ -409,7 +408,8 @@ export class EBMLReader {
// Read remaining bytes
for (let i = 1; i < width; i++) {
- value = (value << 8) | fullView.getUint8(fullOffset + i);
+ value *= 1 << 8;
+ value += fullView.getUint8(fullOffset + i);
}
this.pos += width;
@@ -426,7 +426,8 @@ export class EBMLReader {
// Read bytes from most significant to least significant
for (let i = 0; i < width; i++) {
- value = (value << 8) | view.getUint8(offset + i);
+ value *= 1 << 8;
+ value += view.getUint8(offset + i);
}
this.pos += width;
diff --git a/src/media-sink.ts b/src/media-sink.ts
index 0cc4b52..d64cb97 100644
--- a/src/media-sink.ts
+++ b/src/media-sink.ts
@@ -129,13 +129,24 @@ export class EncodedPacketSink {
* method will intelligently preload packets based on the speed of the consumer.
*
* @param startPacket - (optional) The packet from which iteration should begin. This packet will also be yielded.
- * @param endTimestamp - The timestamp in seconds at which to stop iteration. This timestamp is exclusive.
+ * @param endTimestamp - (optional) The timestamp at which iteration should end. This packet will _not_ be yielded.
*/
packets(
startPacket?: EncodedPacket,
- endTimestamp = Infinity,
- options?: PacketRetrievalOptions,
+ endPacket?: EncodedPacket,
+ options: PacketRetrievalOptions = {},
): AsyncGenerator {
+ if (startPacket !== undefined && !(startPacket instanceof EncodedPacket)) {
+ throw new TypeError('startPacket must be an EncodedPacket.');
+ }
+ if (startPacket !== undefined && startPacket.isMetadataOnly && !options?.metadataOnly) {
+ throw new TypeError('startPacket can only be metadata-only if options.metadataOnly is enabled.');
+ }
+ if (endPacket !== undefined && !(endPacket instanceof EncodedPacket)) {
+ throw new TypeError('endPacket must be an EncodedPacket.');
+ }
+ validatePacketRetrievalOptions(options);
+
const packetQueue: EncodedPacket[] = [];
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
@@ -157,7 +168,7 @@ export class EncodedPacketSink {
let packet = startPacket ?? await this.getFirstPacket(options);
while (packet && !terminated) {
- if (packet.timestamp >= endTimestamp) {
+ if (endPacket && packet.sequenceNumber >= endPacket?.sequenceNumber) {
break;
}
@@ -332,25 +343,25 @@ export abstract class BaseMediaSampleSink<
let currentPacket: EncodedPacket | null = keyPacket;
- let packetsEndTimestamp = Infinity;
+ let endPacket: EncodedPacket | undefined = undefined;
if (endTimestamp < Infinity) {
// When an end timestamp is set, we cannot simply use that for the packet iterator due to out-of-order
// frames (B-frames). Instead, we'll need to keep decoding packets 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
// out of order, we can stop at the first key frame after the end timestamp.
- const endPacket = await packetSink.getPacket(endTimestamp);
- const endKeyPacket = !endPacket
+ const packet = await packetSink.getPacket(endTimestamp);
+ const keyPacket = !packet
? null
- : endPacket.type === 'key' && endPacket.timestamp === endTimestamp
- ? endPacket
- : await packetSink.getNextKeyPacket(endPacket);
+ : packet.type === 'key' && packet.timestamp === endTimestamp
+ ? packet
+ : await packetSink.getNextKeyPacket(packet);
- if (endKeyPacket) {
- packetsEndTimestamp = endKeyPacket.timestamp;
+ if (keyPacket) {
+ endPacket = keyPacket;
}
}
- const packets = packetSink.packets(keyPacket, packetsEndTimestamp);
+ const packets = packetSink.packets(keyPacket, endPacket);
await packets.next(); // Skip the start packet as we already have it
while (currentPacket && !ended) {
@@ -847,13 +858,13 @@ export type WrappedCanvas = {
*/
export type CanvasSinkOptions = {
/**
- * The width of the output canvas, defaulting to the display width of the video track. If height is not set, it
- * will be deduced automatically based on aspect ratio.
+ * The width of the output canvas in pixels, defaulting to the display width of the video track. If height is not
+ * set, it will be deduced automatically based on aspect ratio.
*/
width?: number;
/**
- * The height of the output canvas, defaulting to the display height of the video track. If width is not set, it
- * will be deduced automatically based on aspect ratio.
+ * The height of the output canvas in pixels, defaulting to the display height of the video track. If width is not
+ * set, it will be deduced automatically based on aspect ratio.
*/
height?: number;
/**
diff --git a/src/media-source.ts b/src/media-source.ts
index 41c2fb3..ded3738 100644
--- a/src/media-source.ts
+++ b/src/media-source.ts
@@ -188,7 +188,7 @@ export type VideoEncodingConfig = {
*/
keyFrameInterval?: number;
/**
- * The full codec string as specified in the WebCodecs API Codec Registry. This string must match the codec
+ * The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
*/
fullCodecString?: string;
@@ -653,7 +653,7 @@ export type AudioEncodingConfig = {
*/
bitrate?: number | Quality;
/**
- * The full codec string as specified in the WebCodecs API Codec Registry. This string must match the codec
+ * The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
*/
fullCodecString?: string;
diff --git a/src/reader.ts b/src/reader.ts
index 6ac6eb6..88a8ac4 100644
--- a/src/reader.ts
+++ b/src/reader.ts
@@ -58,7 +58,7 @@ export class Reader {
}
}
- this.source.onread?.({ start, end });
+ this.source.onread?.(start, end);
const bytesPromise = this.source._read(start, end);
const loadingSegment: LoadingSegment = { start, end, promise: bytesPromise };
this.loadingSegments.push(loadingSegment);
diff --git a/src/sample.ts b/src/sample.ts
index 634bc18..96d456c 100644
--- a/src/sample.ts
+++ b/src/sample.ts
@@ -348,8 +348,8 @@ export class VideoSample {
*
* @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
* @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
- * @param dWidth - The width to draw the image in the destination canvas. This allows scaling of the drawn image.
- * @param dHeight - The height to draw the image in the destination canvas. This allows scaling of the drawn image.
+ * @param dWidth - The width in pixels with which to draw the image in the destination canvas.
+ * @param dHeight - The height in pixels with which to draw the image in the destination canvas.
*/
draw(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
diff --git a/src/source.ts b/src/source.ts
index bb0bb19..854532a 100644
--- a/src/source.ts
+++ b/src/source.ts
@@ -19,12 +19,7 @@ export abstract class Source {
}
/** Called each time data is requested from the source. */
- onread: ((range: {
- /** The start byte offset (inclusive). */
- start: number;
- /** The end byte offset (exclusive). */
- end: number;
- }) => unknown) | null = null;
+ onread: ((start: number, end: number) => unknown) | null = null;
}
/**