mediabunny
Version:
Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.
1,485 lines (1,479 loc) • 1.42 MB
JavaScript
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
"use strict";
var Mediabunny = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
throw TypeError(msg);
};
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc2) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc2 = __getOwnPropDesc(from, key)) || desc2.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __using = (stack, value, async) => {
if (value != null) {
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
var dispose, inner;
if (async) dispose = value[__knownSymbol("asyncDispose")];
if (dispose === void 0) {
dispose = value[__knownSymbol("dispose")];
if (async) inner = dispose;
}
if (typeof dispose !== "function") __typeError("Object not disposable");
if (inner) dispose = function() {
try {
inner.call(this);
} catch (e) {
return Promise.reject(e);
}
};
stack.push([async, dispose, value]);
} else if (async) {
stack.push([async]);
}
return value;
};
var __callDispose = (stack, error, hasError) => {
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
};
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
var next = (it) => {
while (it = stack.pop()) {
try {
var result = it[1] && it[1].call(it[2]);
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
} catch (e) {
fail(e);
}
}
if (hasError) throw error;
};
return next();
};
// src/index.ts
var index_exports = {};
__export(index_exports, {
ADTS: () => ADTS,
ALL_FORMATS: () => ALL_FORMATS,
ALL_TRACK_TYPES: () => ALL_TRACK_TYPES,
AUDIO_CODECS: () => AUDIO_CODECS,
AdtsInputFormat: () => AdtsInputFormat,
AdtsOutputFormat: () => AdtsOutputFormat,
AppendOnlyStreamTarget: () => AppendOnlyStreamTarget,
AttachedFile: () => AttachedFile,
AudioBufferSink: () => AudioBufferSink,
AudioBufferSource: () => AudioBufferSource,
AudioSample: () => AudioSample,
AudioSampleResource: () => AudioSampleResource,
AudioSampleSink: () => AudioSampleSink,
AudioSampleSource: () => AudioSampleSource,
AudioSource: () => AudioSource,
BaseMediaSampleSink: () => BaseMediaSampleSink,
BlobSource: () => BlobSource,
BufferSource: () => BufferSource,
BufferTarget: () => BufferTarget,
CanvasSink: () => CanvasSink,
CanvasSource: () => CanvasSource,
CmafOutputFormat: () => CmafOutputFormat,
ConcurrentRunner: () => ConcurrentRunner,
Conversion: () => Conversion,
ConversionCanceledError: () => ConversionCanceledError,
CustomAudioDecoder: () => CustomAudioDecoder,
CustomAudioEncoder: () => CustomAudioEncoder,
CustomPathedSource: () => CustomPathedSource,
CustomSource: () => CustomSource,
CustomVideoDecoder: () => CustomVideoDecoder,
CustomVideoEncoder: () => CustomVideoEncoder,
EncodedAudioPacketSource: () => EncodedAudioPacketSource,
EncodedPacket: () => EncodedPacket,
EncodedPacketSink: () => EncodedPacketSink,
EncodedVideoPacketSource: () => EncodedVideoPacketSource,
EventEmitter: () => EventEmitter,
FLAC: () => FLAC,
FilePathSource: () => FilePathSource,
FilePathTarget: () => FilePathTarget,
FlacInputFormat: () => FlacInputFormat,
FlacOutputFormat: () => FlacOutputFormat,
HLS: () => HLS,
HLS_FORMATS: () => HLS_FORMATS,
HlsInputFormat: () => HlsInputFormat,
HlsOutputFormat: () => HlsOutputFormat,
Input: () => Input,
InputAudioTrack: () => InputAudioTrack,
InputDisposedError: () => InputDisposedError,
InputFormat: () => InputFormat,
InputTrack: () => InputTrack,
InputVideoTrack: () => InputVideoTrack,
IsobmffInputFormat: () => IsobmffInputFormat,
IsobmffOutputFormat: () => IsobmffOutputFormat2,
MATROSKA: () => MATROSKA,
MP3: () => MP3,
MP4: () => MP4,
MPEG_TS: () => MPEG_TS,
MatroskaInputFormat: () => MatroskaInputFormat,
MediaSource: () => MediaSource,
MediaStreamAudioTrackSource: () => MediaStreamAudioTrackSource,
MediaStreamVideoTrackSource: () => MediaStreamVideoTrackSource,
MkvOutputFormat: () => MkvOutputFormat2,
MovOutputFormat: () => MovOutputFormat,
Mp3InputFormat: () => Mp3InputFormat,
Mp3OutputFormat: () => Mp3OutputFormat,
Mp4InputFormat: () => Mp4InputFormat,
Mp4OutputFormat: () => Mp4OutputFormat,
MpegTsInputFormat: () => MpegTsInputFormat,
MpegTsOutputFormat: () => MpegTsOutputFormat,
NON_PCM_AUDIO_CODECS: () => NON_PCM_AUDIO_CODECS,
NullTarget: () => NullTarget,
OGG: () => OGG,
OggInputFormat: () => OggInputFormat,
OggOutputFormat: () => OggOutputFormat,
Output: () => Output,
OutputAudioTrack: () => OutputAudioTrack2,
OutputFormat: () => OutputFormat,
OutputSubtitleTrack: () => OutputSubtitleTrack2,
OutputTrack: () => OutputTrack2,
OutputTrackGroup: () => OutputTrackGroup,
OutputVideoTrack: () => OutputVideoTrack2,
PCM_AUDIO_CODECS: () => PCM_AUDIO_CODECS,
PathedSource: () => PathedSource,
PathedTarget: () => PathedTarget,
QTFF: () => QTFF,
QUALITY_HIGH: () => QUALITY_HIGH,
QUALITY_LOW: () => QUALITY_LOW,
QUALITY_MEDIUM: () => QUALITY_MEDIUM,
QUALITY_VERY_HIGH: () => QUALITY_VERY_HIGH,
QUALITY_VERY_LOW: () => QUALITY_VERY_LOW,
Quality: () => Quality,
QuickTimeInputFormat: () => QuickTimeInputFormat,
RangedSource: () => RangedSource,
RangedTarget: () => RangedTarget,
ReadableStreamSource: () => ReadableStreamSource,
RichImageData: () => RichImageData,
SUBTITLE_CODECS: () => SUBTITLE_CODECS,
Source: () => Source,
SourceRef: () => SourceRef,
StreamSource: () => StreamSource,
StreamTarget: () => StreamTarget,
SubtitleSource: () => SubtitleSource,
Target: () => Target,
TextSubtitleSource: () => TextSubtitleSource,
UnsupportedInputFormatError: () => UnsupportedInputFormatError,
UrlSource: () => UrlSource,
VIDEO_CODECS: () => VIDEO_CODECS,
VIDEO_SAMPLE_PIXEL_FORMATS: () => VIDEO_SAMPLE_PIXEL_FORMATS,
VideoSample: () => VideoSample,
VideoSampleColorSpace: () => VideoSampleColorSpace,
VideoSampleResource: () => VideoSampleResource,
VideoSampleSink: () => VideoSampleSink,
VideoSampleSource: () => VideoSampleSource,
VideoSource: () => VideoSource,
WAVE: () => WAVE,
WEBM: () => WEBM,
WavOutputFormat: () => WavOutputFormat,
WaveInputFormat: () => WaveInputFormat,
WebMInputFormat: () => WebMInputFormat,
WebMOutputFormat: () => WebMOutputFormat,
asc: () => asc,
canDecode: () => canDecode,
canDecodeAudio: () => canDecodeAudio,
canDecodeVideo: () => canDecodeVideo,
canEncode: () => canEncode,
canEncodeAudio: () => canEncodeAudio,
canEncodeSubtitles: () => canEncodeSubtitles,
canEncodeVideo: () => canEncodeVideo,
desc: () => desc,
getDecodableAudioCodecs: () => getDecodableAudioCodecs,
getDecodableCodecs: () => getDecodableCodecs,
getDecodableVideoCodecs: () => getDecodableVideoCodecs,
getEncodableAudioCodecs: () => getEncodableAudioCodecs,
getEncodableCodecs: () => getEncodableCodecs,
getEncodableSubtitleCodecs: () => getEncodableSubtitleCodecs,
getEncodableVideoCodecs: () => getEncodableVideoCodecs,
getFirstEncodableAudioCodec: () => getFirstEncodableAudioCodec,
getFirstEncodableSubtitleCodec: () => getFirstEncodableSubtitleCodec,
getFirstEncodableVideoCodec: () => getFirstEncodableVideoCodec,
prefer: () => prefer,
registerDecoder: () => registerDecoder,
registerEncoder: () => registerEncoder,
registerVideoSampleTransformer: () => registerVideoSampleTransformer
});
// src/misc.ts
function assert(x) {
if (!x) {
throw new Error("Assertion failed.");
}
}
var normalizeRotation = (rotation) => {
const mappedRotation = (rotation % 360 + 360) % 360;
if (mappedRotation === 0 || mappedRotation === 90 || mappedRotation === 180 || mappedRotation === 270) {
return mappedRotation;
} else {
throw new Error(`Invalid rotation ${rotation}.`);
}
};
var last = (arr) => {
return arr && arr[arr.length - 1];
};
var isU32 = (value) => {
return value >= 0 && value < 2 ** 32;
};
var readExpGolomb = (bitstream) => {
let leadingZeroBits = 0;
while (bitstream.readBits(1) === 0 && leadingZeroBits < 32) {
leadingZeroBits++;
}
if (leadingZeroBits >= 32) {
throw new Error("Invalid exponential-Golomb code.");
}
const result = (1 << leadingZeroBits) - 1 + bitstream.readBits(leadingZeroBits);
return result;
};
var readSignedExpGolomb = (bitstream) => {
const codeNum = readExpGolomb(bitstream);
return (codeNum & 1) === 0 ? -(codeNum >> 1) : codeNum + 1 >> 1;
};
var writeBits = (bytes2, start, end, value) => {
for (let i = start; i < end; i++) {
const byteIndex = Math.floor(i / 8);
let byte = bytes2[byteIndex];
const bitIndex = 7 - (i & 7);
byte &= ~(1 << bitIndex);
byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex;
bytes2[byteIndex] = byte;
}
};
var toUint8Array = (source) => {
if (source.constructor === Uint8Array) {
return source;
} else if (ArrayBuffer.isView(source)) {
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
} else {
return new Uint8Array(source);
}
};
var toDataView = (source) => {
if (source.constructor === DataView) {
return source;
} else if (ArrayBuffer.isView(source)) {
return new DataView(source.buffer, source.byteOffset, source.byteLength);
} else {
return new DataView(source);
}
};
var textDecoder = /* @__PURE__ */ new TextDecoder();
var textEncoder = /* @__PURE__ */ new TextEncoder();
var isIso88591Compatible = (text) => {
for (let i = 0; i < text.length; i++) {
const code = text.charCodeAt(i);
if (code > 255) {
return false;
}
}
return true;
};
var invertObject = (object) => {
return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key]));
};
var COLOR_PRIMARIES_MAP = {
bt709: 1,
// ITU-R BT.709
bt470bg: 5,
// ITU-R BT.470BG
smpte170m: 6,
// ITU-R BT.601 525 - SMPTE 170M
bt2020: 9,
// ITU-R BT.202
smpte432: 12
// SMPTE EG 432-1
};
var COLOR_PRIMARIES_MAP_INVERSE = /* @__PURE__ */ invertObject(COLOR_PRIMARIES_MAP);
var TRANSFER_CHARACTERISTICS_MAP = {
"bt709": 1,
// ITU-R BT.709
"smpte170m": 6,
// SMPTE 170M
"linear": 8,
// Linear transfer characteristics
"iec61966-2-1": 13,
// IEC 61966-2-1
"pq": 16,
// Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system
"hlg": 18
// Rec. ITU-R BT.2100-2 hybrid loggamma (HLG) system
};
var TRANSFER_CHARACTERISTICS_MAP_INVERSE = /* @__PURE__ */ invertObject(TRANSFER_CHARACTERISTICS_MAP);
var MATRIX_COEFFICIENTS_MAP = {
"rgb": 0,
// Identity
"bt709": 1,
// ITU-R BT.709
"bt470bg": 5,
// ITU-R BT.470BG
"smpte170m": 6,
// SMPTE 170M
"bt2020-ncl": 9
// ITU-R BT.2020-2 (non-constant luminance)
};
var MATRIX_COEFFICIENTS_MAP_INVERSE = /* @__PURE__ */ invertObject(MATRIX_COEFFICIENTS_MAP);
var colorSpaceIsComplete = (colorSpace) => {
return !!colorSpace && !!colorSpace.primaries && !!colorSpace.transfer && !!colorSpace.matrix && colorSpace.fullRange !== void 0;
};
var isAllowSharedBufferSource = (x) => {
return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x);
};
var AsyncMutex = class {
constructor() {
this.currentPromise = Promise.resolve();
this.pending = 0;
}
async acquire() {
let resolver;
const nextPromise = new Promise((resolve) => {
let resolved = false;
resolver = () => {
if (resolved) {
return;
}
resolve();
this.pending--;
resolved = true;
};
});
const currentPromiseAlias = this.currentPromise;
this.currentPromise = nextPromise;
this.pending++;
await currentPromiseAlias;
return resolver;
}
};
var HEX_STRING_REGEX = /^[0-9a-fA-F]+$/;
var bytesToHexString = (bytes2) => {
return [...bytes2].map((x) => x.toString(16).padStart(2, "0")).join("");
};
var hexStringToBytes = (hexString) => {
assert(hexString.length % 2 === 0);
const bytes2 = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
bytes2[i / 2] = parseInt(hexString.slice(i, i + 2), 16);
}
return bytes2;
};
var reverseBitsU32 = (x) => {
x = x >> 1 & 1431655765 | (x & 1431655765) << 1;
x = x >> 2 & 858993459 | (x & 858993459) << 2;
x = x >> 4 & 252645135 | (x & 252645135) << 4;
x = x >> 8 & 16711935 | (x & 16711935) << 8;
x = x >> 16 & 65535 | (x & 65535) << 16;
return x >>> 0;
};
var binarySearchExact = (arr, key, valueGetter) => {
let low = 0;
let high = arr.length - 1;
let ans = -1;
while (low <= high) {
const mid = low + high >> 1;
const midVal = valueGetter(arr[mid]);
if (midVal === key) {
ans = mid;
high = mid - 1;
} else if (midVal < key) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return ans;
};
var binarySearchLessOrEqual = (arr, key, valueGetter) => {
let low = 0;
let high = arr.length - 1;
let ans = -1;
while (low <= high) {
const mid = low + (high - low + 1) / 2 | 0;
const midVal = valueGetter(arr[mid]);
if (midVal <= key) {
ans = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return ans;
};
var insertSorted = (arr, item, valueGetter) => {
const insertionIndex = binarySearchLessOrEqual(arr, valueGetter(item), valueGetter);
arr.splice(insertionIndex + 1, 0, item);
};
var promiseWithResolvers = () => {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
};
var removeItem = (arr, item) => {
const index = arr.indexOf(item);
if (index !== -1) {
arr.splice(index, 1);
}
};
var findLast = (arr, predicate) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i])) {
return arr[i];
}
}
return void 0;
};
var findLastIndex = (arr, predicate) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (predicate(arr[i])) {
return i;
}
}
return -1;
};
var toAsyncIterator = async function* (source) {
if (Symbol.iterator in source) {
yield* source[Symbol.iterator]();
} else {
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.");
}
};
var assertNever = (x) => {
throw new Error(`Unexpected value: ${x}`);
};
var getUint24 = (view2, byteOffset, littleEndian) => {
const byte1 = view2.getUint8(byteOffset);
const byte2 = view2.getUint8(byteOffset + 1);
const byte3 = view2.getUint8(byteOffset + 2);
if (littleEndian) {
return byte1 | byte2 << 8 | byte3 << 16;
} else {
return byte1 << 16 | byte2 << 8 | byte3;
}
};
var getInt24 = (view2, byteOffset, littleEndian) => {
return getUint24(view2, byteOffset, littleEndian) << 8 >> 8;
};
var setUint24 = (view2, byteOffset, value, littleEndian) => {
value = value >>> 0;
value = value & 16777215;
if (littleEndian) {
view2.setUint8(byteOffset, value & 255);
view2.setUint8(byteOffset + 1, value >>> 8 & 255);
view2.setUint8(byteOffset + 2, value >>> 16 & 255);
} else {
view2.setUint8(byteOffset, value >>> 16 & 255);
view2.setUint8(byteOffset + 1, value >>> 8 & 255);
view2.setUint8(byteOffset + 2, value & 255);
}
};
var setInt24 = (view2, byteOffset, value, littleEndian) => {
value = clamp(value, -8388608, 8388607);
if (value < 0) {
value = value + 16777216 & 16777215;
}
setUint24(view2, byteOffset, value, littleEndian);
};
var setInt64 = (view2, byteOffset, value, littleEndian) => {
if (littleEndian) {
view2.setUint32(byteOffset + 0, value, true);
view2.setInt32(byteOffset + 4, Math.floor(value / 2 ** 32), true);
} else {
view2.setInt32(byteOffset + 0, Math.floor(value / 2 ** 32), true);
view2.setUint32(byteOffset + 4, value, true);
}
};
var mapAsyncGenerator = (generator, map) => {
return {
async next() {
const result = await generator.next();
if (result.done) {
return { value: void 0, done: true };
} else {
return { value: map(result.value), done: false };
}
},
return() {
return generator.return();
},
throw(error) {
return generator.throw(error);
},
[Symbol.asyncIterator]() {
return this;
}
};
};
var clamp = (value, min, max) => {
return Math.max(min, Math.min(max, value));
};
var UNDETERMINED_LANGUAGE = "und";
var roundIfAlmostInteger = (value) => {
const rounded = Math.round(value);
if (Math.abs(value / rounded - 1) < 10 * Number.EPSILON) {
return rounded;
} else {
return value;
}
};
var roundToMultiple = (value, multiple) => {
return Math.round(value / multiple) * multiple;
};
var roundToDivisor = (value, multiple) => {
return Math.round(value * multiple) / multiple;
};
var floorToMultiple = (value, multiple) => {
return Math.floor(value / multiple) * multiple;
};
var floorToDivisor = (value, multiple) => {
return Math.floor(value * multiple) / multiple;
};
var ilog = (x) => {
let ret = 0;
while (x) {
ret++;
x >>= 1;
}
return ret;
};
var ISO_639_2_REGEX = /^[a-z]{3}$/;
var isIso639Dash2LanguageCode = (x) => {
return ISO_639_2_REGEX.test(x);
};
var SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
var mergeRequestInit = (init1, init2) => {
const merged = { ...init1, ...init2 };
if (init1.headers || init2.headers) {
const headers1 = init1.headers ? normalizeHeaders(init1.headers) : {};
const headers2 = init2.headers ? normalizeHeaders(init2.headers) : {};
const mergedHeaders = { ...headers1 };
Object.entries(headers2).forEach(([key2, value2]) => {
const existingKey = Object.keys(mergedHeaders).find(
(key1) => key1.toLowerCase() === key2.toLowerCase()
);
if (existingKey) {
delete mergedHeaders[existingKey];
}
mergedHeaders[key2] = value2;
});
merged.headers = mergedHeaders;
}
return merged;
};
var normalizeHeaders = (headers) => {
if (headers instanceof Headers) {
const result = {};
headers.forEach((value, key) => {
result[key] = value;
});
return result;
}
if (Array.isArray(headers)) {
const result = {};
headers.forEach(([key, value]) => {
result[key] = value;
});
return result;
}
return headers;
};
var retriedFetch = async (fetchFn, url2, requestInit, getRetryDelay, shouldStop) => {
let attempts = 0;
while (true) {
try {
return await fetchFn(url2, requestInit);
} catch (error) {
if (shouldStop()) {
throw error;
}
attempts++;
const retryDelayInSeconds = getRetryDelay(attempts, error, url2);
if (retryDelayInSeconds === null) {
throw error;
}
console.error("Retrying failed fetch. Error:", error);
if (!Number.isFinite(retryDelayInSeconds) || retryDelayInSeconds < 0) {
throw new TypeError("Retry delay must be a non-negative finite number.");
}
if (retryDelayInSeconds > 0) {
await wait(1e3 * retryDelayInSeconds);
}
if (shouldStop()) {
throw error;
}
}
}
};
var computeRationalApproximation = (x, maxDenominator) => {
const sign = x < 0 ? -1 : 1;
x = Math.abs(x);
let prevNumerator = 0, prevDenominator = 1;
let currNumerator = 1, currDenominator = 0;
let remainder = x;
while (true) {
const integer = Math.floor(remainder);
const nextNumerator = integer * currNumerator + prevNumerator;
const nextDenominator = integer * currDenominator + prevDenominator;
if (nextDenominator > maxDenominator) {
return {
num: sign * currNumerator,
den: currDenominator
};
}
prevNumerator = currNumerator;
prevDenominator = currDenominator;
currNumerator = nextNumerator;
currDenominator = nextDenominator;
remainder = 1 / (remainder - integer);
if (!isFinite(remainder)) {
break;
}
}
return {
num: sign * currNumerator,
den: currDenominator
};
};
var CallSerializer = class {
constructor() {
this.currentPromise = Promise.resolve();
}
call(fn) {
return this.currentPromise = this.currentPromise.then(fn);
}
};
var isWebKitCache = null;
var isWebKit = () => {
if (isWebKitCache !== null) {
return isWebKitCache;
}
return isWebKitCache = !!(typeof navigator !== "undefined" && // eslint-disable-next-line @typescript-eslint/no-deprecated
(navigator.vendor?.match(/apple/i) || /AppleWebKit/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) || /\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent)));
};
var isFirefoxCache = null;
var isFirefox = () => {
if (isFirefoxCache !== null) {
return isFirefoxCache;
}
return isFirefoxCache = typeof navigator !== "undefined" && navigator.userAgent?.includes("Firefox");
};
var isChromiumCache = null;
var isChromium = () => {
if (isChromiumCache !== null) {
return isChromiumCache;
}
return isChromiumCache = !!(typeof navigator !== "undefined" && (navigator.vendor?.includes("Google Inc") || /Chrome/.test(navigator.userAgent)));
};
var chromiumVersionCache = null;
var getChromiumVersion = () => {
if (chromiumVersionCache !== null) {
return chromiumVersionCache;
}
if (typeof navigator === "undefined") {
return null;
}
const match = /\bChrome\/(\d+)/.exec(navigator.userAgent);
if (!match) {
return null;
}
return chromiumVersionCache = Number(match[1]);
};
var coalesceIndex = (a, b) => {
return a !== -1 ? a : b;
};
var closedIntervalsOverlap = (startA, endA, startB, endB) => {
return startA <= endB && startB <= endA;
};
var keyValueIterator = function* (object) {
for (const key in object) {
const value = object[key];
if (value === void 0) {
continue;
}
yield { key, value };
}
};
var imageMimeTypeToExtension = (mimeType) => {
switch (mimeType.toLowerCase()) {
case "image/jpeg":
case "image/jpg":
return ".jpg";
case "image/png":
return ".png";
case "image/gif":
return ".gif";
case "image/webp":
return ".webp";
case "image/bmp":
return ".bmp";
case "image/svg+xml":
return ".svg";
case "image/tiff":
return ".tiff";
case "image/avif":
return ".avif";
case "image/x-icon":
case "image/vnd.microsoft.icon":
return ".ico";
default:
return null;
}
};
var base64ToBytes = (base64) => {
const decoded = atob(base64);
const bytes2 = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) {
bytes2[i] = decoded.charCodeAt(i);
}
return bytes2;
};
var bytesToBase64 = (bytes2) => {
let string = "";
for (let i = 0; i < bytes2.length; i++) {
string += String.fromCharCode(bytes2[i]);
}
return btoa(string);
};
var uint8ArraysAreEqual = (a, b) => {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
};
var polyfillSymbolDispose = () => {
Symbol.dispose ??= Symbol("Symbol.dispose");
};
var isNumber = (x) => {
return typeof x === "number" && !Number.isNaN(x);
};
var joinPaths = (basePath, relativePath) => {
if (relativePath.includes("://")) {
return relativePath;
}
if (basePath.includes("://")) {
const queryIndex = basePath.indexOf("?");
if (queryIndex !== -1) {
basePath = basePath.slice(0, queryIndex);
}
}
let result;
if (relativePath.startsWith("/")) {
const protocolIndex2 = basePath.indexOf("://");
if (protocolIndex2 === -1) {
result = relativePath;
} else {
const pathStart = basePath.indexOf("/", protocolIndex2 + 3);
if (pathStart === -1) {
result = basePath + relativePath;
} else {
result = basePath.slice(0, pathStart) + relativePath;
}
}
} else {
const lastSlash = basePath.lastIndexOf("/");
if (lastSlash === -1) {
result = relativePath;
} else {
result = basePath.slice(0, lastSlash + 1) + relativePath;
}
}
let prefix = "";
const protocolIndex = result.indexOf("://");
if (protocolIndex !== -1) {
const pathStart = result.indexOf("/", protocolIndex + 3);
if (pathStart !== -1) {
prefix = result.slice(0, pathStart);
result = result.slice(pathStart);
}
}
const segments = result.split("/");
const normalized = [];
for (const segment of segments) {
if (segment === "..") {
normalized.pop();
} else if (segment !== ".") {
normalized.push(segment);
}
}
return prefix + normalized.join("/");
};
var arrayCount = (array, predicate) => {
let count = 0;
for (let i = 0; i < array.length; i++) {
if (predicate(array[i])) {
count++;
}
}
return count;
};
var arrayArgmin = (array, getValue) => {
let minIndex = -1;
let minValue = Infinity;
for (let i = 0; i < array.length; i++) {
const value = getValue(array[i]);
if (value < minValue) {
minValue = value;
minIndex = i;
}
}
return minIndex;
};
var arrayArgmax = (array, getValue) => {
let maxIndex = -1;
let maxValue = -Infinity;
for (let i = 0; i < array.length; i++) {
const value = getValue(array[i]);
if (value > maxValue) {
maxValue = value;
maxIndex = i;
}
}
return maxIndex;
};
var simplifyRational = (rational) => {
assert(Number.isInteger(rational.num));
assert(Number.isInteger(rational.den));
assert(rational.den !== 0);
let a = Math.abs(rational.num);
let b = Math.abs(rational.den);
while (b !== 0) {
const t = a % b;
a = b;
b = t;
}
const gcd = a || 1;
return {
num: rational.num / gcd,
den: rational.den / gcd
};
};
var validateRectangle = (rect, propertyPath) => {
if (typeof rect !== "object" || !rect) {
throw new TypeError(`${propertyPath} must be an object.`);
}
if (!Number.isInteger(rect.left) || rect.left < 0) {
throw new TypeError(`${propertyPath}.left must be a non-negative integer.`);
}
if (!Number.isInteger(rect.top) || rect.top < 0) {
throw new TypeError(`${propertyPath}.top must be a non-negative integer.`);
}
if (!Number.isInteger(rect.width) || rect.width < 0) {
throw new TypeError(`${propertyPath}.width must be a non-negative integer.`);
}
if (!Number.isInteger(rect.height) || rect.height < 0) {
throw new TypeError(`${propertyPath}.height must be a non-negative integer.`);
}
};
var unthrottledTimerWorker;
var nextUnthrottledTimerId = 1;
var unthrottledTimeoutCallbacks = /* @__PURE__ */ new Map();
var unthrottledIntervalCallbacks = /* @__PURE__ */ new Map();
var shouldUseNativeTimers = () => {
return typeof window === "undefined";
};
var unthrottledTimerWorkerMain = () => {
const timeoutHandles = /* @__PURE__ */ new Map();
const intervalHandles = /* @__PURE__ */ new Map();
self.onmessage = (event) => {
const message = event.data;
switch (message.type) {
case "set-timeout":
{
const handle = setTimeout(() => {
timeoutHandles.delete(message.timerId);
self.postMessage({ type: "fire", timerId: message.timerId });
}, message.delay);
timeoutHandles.set(message.timerId, handle);
}
;
break;
case "set-interval":
{
const handle = setInterval(() => {
self.postMessage({ type: "fire", timerId: message.timerId });
}, message.delay);
intervalHandles.set(message.timerId, handle);
}
;
break;
case "clear-timeout":
{
const handle = timeoutHandles.get(message.timerId);
if (handle !== void 0) {
clearTimeout(handle);
timeoutHandles.delete(message.timerId);
}
}
;
break;
case "clear-interval":
{
const handle = intervalHandles.get(message.timerId);
if (handle !== void 0) {
clearInterval(handle);
intervalHandles.delete(message.timerId);
}
}
;
break;
}
};
};
var getUnthrottledTimerWorker = () => {
if (unthrottledTimerWorker) {
return unthrottledTimerWorker;
}
const workerSource = `(${unthrottledTimerWorkerMain.toString()})();`;
const workerURL = URL.createObjectURL(new Blob([workerSource], { type: "text/javascript" }));
unthrottledTimerWorker = new Worker(workerURL);
URL.revokeObjectURL(workerURL);
unthrottledTimerWorker.onmessage = (event) => {
const message = event.data;
const timeoutCallback = unthrottledTimeoutCallbacks.get(message.timerId);
if (timeoutCallback) {
unthrottledTimeoutCallbacks.delete(message.timerId);
timeoutCallback();
return;
}
const intervalCallback = unthrottledIntervalCallbacks.get(message.timerId);
if (intervalCallback) {
intervalCallback();
}
};
return unthrottledTimerWorker;
};
var setIntervalUnthrottled = (callback, delay) => {
if (shouldUseNativeTimers()) {
return { id: setInterval(callback, delay) };
}
const timerId = nextUnthrottledTimerId++;
unthrottledIntervalCallbacks.set(timerId, () => {
callback();
});
getUnthrottledTimerWorker().postMessage({
type: "set-interval",
timerId,
delay
});
return { id: timerId };
};
var clearIntervalUnthrottled = (timer) => {
if (shouldUseNativeTimers()) {
clearInterval(timer.id);
return;
}
assert(typeof timer.id === "number");
unthrottledIntervalCallbacks.delete(timer.id);
getUnthrottledTimerWorker().postMessage({
type: "clear-interval",
timerId: timer.id
});
};
var wait = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
var toArray = (x) => {
if (Array.isArray(x)) {
return x;
} else {
return [x];
}
};
var EventEmitter = class {
constructor() {
/** @internal */
this._listeners = /* @__PURE__ */ new Map();
}
/** Registers a listener for the given event. */
on(event, listener, options) {
if (!this._listeners.has(event)) {
this._listeners.set(event, /* @__PURE__ */ new Set());
}
const entry = { fn: listener, once: options?.once ?? false };
this._listeners.get(event).add(entry);
return () => {
this._listeners.get(event)?.delete(entry);
};
}
/** @internal */
_emit(...args) {
const [event, data] = args;
const listeners = this._listeners.get(event);
if (!listeners) {
return;
}
for (const entry of listeners) {
try {
entry.fn(data);
} catch (error) {
console.error(error);
}
if (entry.once) {
listeners.delete(entry);
}
}
}
};
var ceilToMultipleOfTwo = (value) => Math.ceil(value / 2) * 2;
var ConcurrentRunner = class {
constructor(parallelism) {
/** @internal */
this._queue = [];
/** @internal */
this._errored = false;
this.parallelism = parallelism;
}
/** Whether any function has errored. The runner is effectively bricked if this is `true`, by design. */
get errored() {
return this._errored;
}
/** The number of tasks currently running. */
get inFlightCount() {
return this._queue.length;
}
/**
* Schedules an async function to be run. If the maximum allowed level of parallelism has not yet been reached,
* the function will be executed immediately and `run()` will resolve immediately. Otherwise, the function will be
* called as soon as any currently-running function finishes, and `run()` will only resolve then.
*
* Throws if the runner is errored.
*/
async run(fn) {
if (this._errored) {
await Promise.race(this._queue);
}
while (this._queue.length >= this.parallelism) {
await Promise.race(this._queue);
}
const promise = fn();
this._queue.push(promise);
void promise.then(() => removeItem(this._queue, promise)).catch(() => this._errored = true);
}
/** Waits for all currently running functions to finish. Throws if the runner is errored. */
async flush() {
await Promise.all(this._queue);
}
};
var isRecordStringString = (value) => {
return value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype && Object.values(value).every((x) => typeof x === "string");
};
// src/metadata.ts
var RichImageData = class {
/** Creates a new {@link RichImageData}. */
constructor(data, mimeType) {
this.data = data;
this.mimeType = mimeType;
if (!(data instanceof Uint8Array)) {
throw new TypeError("data must be a Uint8Array.");
}
if (typeof mimeType !== "string") {
throw new TypeError("mimeType must be a string.");
}
}
};
var AttachedFile = class {
/** Creates a new {@link AttachedFile}. */
constructor(data, mimeType, name, description) {
this.data = data;
this.mimeType = mimeType;
this.name = name;
this.description = description;
if (!(data instanceof Uint8Array)) {
throw new TypeError("data must be a Uint8Array.");
}
if (mimeType !== void 0 && typeof mimeType !== "string") {
throw new TypeError("mimeType, when provided, must be a string.");
}
if (name !== void 0 && typeof name !== "string") {
throw new TypeError("name, when provided, must be a string.");
}
if (description !== void 0 && typeof description !== "string") {
throw new TypeError("description, when provided, must be a string.");
}
}
};
var validateMetadataTags = (tags) => {
if (!tags || typeof tags !== "object") {
throw new TypeError("tags must be an object.");
}
if (tags.title !== void 0 && typeof tags.title !== "string") {
throw new TypeError("tags.title, when provided, must be a string.");
}
if (tags.description !== void 0 && typeof tags.description !== "string") {
throw new TypeError("tags.description, when provided, must be a string.");
}
if (tags.artist !== void 0 && typeof tags.artist !== "string") {
throw new TypeError("tags.artist, when provided, must be a string.");
}
if (tags.album !== void 0 && typeof tags.album !== "string") {
throw new TypeError("tags.album, when provided, must be a string.");
}
if (tags.albumArtist !== void 0 && typeof tags.albumArtist !== "string") {
throw new TypeError("tags.albumArtist, when provided, must be a string.");
}
if (tags.trackNumber !== void 0 && (!Number.isInteger(tags.trackNumber) || tags.trackNumber <= 0)) {
throw new TypeError("tags.trackNumber, when provided, must be a positive integer.");
}
if (tags.tracksTotal !== void 0 && (!Number.isInteger(tags.tracksTotal) || tags.tracksTotal <= 0)) {
throw new TypeError("tags.tracksTotal, when provided, must be a positive integer.");
}
if (tags.discNumber !== void 0 && (!Number.isInteger(tags.discNumber) || tags.discNumber <= 0)) {
throw new TypeError("tags.discNumber, when provided, must be a positive integer.");
}
if (tags.discsTotal !== void 0 && (!Number.isInteger(tags.discsTotal) || tags.discsTotal <= 0)) {
throw new TypeError("tags.discsTotal, when provided, must be a positive integer.");
}
if (tags.genre !== void 0 && typeof tags.genre !== "string") {
throw new TypeError("tags.genre, when provided, must be a string.");
}
if (tags.date !== void 0 && (!(tags.date instanceof Date) || Number.isNaN(tags.date.getTime()))) {
throw new TypeError("tags.date, when provided, must be a valid Date.");
}
if (tags.lyrics !== void 0 && typeof tags.lyrics !== "string") {
throw new TypeError("tags.lyrics, when provided, must be a string.");
}
if (tags.images !== void 0) {
if (!Array.isArray(tags.images)) {
throw new TypeError("tags.images, when provided, must be an array.");
}
for (const image of tags.images) {
if (!image || typeof image !== "object") {
throw new TypeError("Each image in tags.images must be an object.");
}
if (!(image.data instanceof Uint8Array)) {
throw new TypeError("Each image.data must be a Uint8Array.");
}
if (typeof image.mimeType !== "string") {
throw new TypeError("Each image.mimeType must be a string.");
}
if (!["coverFront", "coverBack", "unknown"].includes(image.kind)) {
throw new TypeError("Each image.kind must be 'coverFront', 'coverBack', or 'unknown'.");
}
}
}
if (tags.comment !== void 0 && typeof tags.comment !== "string") {
throw new TypeError("tags.comment, when provided, must be a string.");
}
if (tags.raw !== void 0) {
if (!tags.raw || typeof tags.raw !== "object") {
throw new TypeError("tags.raw, when provided, must be an object.");
}
for (const value of Object.values(tags.raw)) {
if (value !== null && typeof value !== "string" && !(value instanceof Uint8Array) && !(value instanceof RichImageData) && !(value instanceof AttachedFile) && !isRecordStringString(value)) {
throw new TypeError(
"Each value in tags.raw must be a string, Uint8Array, RichImageData, AttachedFile, Record<string, string>, or null."
);
}
}
}
};
var metadataTagsAreEmpty = (tags) => {
return tags.title === void 0 && tags.description === void 0 && tags.artist === void 0 && tags.album === void 0 && tags.albumArtist === void 0 && tags.trackNumber === void 0 && tags.tracksTotal === void 0 && tags.discNumber === void 0 && tags.discsTotal === void 0 && tags.genre === void 0 && tags.date === void 0 && tags.lyrics === void 0 && (!tags.images || tags.images.length === 0) && tags.comment === void 0 && (tags.raw === void 0 || Object.keys(tags.raw).length === 0);
};
var DEFAULT_TRACK_DISPOSITION = {
default: true,
primary: true,
forced: false,
original: false,
commentary: false,
hearingImpaired: false,
visuallyImpaired: false
};
var validateTrackDisposition = (disposition) => {
if (!disposition || typeof disposition !== "object") {
throw new TypeError("disposition must be an object.");
}
if (disposition.default !== void 0 && typeof disposition.default !== "boolean") {
throw new TypeError("disposition.default must be a boolean.");
}
if (disposition.primary !== void 0 && typeof disposition.primary !== "boolean") {
throw new TypeError("disposition.primary must be a boolean.");
}
if (disposition.forced !== void 0 && typeof disposition.forced !== "boolean") {
throw new TypeError("disposition.forced must be a boolean.");
}
if (disposition.original !== void 0 && typeof disposition.original !== "boolean") {
throw new TypeError("disposition.original must be a boolean.");
}
if (disposition.commentary !== void 0 && typeof disposition.commentary !== "boolean") {
throw new TypeError("disposition.commentary must be a boolean.");
}
if (disposition.hearingImpaired !== void 0 && typeof disposition.hearingImpaired !== "boolean") {
throw new TypeError("disposition.hearingImpaired must be a boolean.");
}
if (disposition.visuallyImpaired !== void 0 && typeof disposition.visuallyImpaired !== "boolean") {
throw new TypeError("disposition.visuallyImpaired must be a boolean.");
}
};
// shared/bitstream.ts
var Bitstream = class _Bitstream {
constructor(bytes2) {
this.bytes = bytes2;
/** Current offset in bits. */
this.pos = 0;
}
seekToByte(byteOffset) {
this.pos = 8 * byteOffset;
}
readBit() {
const byteIndex = Math.floor(this.pos / 8);
const byte = this.bytes[byteIndex] ?? 0;
const bitIndex = 7 - (this.pos & 7);
const bit = (byte & 1 << bitIndex) >> bitIndex;
this.pos++;
return bit;
}
readBits(n) {
if (n === 1) {
return this.readBit();
}
let result = 0;
for (let i = 0; i < n; i++) {
result <<= 1;
result |= this.readBit();
}
return result;
}
writeBits(n, value) {
const end = this.pos + n;
for (let i = this.pos; i < end; i++) {
const byteIndex = Math.floor(i / 8);
let byte = this.bytes[byteIndex];
const bitIndex = 7 - (i & 7);
byte &= ~(1 << bitIndex);
byte |= (value & 1 << end - i - 1) >> end - i - 1 << bitIndex;
this.bytes[byteIndex] = byte;
}
this.pos = end;
}
readAlignedByte() {
if (this.pos % 8 !== 0) {
throw new Error("Bitstream is not byte-aligned.");
}
const byteIndex = this.pos / 8;
const byte = this.bytes[byteIndex] ?? 0;
this.pos += 8;
return byte;
}
skipBits(n) {
this.pos += n;
}
getBitsLeft() {
return this.bytes.length * 8 - this.pos;
}
clone() {
const clone = new _Bitstream(this.bytes);
clone.pos = this.pos;
return clone;
}
};
// shared/aac-misc.ts
var aacFrequencyTable = [
96e3,
88200,
64e3,
48e3,
44100,
32e3,
24e3,
22050,
16e3,
12e3,
11025,
8e3,
7350
];
var aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
var parseAacAudioSpecificConfig = (bytes2) => {
if (!bytes2 || bytes2.byteLength < 2) {
throw new TypeError("AAC description must be at least 2 bytes long.");
}
const bitstream = new Bitstream(bytes2);
let objectType = bitstream.readBits(5);
if (objectType === 31) {
objectType = 32 + bitstream.readBits(6);
}
const frequencyIndex = bitstream.readBits(4);
let sampleRate = null;
if (frequencyIndex === 15) {
sampleRate = bitstream.readBits(24);
} else {
if (frequencyIndex < aacFrequencyTable.length) {
sampleRate = aacFrequencyTable[frequencyIndex];
}
}
const channelConfiguration = bitstream.readBits(4);
let numberOfChannels = null;
if (channelConfiguration >= 1 && channelConfiguration <= 7) {
numberOfChannels = aacChannelMap[channelConfiguration];
}
return {
objectType,
frequencyIndex,
sampleRate,
channelConfiguration,
numberOfChannels
};
};
var buildAacAudioSpecificConfig = (config) => {
let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
let customSampleRate = null;
if (frequencyIndex === -1) {
frequencyIndex = 15;
customSampleRate = config.sampleRate;
}
const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
if (channelConfiguration === -1) {
throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
}
let bitCount = 5 + 4 + 4;
if (config.objectType >= 32) {
bitCount += 6;
}
if (frequencyIndex === 15) {
bitCount += 24;
}
const byteCount = Math.ceil(bitCount / 8);
const bytes2 = new Uint8Array(byteCount);
const bitstream = new Bitstream(bytes2);
if (config.objectType < 32) {
bitstream.writeBits(5, config.objectType);
} else {
bitstream.writeBits(5, 31);
bitstream.writeBits(6, config.objectType - 32);
}
bitstream.writeBits(4, frequencyIndex);
if (frequencyIndex === 15) {
bitstream.writeBits(24, customSampleRate);
}
bitstream.writeBits(4, channelConfiguration);
return bytes2;
};
var buildAdtsHeaderTemplate = (config) => {
const header = new Uint8Array(7);
const bitstream = new Bitstream(header);
const { objectType, frequencyIndex, channelConfiguration } = config;
const profile = objectType - 1;
bitstream.writeBits(12, 4095);
bitstream.writeBits(1, 0);
bitstream.writeBits(2, 0);
bitstream.writeBits(1, 1);
bitstream.writeBits(2, profile);
bitstream.writeBits(4, frequencyIndex);
bitstream.writeBits(1, 0);
bitstream.writeBits(3, channelConfiguration);
bitstream.writeBits(1, 0);
bitstream.writeBits(1, 0);
bitstream.writeBits(1, 0);
bitstream.writeBits(1, 0);
bitstream.skipBits(13);
bitstream.writeBits(11, 2047);
bitstream.writeBits(2, 0);
return { header, bitstream };
};
var writeAdtsFrameLength = (bitstream, frameLength) => {
bitstream.pos = 30;
bitstream.writeBits(13, frameLength);
};
// src/codec.ts
var VIDEO_CODECS = [
"avc",
"hevc",
"vp9",
"av1",
"vp8"
];
var PCM_AUDIO_CODECS = [
"pcm-s16",
// We don't prefix 'le' so we're compatible with the WebCodecs-registered PCM codec strings
"pcm-s16be",
"pcm-s24",
"pcm-s24be",
"pcm-s32",
"pcm-s32be",
"pcm-f32",
"pcm-f32be",
"pcm-f64",
"pcm-f64be",
"pcm-u8",
"pcm-s8",
"ulaw",
"alaw"
];
var NON_PCM_AUDIO_CODECS = [
"aac",
"opus",
"mp3",
"vorbis",
"flac",
"ac3",
"eac3"
];
var AUDIO_CODECS = [
...NON_PCM_AUDIO_CODECS,
...PCM_AUDIO_CODECS
];
var SUBTITLE_CODECS = [
"webvtt"
];
var AVC_LEVEL_TABLE = [
{ maxMacroblocks: 99, maxBitrate: 64e3, maxDpbMbs: 396, level: 10 },
// Level 1
{ maxMacroblocks: 396, maxBitrate: 192e3, maxDpbMbs: 900, level: 11 },
// Level 1.1
{ maxMacroblocks: 396, maxBitrate: 384e3, maxDpbMbs: 2376, level: 12 },
// Level 1.2
{ maxMacroblocks: 396, maxBitrate: 768e3, maxDpbMbs: 2376, level: 13 },
// Level 1.3
{ maxMacroblocks: 396, maxBitrate: 2e6, maxDpbMbs: 2376, level: 20 },
// Level 2
{ maxMacroblocks: 792, maxBitrate: 4e6, maxDpbMbs: 475