@kenzuya/mediabunny 1.28.5 → 1.28.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundles/{mediabunny.js → mediabunny.browser.js} +6 -8
- package/dist/bundles/{mediabunny.min.js → mediabunny.browser.min.js} +36 -36
- package/dist/bundles/mediabunny.node.js +26704 -0
- package/dist/bundles/mediabunny.node.min.js +490 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/packages/eac3/build/eac3.d.ts +3 -0
- package/dist/packages/eac3/build/eac3.d.ts.map +1 -0
- package/dist/packages/eac3/mediabunny-eac3.node.js +1058 -0
- package/dist/packages/eac3/mediabunny-eac3.node.min.js +44 -0
- package/dist/packages/eac3/src/decode.worker.d.ts +9 -0
- package/dist/packages/eac3/src/decode.worker.d.ts.map +1 -0
- package/dist/packages/eac3/src/eac3-loader.d.ts +12 -0
- package/dist/packages/eac3/src/eac3-loader.d.ts.map +1 -0
- package/dist/packages/eac3/src/encode.worker.d.ts +9 -0
- package/dist/packages/eac3/src/encode.worker.d.ts.map +1 -0
- package/dist/packages/eac3/src/index.d.ts +26 -0
- package/dist/packages/eac3/src/index.d.ts.map +1 -0
- package/dist/packages/eac3/src/shared.d.ts +67 -0
- package/dist/packages/eac3/src/shared.d.ts.map +1 -0
- package/dist/packages/mp3-encoder/build/lame.d.ts +3 -0
- package/dist/packages/mp3-encoder/build/lame.d.ts.map +1 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.node.js +689 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.node.min.js +58 -0
- package/dist/{modules/src/node.d.ts → packages/mp3-encoder/src/encode.worker.d.ts} +2 -2
- package/dist/packages/mp3-encoder/src/encode.worker.d.ts.map +1 -0
- package/dist/packages/mp3-encoder/src/index.d.ts +27 -0
- package/dist/packages/mp3-encoder/src/index.d.ts.map +1 -0
- package/dist/packages/mp3-encoder/src/shared.d.ts +40 -0
- package/dist/packages/mp3-encoder/src/shared.d.ts.map +1 -0
- package/dist/packages/mpeg4/build/xvid.d.ts +3 -0
- package/dist/packages/mpeg4/build/xvid.d.ts.map +1 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.node.js +1198 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.node.min.js +44 -0
- package/dist/packages/mpeg4/src/decode.worker.d.ts +9 -0
- package/dist/packages/mpeg4/src/decode.worker.d.ts.map +1 -0
- package/dist/packages/mpeg4/src/encode.worker.d.ts +9 -0
- package/dist/packages/mpeg4/src/encode.worker.d.ts.map +1 -0
- package/dist/packages/mpeg4/src/index.d.ts +49 -0
- package/dist/packages/mpeg4/src/index.d.ts.map +1 -0
- package/dist/packages/mpeg4/src/shared.d.ts +39 -0
- package/dist/packages/mpeg4/src/shared.d.ts.map +1 -0
- package/dist/packages/mpeg4/src/xvid-loader.d.ts +12 -0
- package/dist/packages/mpeg4/src/xvid-loader.d.ts.map +1 -0
- package/dist/shared/mp3-misc.d.ts.map +1 -0
- package/dist/src/adts/adts-demuxer.d.ts.map +1 -0
- package/dist/src/adts/adts-muxer.d.ts.map +1 -0
- package/dist/src/adts/adts-reader.d.ts.map +1 -0
- package/dist/src/avi/avi-demuxer.d.ts.map +1 -0
- package/dist/src/avi/avi-misc.d.ts.map +1 -0
- package/dist/src/avi/avi-muxer.d.ts.map +1 -0
- package/dist/src/avi/riff-writer.d.ts.map +1 -0
- package/dist/src/codec-data.d.ts.map +1 -0
- package/dist/src/codec.d.ts.map +1 -0
- package/dist/src/conversion.d.ts.map +1 -0
- package/dist/src/custom-coder.d.ts.map +1 -0
- package/dist/src/demuxer.d.ts.map +1 -0
- package/dist/src/encode.d.ts.map +1 -0
- package/dist/src/flac/flac-demuxer.d.ts.map +1 -0
- package/dist/src/flac/flac-misc.d.ts.map +1 -0
- package/dist/src/flac/flac-muxer.d.ts.map +1 -0
- package/dist/src/id3.d.ts.map +1 -0
- package/dist/src/index.d.ts +30 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/input-format.d.ts.map +1 -0
- package/dist/src/input-track.d.ts.map +1 -0
- package/dist/src/input.d.ts.map +1 -0
- package/dist/src/isobmff/isobmff-boxes.d.ts.map +1 -0
- package/dist/src/isobmff/isobmff-demuxer.d.ts.map +1 -0
- package/dist/src/isobmff/isobmff-misc.d.ts.map +1 -0
- package/dist/src/isobmff/isobmff-muxer.d.ts.map +1 -0
- package/dist/src/isobmff/isobmff-reader.d.ts.map +1 -0
- package/dist/src/matroska/ebml.d.ts.map +1 -0
- package/dist/src/matroska/matroska-demuxer.d.ts.map +1 -0
- package/dist/src/matroska/matroska-input.d.ts.map +1 -0
- package/dist/src/matroska/matroska-misc.d.ts.map +1 -0
- package/dist/src/matroska/matroska-muxer.d.ts.map +1 -0
- package/dist/src/media-sink.d.ts.map +1 -0
- package/dist/src/media-source.d.ts.map +1 -0
- package/dist/src/metadata.d.ts.map +1 -0
- package/dist/src/misc.d.ts.map +1 -0
- package/dist/src/mp3/mp3-demuxer.d.ts.map +1 -0
- package/dist/src/mp3/mp3-muxer.d.ts.map +1 -0
- package/dist/src/mp3/mp3-reader.d.ts.map +1 -0
- package/dist/src/mp3/mp3-writer.d.ts.map +1 -0
- package/dist/src/muxer.d.ts.map +1 -0
- package/dist/src/ogg/ogg-demuxer.d.ts.map +1 -0
- package/dist/src/ogg/ogg-misc.d.ts.map +1 -0
- package/dist/src/ogg/ogg-muxer.d.ts.map +1 -0
- package/dist/src/ogg/ogg-reader.d.ts.map +1 -0
- package/dist/src/output-format.d.ts.map +1 -0
- package/dist/src/output.d.ts.map +1 -0
- package/dist/src/packet.d.ts.map +1 -0
- package/dist/src/pcm.d.ts.map +1 -0
- package/dist/src/reader.d.ts.map +1 -0
- package/dist/src/sample.d.ts.map +1 -0
- package/dist/src/source.d.ts.map +1 -0
- package/dist/src/subtitles.d.ts.map +1 -0
- package/dist/src/target.d.ts.map +1 -0
- package/dist/src/tsconfig.tsbuildinfo +1 -0
- package/dist/src/wave/riff-writer.d.ts.map +1 -0
- package/dist/src/wave/wave-demuxer.d.ts.map +1 -0
- package/dist/src/wave/wave-muxer.d.ts.map +1 -0
- package/dist/src/writer.d.ts.map +1 -0
- package/package.json +44 -8
- package/dist/modules/shared/mp3-misc.d.ts.map +0 -1
- package/dist/modules/src/adts/adts-demuxer.d.ts.map +0 -1
- package/dist/modules/src/adts/adts-muxer.d.ts.map +0 -1
- package/dist/modules/src/adts/adts-reader.d.ts.map +0 -1
- package/dist/modules/src/avi/avi-demuxer.d.ts.map +0 -1
- package/dist/modules/src/avi/avi-misc.d.ts.map +0 -1
- package/dist/modules/src/avi/avi-muxer.d.ts.map +0 -1
- package/dist/modules/src/avi/riff-writer.d.ts.map +0 -1
- package/dist/modules/src/codec-data.d.ts.map +0 -1
- package/dist/modules/src/codec.d.ts.map +0 -1
- package/dist/modules/src/conversion.d.ts.map +0 -1
- package/dist/modules/src/custom-coder.d.ts.map +0 -1
- package/dist/modules/src/demuxer.d.ts.map +0 -1
- package/dist/modules/src/encode.d.ts.map +0 -1
- package/dist/modules/src/flac/flac-demuxer.d.ts.map +0 -1
- package/dist/modules/src/flac/flac-misc.d.ts.map +0 -1
- package/dist/modules/src/flac/flac-muxer.d.ts.map +0 -1
- package/dist/modules/src/id3.d.ts.map +0 -1
- package/dist/modules/src/index.d.ts.map +0 -1
- package/dist/modules/src/input-format.d.ts.map +0 -1
- package/dist/modules/src/input-track.d.ts.map +0 -1
- package/dist/modules/src/input.d.ts.map +0 -1
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +0 -1
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +0 -1
- package/dist/modules/src/isobmff/isobmff-misc.d.ts.map +0 -1
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +0 -1
- package/dist/modules/src/isobmff/isobmff-reader.d.ts.map +0 -1
- package/dist/modules/src/matroska/ebml.d.ts.map +0 -1
- package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +0 -1
- package/dist/modules/src/matroska/matroska-input.d.ts.map +0 -1
- package/dist/modules/src/matroska/matroska-misc.d.ts.map +0 -1
- package/dist/modules/src/matroska/matroska-muxer.d.ts.map +0 -1
- package/dist/modules/src/media-sink.d.ts.map +0 -1
- package/dist/modules/src/media-source.d.ts.map +0 -1
- package/dist/modules/src/metadata.d.ts.map +0 -1
- package/dist/modules/src/misc.d.ts.map +0 -1
- package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +0 -1
- package/dist/modules/src/mp3/mp3-muxer.d.ts.map +0 -1
- package/dist/modules/src/mp3/mp3-reader.d.ts.map +0 -1
- package/dist/modules/src/mp3/mp3-writer.d.ts.map +0 -1
- package/dist/modules/src/muxer.d.ts.map +0 -1
- package/dist/modules/src/node.d.ts.map +0 -1
- package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +0 -1
- package/dist/modules/src/ogg/ogg-misc.d.ts.map +0 -1
- package/dist/modules/src/ogg/ogg-muxer.d.ts.map +0 -1
- package/dist/modules/src/ogg/ogg-reader.d.ts.map +0 -1
- package/dist/modules/src/output-format.d.ts.map +0 -1
- package/dist/modules/src/output.d.ts.map +0 -1
- package/dist/modules/src/packet.d.ts.map +0 -1
- package/dist/modules/src/pcm.d.ts.map +0 -1
- package/dist/modules/src/reader.d.ts.map +0 -1
- package/dist/modules/src/sample.d.ts.map +0 -1
- package/dist/modules/src/source.d.ts.map +0 -1
- package/dist/modules/src/subtitles.d.ts.map +0 -1
- package/dist/modules/src/target.d.ts.map +0 -1
- package/dist/modules/src/tsconfig.tsbuildinfo +0 -1
- package/dist/modules/src/wave/riff-writer.d.ts.map +0 -1
- package/dist/modules/src/wave/wave-demuxer.d.ts.map +0 -1
- package/dist/modules/src/wave/wave-muxer.d.ts.map +0 -1
- package/dist/modules/src/writer.d.ts.map +0 -1
- /package/dist/{modules/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/packages/eac3/{mediabunny-eac3.js → mediabunny-eac3.browser.js} +0 -0
- /package/dist/packages/eac3/{mediabunny-eac3.min.js → mediabunny-eac3.browser.min.js} +0 -0
- /package/dist/packages/mp3-encoder/{mediabunny-mp3-encoder.js → mediabunny-mp3-encoder.browser.js} +0 -0
- /package/dist/packages/mp3-encoder/{mediabunny-mp3-encoder.min.js → mediabunny-mp3-encoder.browser.min.js} +0 -0
- /package/dist/packages/mpeg4/{mediabunny-mpeg4.js → mediabunny-mpeg4.browser.js} +0 -0
- /package/dist/packages/mpeg4/{mediabunny-mpeg4.min.js → mediabunny-mpeg4.browser.min.js} +0 -0
- /package/dist/{modules/shared → shared}/mp3-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/adts/adts-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/adts/adts-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/adts/adts-reader.d.ts +0 -0
- /package/dist/{modules/src → src}/avi/avi-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/avi/avi-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/avi/avi-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/avi/riff-writer.d.ts +0 -0
- /package/dist/{modules/src → src}/codec-data.d.ts +0 -0
- /package/dist/{modules/src → src}/codec.d.ts +0 -0
- /package/dist/{modules/src → src}/conversion.d.ts +0 -0
- /package/dist/{modules/src → src}/custom-coder.d.ts +0 -0
- /package/dist/{modules/src → src}/demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/encode.d.ts +0 -0
- /package/dist/{modules/src → src}/flac/flac-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/flac/flac-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/flac/flac-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/id3.d.ts +0 -0
- /package/dist/{modules/src → src}/input-format.d.ts +0 -0
- /package/dist/{modules/src → src}/input-track.d.ts +0 -0
- /package/dist/{modules/src → src}/input.d.ts +0 -0
- /package/dist/{modules/src → src}/isobmff/isobmff-boxes.d.ts +0 -0
- /package/dist/{modules/src → src}/isobmff/isobmff-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/isobmff/isobmff-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/isobmff/isobmff-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/isobmff/isobmff-reader.d.ts +0 -0
- /package/dist/{modules/src → src}/matroska/ebml.d.ts +0 -0
- /package/dist/{modules/src → src}/matroska/matroska-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/matroska/matroska-input.d.ts +0 -0
- /package/dist/{modules/src → src}/matroska/matroska-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/matroska/matroska-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/media-sink.d.ts +0 -0
- /package/dist/{modules/src → src}/media-source.d.ts +0 -0
- /package/dist/{modules/src → src}/metadata.d.ts +0 -0
- /package/dist/{modules/src → src}/misc.d.ts +0 -0
- /package/dist/{modules/src → src}/mp3/mp3-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/mp3/mp3-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/mp3/mp3-reader.d.ts +0 -0
- /package/dist/{modules/src → src}/mp3/mp3-writer.d.ts +0 -0
- /package/dist/{modules/src → src}/muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/ogg/ogg-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/ogg/ogg-misc.d.ts +0 -0
- /package/dist/{modules/src → src}/ogg/ogg-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/ogg/ogg-reader.d.ts +0 -0
- /package/dist/{modules/src → src}/output-format.d.ts +0 -0
- /package/dist/{modules/src → src}/output.d.ts +0 -0
- /package/dist/{modules/src → src}/packet.d.ts +0 -0
- /package/dist/{modules/src → src}/pcm.d.ts +0 -0
- /package/dist/{modules/src → src}/reader.d.ts +0 -0
- /package/dist/{modules/src → src}/sample.d.ts +0 -0
- /package/dist/{modules/src → src}/source.d.ts +0 -0
- /package/dist/{modules/src → src}/subtitles.d.ts +0 -0
- /package/dist/{modules/src → src}/target.d.ts +0 -0
- /package/dist/{modules/src → src}/wave/riff-writer.d.ts +0 -0
- /package/dist/{modules/src → src}/wave/wave-demuxer.d.ts +0 -0
- /package/dist/{modules/src → src}/wave/wave-muxer.d.ts +0 -0
- /package/dist/{modules/src → src}/writer.d.ts +0 -0
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// src/misc.ts
|
|
10
|
+
/*!
|
|
11
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
12
|
+
*
|
|
13
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
14
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
15
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
16
|
+
*/
|
|
17
|
+
function assert(x) {
|
|
18
|
+
if (!x) {
|
|
19
|
+
throw new Error("Assertion failed.");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var toUint8Array = (source) => {
|
|
23
|
+
if (source.constructor === Uint8Array) {
|
|
24
|
+
return source;
|
|
25
|
+
}
|
|
26
|
+
if (ArrayBuffer.isView(source)) {
|
|
27
|
+
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
28
|
+
}
|
|
29
|
+
if (typeof SharedArrayBuffer !== "undefined" && source instanceof SharedArrayBuffer) {
|
|
30
|
+
return new Uint8Array(source);
|
|
31
|
+
}
|
|
32
|
+
return new Uint8Array(source);
|
|
33
|
+
};
|
|
34
|
+
var textDecoder = new TextDecoder;
|
|
35
|
+
var textEncoder = new TextEncoder;
|
|
36
|
+
var invertObject = (object) => {
|
|
37
|
+
return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key]));
|
|
38
|
+
};
|
|
39
|
+
var COLOR_PRIMARIES_MAP = {
|
|
40
|
+
bt709: 1,
|
|
41
|
+
bt470bg: 5,
|
|
42
|
+
smpte170m: 6,
|
|
43
|
+
bt2020: 9,
|
|
44
|
+
smpte432: 12
|
|
45
|
+
};
|
|
46
|
+
var COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP);
|
|
47
|
+
var TRANSFER_CHARACTERISTICS_MAP = {
|
|
48
|
+
bt709: 1,
|
|
49
|
+
smpte170m: 6,
|
|
50
|
+
linear: 8,
|
|
51
|
+
"iec61966-2-1": 13,
|
|
52
|
+
pq: 16,
|
|
53
|
+
hlg: 18
|
|
54
|
+
};
|
|
55
|
+
var TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP);
|
|
56
|
+
var MATRIX_COEFFICIENTS_MAP = {
|
|
57
|
+
rgb: 0,
|
|
58
|
+
bt709: 1,
|
|
59
|
+
bt470bg: 5,
|
|
60
|
+
smpte170m: 6,
|
|
61
|
+
"bt2020-ncl": 9
|
|
62
|
+
};
|
|
63
|
+
var MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP);
|
|
64
|
+
var isAllowSharedBufferSource = (x) => {
|
|
65
|
+
return x instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && x instanceof SharedArrayBuffer || ArrayBuffer.isView(x);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
class AsyncMutex {
|
|
69
|
+
currentPromise = Promise.resolve();
|
|
70
|
+
async acquire() {
|
|
71
|
+
let resolver;
|
|
72
|
+
const nextPromise = new Promise((resolve) => {
|
|
73
|
+
resolver = resolve;
|
|
74
|
+
});
|
|
75
|
+
const currentPromiseAlias = this.currentPromise;
|
|
76
|
+
this.currentPromise = nextPromise;
|
|
77
|
+
await currentPromiseAlias;
|
|
78
|
+
return resolver;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
var assertNever = (x) => {
|
|
82
|
+
throw new Error(`Unexpected value: ${x}`);
|
|
83
|
+
};
|
|
84
|
+
var SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
|
|
85
|
+
class CallSerializer {
|
|
86
|
+
currentPromise = Promise.resolve();
|
|
87
|
+
call(fn) {
|
|
88
|
+
return this.currentPromise = this.currentPromise.then(fn);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
var isFirefoxCache = null;
|
|
92
|
+
var isFirefox = () => {
|
|
93
|
+
if (isFirefoxCache !== null) {
|
|
94
|
+
return isFirefoxCache;
|
|
95
|
+
}
|
|
96
|
+
return isFirefoxCache = typeof navigator !== "undefined" && navigator.userAgent?.includes("Firefox");
|
|
97
|
+
};
|
|
98
|
+
var polyfillSymbolDispose = () => {
|
|
99
|
+
Symbol.dispose ??= Symbol("Symbol.dispose");
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/custom-coder.ts
|
|
103
|
+
/*!
|
|
104
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
105
|
+
*
|
|
106
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
107
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
108
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
class CustomVideoDecoder {
|
|
112
|
+
codec;
|
|
113
|
+
config;
|
|
114
|
+
onSample;
|
|
115
|
+
static supports(codec, config) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class CustomAudioDecoder {
|
|
121
|
+
codec;
|
|
122
|
+
config;
|
|
123
|
+
onSample;
|
|
124
|
+
static supports(codec, config) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class CustomVideoEncoder {
|
|
130
|
+
codec;
|
|
131
|
+
config;
|
|
132
|
+
onPacket;
|
|
133
|
+
static supports(codec, config) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class CustomAudioEncoder {
|
|
139
|
+
codec;
|
|
140
|
+
config;
|
|
141
|
+
onPacket;
|
|
142
|
+
static supports(codec, config) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
var customVideoDecoders = [];
|
|
147
|
+
var customAudioDecoders = [];
|
|
148
|
+
var customVideoEncoders = [];
|
|
149
|
+
var customAudioEncoders = [];
|
|
150
|
+
var registerDecoder = (decoder) => {
|
|
151
|
+
if (decoder.prototype instanceof CustomVideoDecoder) {
|
|
152
|
+
const casted = decoder;
|
|
153
|
+
if (customVideoDecoders.includes(casted)) {
|
|
154
|
+
console.warn("Video decoder already registered.");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
customVideoDecoders.push(casted);
|
|
158
|
+
} else if (decoder.prototype instanceof CustomAudioDecoder) {
|
|
159
|
+
const casted = decoder;
|
|
160
|
+
if (customAudioDecoders.includes(casted)) {
|
|
161
|
+
console.warn("Audio decoder already registered.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
customAudioDecoders.push(casted);
|
|
165
|
+
} else {
|
|
166
|
+
throw new TypeError("Decoder must be a CustomVideoDecoder or CustomAudioDecoder.");
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var registerEncoder = (encoder) => {
|
|
170
|
+
if (encoder.prototype instanceof CustomVideoEncoder) {
|
|
171
|
+
const casted = encoder;
|
|
172
|
+
if (customVideoEncoders.includes(casted)) {
|
|
173
|
+
console.warn("Video encoder already registered.");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
customVideoEncoders.push(casted);
|
|
177
|
+
} else if (encoder.prototype instanceof CustomAudioEncoder) {
|
|
178
|
+
const casted = encoder;
|
|
179
|
+
if (customAudioEncoders.includes(casted)) {
|
|
180
|
+
console.warn("Audio encoder already registered.");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
customAudioEncoders.push(casted);
|
|
184
|
+
} else {
|
|
185
|
+
throw new TypeError("Encoder must be a CustomVideoEncoder or CustomAudioEncoder.");
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/packet.ts
|
|
190
|
+
/*!
|
|
191
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
192
|
+
*
|
|
193
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
194
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
195
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
196
|
+
*/
|
|
197
|
+
var PLACEHOLDER_DATA = new Uint8Array(0);
|
|
198
|
+
|
|
199
|
+
class EncodedPacket {
|
|
200
|
+
data;
|
|
201
|
+
type;
|
|
202
|
+
timestamp;
|
|
203
|
+
duration;
|
|
204
|
+
sequenceNumber;
|
|
205
|
+
byteLength;
|
|
206
|
+
sideData;
|
|
207
|
+
constructor(data, type, timestamp, duration, sequenceNumber = -1, byteLength, sideData) {
|
|
208
|
+
this.data = data;
|
|
209
|
+
this.type = type;
|
|
210
|
+
this.timestamp = timestamp;
|
|
211
|
+
this.duration = duration;
|
|
212
|
+
this.sequenceNumber = sequenceNumber;
|
|
213
|
+
if (data === PLACEHOLDER_DATA && byteLength === undefined) {
|
|
214
|
+
throw new Error("Internal error: byteLength must be explicitly provided when constructing metadata-only packets.");
|
|
215
|
+
}
|
|
216
|
+
if (byteLength === undefined) {
|
|
217
|
+
byteLength = data.byteLength;
|
|
218
|
+
}
|
|
219
|
+
if (!(data instanceof Uint8Array)) {
|
|
220
|
+
throw new TypeError("data must be a Uint8Array.");
|
|
221
|
+
}
|
|
222
|
+
if (type !== "key" && type !== "delta") {
|
|
223
|
+
throw new TypeError('type must be either "key" or "delta".');
|
|
224
|
+
}
|
|
225
|
+
if (!Number.isFinite(timestamp)) {
|
|
226
|
+
throw new TypeError("timestamp must be a number.");
|
|
227
|
+
}
|
|
228
|
+
if (!Number.isFinite(duration) || duration < 0) {
|
|
229
|
+
throw new TypeError("duration must be a non-negative number.");
|
|
230
|
+
}
|
|
231
|
+
if (!Number.isFinite(sequenceNumber)) {
|
|
232
|
+
throw new TypeError("sequenceNumber must be a number.");
|
|
233
|
+
}
|
|
234
|
+
if (!Number.isInteger(byteLength) || byteLength < 0) {
|
|
235
|
+
throw new TypeError("byteLength must be a non-negative integer.");
|
|
236
|
+
}
|
|
237
|
+
if (sideData !== undefined && (typeof sideData !== "object" || !sideData)) {
|
|
238
|
+
throw new TypeError("sideData, when provided, must be an object.");
|
|
239
|
+
}
|
|
240
|
+
if (sideData?.alpha !== undefined && !(sideData.alpha instanceof Uint8Array)) {
|
|
241
|
+
throw new TypeError("sideData.alpha, when provided, must be a Uint8Array.");
|
|
242
|
+
}
|
|
243
|
+
if (sideData?.alphaByteLength !== undefined && (!Number.isInteger(sideData.alphaByteLength) || sideData.alphaByteLength < 0)) {
|
|
244
|
+
throw new TypeError("sideData.alphaByteLength, when provided, must be a non-negative integer.");
|
|
245
|
+
}
|
|
246
|
+
this.byteLength = byteLength;
|
|
247
|
+
this.sideData = sideData ?? {};
|
|
248
|
+
if (this.sideData.alpha && this.sideData.alphaByteLength === undefined) {
|
|
249
|
+
this.sideData.alphaByteLength = this.sideData.alpha.byteLength;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
get isMetadataOnly() {
|
|
253
|
+
return this.data === PLACEHOLDER_DATA;
|
|
254
|
+
}
|
|
255
|
+
get microsecondTimestamp() {
|
|
256
|
+
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
|
|
257
|
+
}
|
|
258
|
+
get microsecondDuration() {
|
|
259
|
+
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
|
|
260
|
+
}
|
|
261
|
+
toEncodedVideoChunk() {
|
|
262
|
+
if (this.isMetadataOnly) {
|
|
263
|
+
throw new TypeError("Metadata-only packets cannot be converted to a video chunk.");
|
|
264
|
+
}
|
|
265
|
+
if (typeof EncodedVideoChunk === "undefined") {
|
|
266
|
+
throw new Error("Your browser does not support EncodedVideoChunk.");
|
|
267
|
+
}
|
|
268
|
+
return new EncodedVideoChunk({
|
|
269
|
+
data: this.data,
|
|
270
|
+
type: this.type,
|
|
271
|
+
timestamp: this.microsecondTimestamp,
|
|
272
|
+
duration: this.microsecondDuration
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
alphaToEncodedVideoChunk(type = this.type) {
|
|
276
|
+
if (!this.sideData.alpha) {
|
|
277
|
+
throw new TypeError("This packet does not contain alpha side data.");
|
|
278
|
+
}
|
|
279
|
+
if (this.isMetadataOnly) {
|
|
280
|
+
throw new TypeError("Metadata-only packets cannot be converted to a video chunk.");
|
|
281
|
+
}
|
|
282
|
+
if (typeof EncodedVideoChunk === "undefined") {
|
|
283
|
+
throw new Error("Your browser does not support EncodedVideoChunk.");
|
|
284
|
+
}
|
|
285
|
+
return new EncodedVideoChunk({
|
|
286
|
+
data: this.sideData.alpha,
|
|
287
|
+
type,
|
|
288
|
+
timestamp: this.microsecondTimestamp,
|
|
289
|
+
duration: this.microsecondDuration
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
toEncodedAudioChunk() {
|
|
293
|
+
if (this.isMetadataOnly) {
|
|
294
|
+
throw new TypeError("Metadata-only packets cannot be converted to an audio chunk.");
|
|
295
|
+
}
|
|
296
|
+
if (typeof EncodedAudioChunk === "undefined") {
|
|
297
|
+
throw new Error("Your browser does not support EncodedAudioChunk.");
|
|
298
|
+
}
|
|
299
|
+
return new EncodedAudioChunk({
|
|
300
|
+
data: this.data,
|
|
301
|
+
type: this.type,
|
|
302
|
+
timestamp: this.microsecondTimestamp,
|
|
303
|
+
duration: this.microsecondDuration
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
static fromEncodedChunk(chunk, sideData) {
|
|
307
|
+
if (!(chunk instanceof EncodedVideoChunk || chunk instanceof EncodedAudioChunk)) {
|
|
308
|
+
throw new TypeError("chunk must be an EncodedVideoChunk or EncodedAudioChunk.");
|
|
309
|
+
}
|
|
310
|
+
const data = new Uint8Array(chunk.byteLength);
|
|
311
|
+
chunk.copyTo(data);
|
|
312
|
+
return new EncodedPacket(data, chunk.type, chunk.timestamp / 1e6, (chunk.duration ?? 0) / 1e6, undefined, undefined, sideData);
|
|
313
|
+
}
|
|
314
|
+
clone(options) {
|
|
315
|
+
if (options !== undefined && (typeof options !== "object" || options === null)) {
|
|
316
|
+
throw new TypeError("options, when provided, must be an object.");
|
|
317
|
+
}
|
|
318
|
+
if (options?.timestamp !== undefined && !Number.isFinite(options.timestamp)) {
|
|
319
|
+
throw new TypeError("options.timestamp, when provided, must be a number.");
|
|
320
|
+
}
|
|
321
|
+
if (options?.duration !== undefined && !Number.isFinite(options.duration)) {
|
|
322
|
+
throw new TypeError("options.duration, when provided, must be a number.");
|
|
323
|
+
}
|
|
324
|
+
return new EncodedPacket(this.data, this.type, options?.timestamp ?? this.timestamp, options?.duration ?? this.duration, this.sequenceNumber, this.byteLength);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/sample.ts
|
|
329
|
+
/*!
|
|
330
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
331
|
+
*
|
|
332
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
333
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
334
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
335
|
+
*/
|
|
336
|
+
polyfillSymbolDispose();
|
|
337
|
+
var lastVideoGcErrorLog = -Infinity;
|
|
338
|
+
var lastAudioGcErrorLog = -Infinity;
|
|
339
|
+
var finalizationRegistry = null;
|
|
340
|
+
if (typeof FinalizationRegistry !== "undefined") {
|
|
341
|
+
finalizationRegistry = new FinalizationRegistry((value) => {
|
|
342
|
+
const now = Date.now();
|
|
343
|
+
if (value.type === "video") {
|
|
344
|
+
if (now - lastVideoGcErrorLog >= 1000) {
|
|
345
|
+
console.error(`A VideoSample was garbage collected without first being closed. For proper resource management,` + ` make sure to call close() on all your VideoSamples as soon as you're done using them.`);
|
|
346
|
+
lastVideoGcErrorLog = now;
|
|
347
|
+
}
|
|
348
|
+
if (typeof VideoFrame !== "undefined" && value.data instanceof VideoFrame) {
|
|
349
|
+
value.data.close();
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
if (now - lastAudioGcErrorLog >= 1000) {
|
|
353
|
+
console.error(`An AudioSample was garbage collected without first being closed. For proper resource management,` + ` make sure to call close() on all your AudioSamples as soon as you're done using them.`);
|
|
354
|
+
lastAudioGcErrorLog = now;
|
|
355
|
+
}
|
|
356
|
+
if (typeof AudioData !== "undefined" && value.data instanceof AudioData) {
|
|
357
|
+
value.data.close();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
var VIDEO_SAMPLE_PIXEL_FORMATS = [
|
|
363
|
+
"I420",
|
|
364
|
+
"I420P10",
|
|
365
|
+
"I420P12",
|
|
366
|
+
"I420A",
|
|
367
|
+
"I420AP10",
|
|
368
|
+
"I420AP12",
|
|
369
|
+
"I422",
|
|
370
|
+
"I422P10",
|
|
371
|
+
"I422P12",
|
|
372
|
+
"I422A",
|
|
373
|
+
"I422AP10",
|
|
374
|
+
"I422AP12",
|
|
375
|
+
"I444",
|
|
376
|
+
"I444P10",
|
|
377
|
+
"I444P12",
|
|
378
|
+
"I444A",
|
|
379
|
+
"I444AP10",
|
|
380
|
+
"I444AP12",
|
|
381
|
+
"NV12",
|
|
382
|
+
"RGBA",
|
|
383
|
+
"RGBX",
|
|
384
|
+
"BGRA",
|
|
385
|
+
"BGRX"
|
|
386
|
+
];
|
|
387
|
+
var VIDEO_SAMPLE_PIXEL_FORMATS_SET = new Set(VIDEO_SAMPLE_PIXEL_FORMATS);
|
|
388
|
+
|
|
389
|
+
class VideoSample {
|
|
390
|
+
_data;
|
|
391
|
+
_layout;
|
|
392
|
+
_closed = false;
|
|
393
|
+
format;
|
|
394
|
+
codedWidth;
|
|
395
|
+
codedHeight;
|
|
396
|
+
rotation;
|
|
397
|
+
timestamp;
|
|
398
|
+
duration;
|
|
399
|
+
colorSpace;
|
|
400
|
+
get displayWidth() {
|
|
401
|
+
return this.rotation % 180 === 0 ? this.codedWidth : this.codedHeight;
|
|
402
|
+
}
|
|
403
|
+
get displayHeight() {
|
|
404
|
+
return this.rotation % 180 === 0 ? this.codedHeight : this.codedWidth;
|
|
405
|
+
}
|
|
406
|
+
get microsecondTimestamp() {
|
|
407
|
+
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.timestamp);
|
|
408
|
+
}
|
|
409
|
+
get microsecondDuration() {
|
|
410
|
+
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
|
|
411
|
+
}
|
|
412
|
+
get hasAlpha() {
|
|
413
|
+
return this.format && this.format.includes("A");
|
|
414
|
+
}
|
|
415
|
+
constructor(data, init) {
|
|
416
|
+
if (data instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && data instanceof SharedArrayBuffer || ArrayBuffer.isView(data)) {
|
|
417
|
+
if (!init || typeof init !== "object") {
|
|
418
|
+
throw new TypeError("init must be an object.");
|
|
419
|
+
}
|
|
420
|
+
if (init.format === undefined || !VIDEO_SAMPLE_PIXEL_FORMATS_SET.has(init.format)) {
|
|
421
|
+
throw new TypeError("init.format must be one of: " + VIDEO_SAMPLE_PIXEL_FORMATS.join(", "));
|
|
422
|
+
}
|
|
423
|
+
if (!Number.isInteger(init.codedWidth) || init.codedWidth <= 0) {
|
|
424
|
+
throw new TypeError("init.codedWidth must be a positive integer.");
|
|
425
|
+
}
|
|
426
|
+
if (!Number.isInteger(init.codedHeight) || init.codedHeight <= 0) {
|
|
427
|
+
throw new TypeError("init.codedHeight must be a positive integer.");
|
|
428
|
+
}
|
|
429
|
+
if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
430
|
+
throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
|
|
431
|
+
}
|
|
432
|
+
if (!Number.isFinite(init.timestamp)) {
|
|
433
|
+
throw new TypeError("init.timestamp must be a number.");
|
|
434
|
+
}
|
|
435
|
+
if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
|
|
436
|
+
throw new TypeError("init.duration, when provided, must be a non-negative number.");
|
|
437
|
+
}
|
|
438
|
+
this._data = toUint8Array(data).slice();
|
|
439
|
+
this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
|
|
440
|
+
this.format = init.format;
|
|
441
|
+
this.codedWidth = init.codedWidth;
|
|
442
|
+
this.codedHeight = init.codedHeight;
|
|
443
|
+
this.rotation = init.rotation ?? 0;
|
|
444
|
+
this.timestamp = init.timestamp;
|
|
445
|
+
this.duration = init.duration ?? 0;
|
|
446
|
+
this.colorSpace = new VideoSampleColorSpace(init.colorSpace);
|
|
447
|
+
} else if (typeof VideoFrame !== "undefined" && data instanceof VideoFrame) {
|
|
448
|
+
if (init?.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
449
|
+
throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
|
|
450
|
+
}
|
|
451
|
+
if (init?.timestamp !== undefined && !Number.isFinite(init?.timestamp)) {
|
|
452
|
+
throw new TypeError("init.timestamp, when provided, must be a number.");
|
|
453
|
+
}
|
|
454
|
+
if (init?.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
|
|
455
|
+
throw new TypeError("init.duration, when provided, must be a non-negative number.");
|
|
456
|
+
}
|
|
457
|
+
this._data = data;
|
|
458
|
+
this._layout = null;
|
|
459
|
+
this.format = data.format;
|
|
460
|
+
this.codedWidth = data.displayWidth;
|
|
461
|
+
this.codedHeight = data.displayHeight;
|
|
462
|
+
this.rotation = init?.rotation ?? 0;
|
|
463
|
+
this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
|
|
464
|
+
this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
|
|
465
|
+
this.colorSpace = new VideoSampleColorSpace(data.colorSpace);
|
|
466
|
+
} else if (typeof HTMLImageElement !== "undefined" && data instanceof HTMLImageElement || typeof SVGImageElement !== "undefined" && data instanceof SVGImageElement || typeof ImageBitmap !== "undefined" && data instanceof ImageBitmap || typeof HTMLVideoElement !== "undefined" && data instanceof HTMLVideoElement || typeof HTMLCanvasElement !== "undefined" && data instanceof HTMLCanvasElement || typeof OffscreenCanvas !== "undefined" && data instanceof OffscreenCanvas) {
|
|
467
|
+
if (!init || typeof init !== "object") {
|
|
468
|
+
throw new TypeError("init must be an object.");
|
|
469
|
+
}
|
|
470
|
+
if (init.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
471
|
+
throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
|
|
472
|
+
}
|
|
473
|
+
if (!Number.isFinite(init.timestamp)) {
|
|
474
|
+
throw new TypeError("init.timestamp must be a number.");
|
|
475
|
+
}
|
|
476
|
+
if (init.duration !== undefined && (!Number.isFinite(init.duration) || init.duration < 0)) {
|
|
477
|
+
throw new TypeError("init.duration, when provided, must be a non-negative number.");
|
|
478
|
+
}
|
|
479
|
+
if (typeof VideoFrame !== "undefined") {
|
|
480
|
+
return new VideoSample(new VideoFrame(data, {
|
|
481
|
+
timestamp: Math.trunc(init.timestamp * SECOND_TO_MICROSECOND_FACTOR),
|
|
482
|
+
duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR) || undefined
|
|
483
|
+
}), init);
|
|
484
|
+
}
|
|
485
|
+
let width = 0;
|
|
486
|
+
let height = 0;
|
|
487
|
+
if ("naturalWidth" in data) {
|
|
488
|
+
width = data.naturalWidth;
|
|
489
|
+
height = data.naturalHeight;
|
|
490
|
+
} else if ("videoWidth" in data) {
|
|
491
|
+
width = data.videoWidth;
|
|
492
|
+
height = data.videoHeight;
|
|
493
|
+
} else if ("width" in data) {
|
|
494
|
+
width = Number(data.width);
|
|
495
|
+
height = Number(data.height);
|
|
496
|
+
}
|
|
497
|
+
if (!width || !height) {
|
|
498
|
+
throw new TypeError("Could not determine dimensions.");
|
|
499
|
+
}
|
|
500
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
501
|
+
const context = canvas.getContext("2d", {
|
|
502
|
+
alpha: isFirefox(),
|
|
503
|
+
willReadFrequently: true
|
|
504
|
+
});
|
|
505
|
+
assert(context);
|
|
506
|
+
context.drawImage(data, 0, 0);
|
|
507
|
+
this._data = canvas;
|
|
508
|
+
this._layout = null;
|
|
509
|
+
this.format = "RGBX";
|
|
510
|
+
this.codedWidth = width;
|
|
511
|
+
this.codedHeight = height;
|
|
512
|
+
this.rotation = init.rotation ?? 0;
|
|
513
|
+
this.timestamp = init.timestamp;
|
|
514
|
+
this.duration = init.duration ?? 0;
|
|
515
|
+
this.colorSpace = new VideoSampleColorSpace({
|
|
516
|
+
matrix: "rgb",
|
|
517
|
+
primaries: "bt709",
|
|
518
|
+
transfer: "iec61966-2-1",
|
|
519
|
+
fullRange: true
|
|
520
|
+
});
|
|
521
|
+
} else {
|
|
522
|
+
throw new TypeError("Invalid data type: Must be a BufferSource or CanvasImageSource.");
|
|
523
|
+
}
|
|
524
|
+
finalizationRegistry?.register(this, { type: "video", data: this._data }, this);
|
|
525
|
+
}
|
|
526
|
+
clone() {
|
|
527
|
+
if (this._closed) {
|
|
528
|
+
throw new Error("VideoSample is closed.");
|
|
529
|
+
}
|
|
530
|
+
assert(this._data !== null);
|
|
531
|
+
if (isVideoFrame(this._data)) {
|
|
532
|
+
return new VideoSample(this._data.clone(), {
|
|
533
|
+
timestamp: this.timestamp,
|
|
534
|
+
duration: this.duration,
|
|
535
|
+
rotation: this.rotation
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
if (this._data instanceof Uint8Array) {
|
|
539
|
+
assert(this._layout);
|
|
540
|
+
return new VideoSample(this._data, {
|
|
541
|
+
format: this.format,
|
|
542
|
+
layout: this._layout,
|
|
543
|
+
codedWidth: this.codedWidth,
|
|
544
|
+
codedHeight: this.codedHeight,
|
|
545
|
+
timestamp: this.timestamp,
|
|
546
|
+
duration: this.duration,
|
|
547
|
+
colorSpace: this.colorSpace,
|
|
548
|
+
rotation: this.rotation
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
return new VideoSample(this._data, {
|
|
552
|
+
format: this.format,
|
|
553
|
+
codedWidth: this.codedWidth,
|
|
554
|
+
codedHeight: this.codedHeight,
|
|
555
|
+
timestamp: this.timestamp,
|
|
556
|
+
duration: this.duration,
|
|
557
|
+
colorSpace: this.colorSpace,
|
|
558
|
+
rotation: this.rotation
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
close() {
|
|
562
|
+
if (this._closed) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
finalizationRegistry?.unregister(this);
|
|
566
|
+
if (isVideoFrame(this._data)) {
|
|
567
|
+
this._data.close();
|
|
568
|
+
} else {
|
|
569
|
+
this._data = null;
|
|
570
|
+
}
|
|
571
|
+
this._closed = true;
|
|
572
|
+
}
|
|
573
|
+
allocationSize(options = {}) {
|
|
574
|
+
validateVideoFrameCopyToOptions(options);
|
|
575
|
+
if (this._closed) {
|
|
576
|
+
throw new Error("VideoSample is closed.");
|
|
577
|
+
}
|
|
578
|
+
if ((options.format ?? this.format) === null) {
|
|
579
|
+
throw new Error("Cannot get allocation size when format is null. Please manually provide an RGB pixel format in the" + " options instead.");
|
|
580
|
+
}
|
|
581
|
+
assert(this._data !== null);
|
|
582
|
+
if (!isVideoFrame(this._data)) {
|
|
583
|
+
if (options.colorSpace || options.format && options.format !== this.format || options.layout || options.rect) {
|
|
584
|
+
const videoFrame = this.toVideoFrame();
|
|
585
|
+
const size = videoFrame.allocationSize(options);
|
|
586
|
+
videoFrame.close();
|
|
587
|
+
return size;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (isVideoFrame(this._data)) {
|
|
591
|
+
return this._data.allocationSize(options);
|
|
592
|
+
}
|
|
593
|
+
if (this._data instanceof Uint8Array) {
|
|
594
|
+
return this._data.byteLength;
|
|
595
|
+
}
|
|
596
|
+
return this.codedWidth * this.codedHeight * 4;
|
|
597
|
+
}
|
|
598
|
+
async copyTo(destination, options = {}) {
|
|
599
|
+
if (!isAllowSharedBufferSource(destination)) {
|
|
600
|
+
throw new TypeError("destination must be an ArrayBuffer or an ArrayBuffer view.");
|
|
601
|
+
}
|
|
602
|
+
if (this._closed) {
|
|
603
|
+
throw new Error("VideoSample is closed.");
|
|
604
|
+
}
|
|
605
|
+
if ((options.format ?? this.format) === null) {
|
|
606
|
+
throw new Error("Cannot copy video sample data when format is null. Please manually provide an RGB pixel format in the" + " options instead.");
|
|
607
|
+
}
|
|
608
|
+
assert(this._data !== null);
|
|
609
|
+
if (!isVideoFrame(this._data)) {
|
|
610
|
+
if (options.colorSpace || options.format && options.format !== this.format || options.layout || options.rect) {
|
|
611
|
+
const videoFrame = this.toVideoFrame();
|
|
612
|
+
const layout = await videoFrame.copyTo(destination, options);
|
|
613
|
+
videoFrame.close();
|
|
614
|
+
return layout;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (isVideoFrame(this._data)) {
|
|
618
|
+
return this._data.copyTo(destination, options);
|
|
619
|
+
} else if (this._data instanceof Uint8Array) {
|
|
620
|
+
assert(this._layout);
|
|
621
|
+
const dest = toUint8Array(destination);
|
|
622
|
+
dest.set(this._data);
|
|
623
|
+
return this._layout;
|
|
624
|
+
} else {
|
|
625
|
+
const canvas = this._data;
|
|
626
|
+
const context = canvas.getContext("2d");
|
|
627
|
+
assert(context);
|
|
628
|
+
const imageData = context.getImageData(0, 0, this.codedWidth, this.codedHeight);
|
|
629
|
+
const dest = toUint8Array(destination);
|
|
630
|
+
dest.set(imageData.data);
|
|
631
|
+
return [
|
|
632
|
+
{
|
|
633
|
+
offset: 0,
|
|
634
|
+
stride: 4 * this.codedWidth
|
|
635
|
+
}
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
toVideoFrame() {
|
|
640
|
+
if (this._closed) {
|
|
641
|
+
throw new Error("VideoSample is closed.");
|
|
642
|
+
}
|
|
643
|
+
assert(this._data !== null);
|
|
644
|
+
if (isVideoFrame(this._data)) {
|
|
645
|
+
return new VideoFrame(this._data, {
|
|
646
|
+
timestamp: this.microsecondTimestamp,
|
|
647
|
+
duration: this.microsecondDuration || undefined
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
if (this._data instanceof Uint8Array) {
|
|
651
|
+
return new VideoFrame(this._data, {
|
|
652
|
+
format: this.format,
|
|
653
|
+
codedWidth: this.codedWidth,
|
|
654
|
+
codedHeight: this.codedHeight,
|
|
655
|
+
timestamp: this.microsecondTimestamp,
|
|
656
|
+
duration: this.microsecondDuration || undefined,
|
|
657
|
+
colorSpace: this.colorSpace
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
return new VideoFrame(this._data, {
|
|
661
|
+
timestamp: this.microsecondTimestamp,
|
|
662
|
+
duration: this.microsecondDuration || undefined
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
draw(context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
|
|
666
|
+
let sx = 0;
|
|
667
|
+
let sy = 0;
|
|
668
|
+
let sWidth = this.displayWidth;
|
|
669
|
+
let sHeight = this.displayHeight;
|
|
670
|
+
let dx = 0;
|
|
671
|
+
let dy = 0;
|
|
672
|
+
let dWidth = this.displayWidth;
|
|
673
|
+
let dHeight = this.displayHeight;
|
|
674
|
+
if (arg5 !== undefined) {
|
|
675
|
+
sx = arg1;
|
|
676
|
+
sy = arg2;
|
|
677
|
+
sWidth = arg3;
|
|
678
|
+
sHeight = arg4;
|
|
679
|
+
dx = arg5;
|
|
680
|
+
dy = arg6;
|
|
681
|
+
if (arg7 !== undefined) {
|
|
682
|
+
dWidth = arg7;
|
|
683
|
+
dHeight = arg8;
|
|
684
|
+
} else {
|
|
685
|
+
dWidth = sWidth;
|
|
686
|
+
dHeight = sHeight;
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
dx = arg1;
|
|
690
|
+
dy = arg2;
|
|
691
|
+
if (arg3 !== undefined) {
|
|
692
|
+
dWidth = arg3;
|
|
693
|
+
dHeight = arg4;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (!(typeof CanvasRenderingContext2D !== "undefined" && context instanceof CanvasRenderingContext2D || typeof OffscreenCanvasRenderingContext2D !== "undefined" && context instanceof OffscreenCanvasRenderingContext2D)) {
|
|
697
|
+
throw new TypeError("context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.");
|
|
698
|
+
}
|
|
699
|
+
if (!Number.isFinite(sx)) {
|
|
700
|
+
throw new TypeError("sx must be a number.");
|
|
701
|
+
}
|
|
702
|
+
if (!Number.isFinite(sy)) {
|
|
703
|
+
throw new TypeError("sy must be a number.");
|
|
704
|
+
}
|
|
705
|
+
if (!Number.isFinite(sWidth) || sWidth < 0) {
|
|
706
|
+
throw new TypeError("sWidth must be a non-negative number.");
|
|
707
|
+
}
|
|
708
|
+
if (!Number.isFinite(sHeight) || sHeight < 0) {
|
|
709
|
+
throw new TypeError("sHeight must be a non-negative number.");
|
|
710
|
+
}
|
|
711
|
+
if (!Number.isFinite(dx)) {
|
|
712
|
+
throw new TypeError("dx must be a number.");
|
|
713
|
+
}
|
|
714
|
+
if (!Number.isFinite(dy)) {
|
|
715
|
+
throw new TypeError("dy must be a number.");
|
|
716
|
+
}
|
|
717
|
+
if (!Number.isFinite(dWidth) || dWidth < 0) {
|
|
718
|
+
throw new TypeError("dWidth must be a non-negative number.");
|
|
719
|
+
}
|
|
720
|
+
if (!Number.isFinite(dHeight) || dHeight < 0) {
|
|
721
|
+
throw new TypeError("dHeight must be a non-negative number.");
|
|
722
|
+
}
|
|
723
|
+
if (this._closed) {
|
|
724
|
+
throw new Error("VideoSample is closed.");
|
|
725
|
+
}
|
|
726
|
+
({ sx, sy, sWidth, sHeight } = this._rotateSourceRegion(sx, sy, sWidth, sHeight, this.rotation));
|
|
727
|
+
const source = this.toCanvasImageSource();
|
|
728
|
+
context.save();
|
|
729
|
+
const centerX = dx + dWidth / 2;
|
|
730
|
+
const centerY = dy + dHeight / 2;
|
|
731
|
+
context.translate(centerX, centerY);
|
|
732
|
+
context.rotate(this.rotation * Math.PI / 180);
|
|
733
|
+
const aspectRatioChange = this.rotation % 180 === 0 ? 1 : dWidth / dHeight;
|
|
734
|
+
context.scale(1 / aspectRatioChange, aspectRatioChange);
|
|
735
|
+
context.drawImage(source, sx, sy, sWidth, sHeight, -dWidth / 2, -dHeight / 2, dWidth, dHeight);
|
|
736
|
+
context.restore();
|
|
737
|
+
}
|
|
738
|
+
drawWithFit(context, options) {
|
|
739
|
+
if (!(typeof CanvasRenderingContext2D !== "undefined" && context instanceof CanvasRenderingContext2D || typeof OffscreenCanvasRenderingContext2D !== "undefined" && context instanceof OffscreenCanvasRenderingContext2D)) {
|
|
740
|
+
throw new TypeError("context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.");
|
|
741
|
+
}
|
|
742
|
+
if (!options || typeof options !== "object") {
|
|
743
|
+
throw new TypeError("options must be an object.");
|
|
744
|
+
}
|
|
745
|
+
if (!["fill", "contain", "cover"].includes(options.fit)) {
|
|
746
|
+
throw new TypeError("options.fit must be 'fill', 'contain', or 'cover'.");
|
|
747
|
+
}
|
|
748
|
+
if (options.rotation !== undefined && ![0, 90, 180, 270].includes(options.rotation)) {
|
|
749
|
+
throw new TypeError("options.rotation, when provided, must be 0, 90, 180, or 270.");
|
|
750
|
+
}
|
|
751
|
+
if (options.crop !== undefined) {
|
|
752
|
+
validateCropRectangle(options.crop, "options.");
|
|
753
|
+
}
|
|
754
|
+
const canvasWidth = context.canvas.width;
|
|
755
|
+
const canvasHeight = context.canvas.height;
|
|
756
|
+
const rotation = options.rotation ?? this.rotation;
|
|
757
|
+
const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.codedWidth, this.codedHeight] : [this.codedHeight, this.codedWidth];
|
|
758
|
+
if (options.crop) {
|
|
759
|
+
clampCropRectangle(options.crop, rotatedWidth, rotatedHeight);
|
|
760
|
+
}
|
|
761
|
+
let dx;
|
|
762
|
+
let dy;
|
|
763
|
+
let newWidth;
|
|
764
|
+
let newHeight;
|
|
765
|
+
const { sx, sy, sWidth, sHeight } = this._rotateSourceRegion(options.crop?.left ?? 0, options.crop?.top ?? 0, options.crop?.width ?? rotatedWidth, options.crop?.height ?? rotatedHeight, rotation);
|
|
766
|
+
if (options.fit === "fill") {
|
|
767
|
+
dx = 0;
|
|
768
|
+
dy = 0;
|
|
769
|
+
newWidth = canvasWidth;
|
|
770
|
+
newHeight = canvasHeight;
|
|
771
|
+
} else {
|
|
772
|
+
const [sampleWidth, sampleHeight] = options.crop ? [options.crop.width, options.crop.height] : [rotatedWidth, rotatedHeight];
|
|
773
|
+
const scale = options.fit === "contain" ? Math.min(canvasWidth / sampleWidth, canvasHeight / sampleHeight) : Math.max(canvasWidth / sampleWidth, canvasHeight / sampleHeight);
|
|
774
|
+
newWidth = sampleWidth * scale;
|
|
775
|
+
newHeight = sampleHeight * scale;
|
|
776
|
+
dx = (canvasWidth - newWidth) / 2;
|
|
777
|
+
dy = (canvasHeight - newHeight) / 2;
|
|
778
|
+
}
|
|
779
|
+
context.save();
|
|
780
|
+
const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight;
|
|
781
|
+
context.translate(canvasWidth / 2, canvasHeight / 2);
|
|
782
|
+
context.rotate(rotation * Math.PI / 180);
|
|
783
|
+
context.scale(1 / aspectRatioChange, aspectRatioChange);
|
|
784
|
+
context.translate(-canvasWidth / 2, -canvasHeight / 2);
|
|
785
|
+
context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight);
|
|
786
|
+
context.restore();
|
|
787
|
+
}
|
|
788
|
+
_rotateSourceRegion(sx, sy, sWidth, sHeight, rotation) {
|
|
789
|
+
if (rotation === 90) {
|
|
790
|
+
[sx, sy, sWidth, sHeight] = [sy, this.codedHeight - sx - sWidth, sHeight, sWidth];
|
|
791
|
+
} else if (rotation === 180) {
|
|
792
|
+
[sx, sy] = [this.codedWidth - sx - sWidth, this.codedHeight - sy - sHeight];
|
|
793
|
+
} else if (rotation === 270) {
|
|
794
|
+
[sx, sy, sWidth, sHeight] = [this.codedWidth - sy - sHeight, sx, sHeight, sWidth];
|
|
795
|
+
}
|
|
796
|
+
return { sx, sy, sWidth, sHeight };
|
|
797
|
+
}
|
|
798
|
+
toCanvasImageSource() {
|
|
799
|
+
if (this._closed) {
|
|
800
|
+
throw new Error("VideoSample is closed.");
|
|
801
|
+
}
|
|
802
|
+
assert(this._data !== null);
|
|
803
|
+
if (this._data instanceof Uint8Array) {
|
|
804
|
+
const videoFrame = this.toVideoFrame();
|
|
805
|
+
queueMicrotask(() => videoFrame.close());
|
|
806
|
+
return videoFrame;
|
|
807
|
+
}
|
|
808
|
+
return this._data;
|
|
809
|
+
}
|
|
810
|
+
setRotation(newRotation) {
|
|
811
|
+
if (![0, 90, 180, 270].includes(newRotation)) {
|
|
812
|
+
throw new TypeError("newRotation must be 0, 90, 180, or 270.");
|
|
813
|
+
}
|
|
814
|
+
this.rotation = newRotation;
|
|
815
|
+
}
|
|
816
|
+
setTimestamp(newTimestamp) {
|
|
817
|
+
if (!Number.isFinite(newTimestamp)) {
|
|
818
|
+
throw new TypeError("newTimestamp must be a number.");
|
|
819
|
+
}
|
|
820
|
+
this.timestamp = newTimestamp;
|
|
821
|
+
}
|
|
822
|
+
setDuration(newDuration) {
|
|
823
|
+
if (!Number.isFinite(newDuration) || newDuration < 0) {
|
|
824
|
+
throw new TypeError("newDuration must be a non-negative number.");
|
|
825
|
+
}
|
|
826
|
+
this.duration = newDuration;
|
|
827
|
+
}
|
|
828
|
+
[Symbol.dispose]() {
|
|
829
|
+
this.close();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
class VideoSampleColorSpace {
|
|
834
|
+
primaries;
|
|
835
|
+
transfer;
|
|
836
|
+
matrix;
|
|
837
|
+
fullRange;
|
|
838
|
+
constructor(init) {
|
|
839
|
+
this.primaries = init?.primaries ?? null;
|
|
840
|
+
this.transfer = init?.transfer ?? null;
|
|
841
|
+
this.matrix = init?.matrix ?? null;
|
|
842
|
+
this.fullRange = init?.fullRange ?? null;
|
|
843
|
+
}
|
|
844
|
+
toJSON() {
|
|
845
|
+
return {
|
|
846
|
+
primaries: this.primaries,
|
|
847
|
+
transfer: this.transfer,
|
|
848
|
+
matrix: this.matrix,
|
|
849
|
+
fullRange: this.fullRange
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
var isVideoFrame = (x) => {
|
|
854
|
+
return typeof VideoFrame !== "undefined" && x instanceof VideoFrame;
|
|
855
|
+
};
|
|
856
|
+
var clampCropRectangle = (crop, outerWidth, outerHeight) => {
|
|
857
|
+
crop.left = Math.min(crop.left, outerWidth);
|
|
858
|
+
crop.top = Math.min(crop.top, outerHeight);
|
|
859
|
+
crop.width = Math.min(crop.width, outerWidth - crop.left);
|
|
860
|
+
crop.height = Math.min(crop.height, outerHeight - crop.top);
|
|
861
|
+
assert(crop.width >= 0);
|
|
862
|
+
assert(crop.height >= 0);
|
|
863
|
+
};
|
|
864
|
+
var validateCropRectangle = (crop, prefix) => {
|
|
865
|
+
if (!crop || typeof crop !== "object") {
|
|
866
|
+
throw new TypeError(prefix + "crop, when provided, must be an object.");
|
|
867
|
+
}
|
|
868
|
+
if (!Number.isInteger(crop.left) || crop.left < 0) {
|
|
869
|
+
throw new TypeError(prefix + "crop.left must be a non-negative integer.");
|
|
870
|
+
}
|
|
871
|
+
if (!Number.isInteger(crop.top) || crop.top < 0) {
|
|
872
|
+
throw new TypeError(prefix + "crop.top must be a non-negative integer.");
|
|
873
|
+
}
|
|
874
|
+
if (!Number.isInteger(crop.width) || crop.width < 0) {
|
|
875
|
+
throw new TypeError(prefix + "crop.width must be a non-negative integer.");
|
|
876
|
+
}
|
|
877
|
+
if (!Number.isInteger(crop.height) || crop.height < 0) {
|
|
878
|
+
throw new TypeError(prefix + "crop.height must be a non-negative integer.");
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
var validateVideoFrameCopyToOptions = (options) => {
|
|
882
|
+
if (!options || typeof options !== "object") {
|
|
883
|
+
throw new TypeError("options must be an object.");
|
|
884
|
+
}
|
|
885
|
+
if (options.colorSpace !== undefined && !["display-p3", "srgb"].includes(options.colorSpace)) {
|
|
886
|
+
throw new TypeError("options.colorSpace, when provided, must be 'display-p3' or 'srgb'.");
|
|
887
|
+
}
|
|
888
|
+
if (options.format !== undefined && typeof options.format !== "string") {
|
|
889
|
+
throw new TypeError("options.format, when provided, must be a string.");
|
|
890
|
+
}
|
|
891
|
+
if (options.layout !== undefined) {
|
|
892
|
+
if (!Array.isArray(options.layout)) {
|
|
893
|
+
throw new TypeError("options.layout, when provided, must be an array.");
|
|
894
|
+
}
|
|
895
|
+
for (const plane of options.layout) {
|
|
896
|
+
if (!plane || typeof plane !== "object") {
|
|
897
|
+
throw new TypeError("Each entry in options.layout must be an object.");
|
|
898
|
+
}
|
|
899
|
+
if (!Number.isInteger(plane.offset) || plane.offset < 0) {
|
|
900
|
+
throw new TypeError("plane.offset must be a non-negative integer.");
|
|
901
|
+
}
|
|
902
|
+
if (!Number.isInteger(plane.stride) || plane.stride < 0) {
|
|
903
|
+
throw new TypeError("plane.stride must be a non-negative integer.");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (options.rect !== undefined) {
|
|
908
|
+
if (!options.rect || typeof options.rect !== "object") {
|
|
909
|
+
throw new TypeError("options.rect, when provided, must be an object.");
|
|
910
|
+
}
|
|
911
|
+
if (options.rect.x !== undefined && (!Number.isInteger(options.rect.x) || options.rect.x < 0)) {
|
|
912
|
+
throw new TypeError("options.rect.x, when provided, must be a non-negative integer.");
|
|
913
|
+
}
|
|
914
|
+
if (options.rect.y !== undefined && (!Number.isInteger(options.rect.y) || options.rect.y < 0)) {
|
|
915
|
+
throw new TypeError("options.rect.y, when provided, must be a non-negative integer.");
|
|
916
|
+
}
|
|
917
|
+
if (options.rect.width !== undefined && (!Number.isInteger(options.rect.width) || options.rect.width < 0)) {
|
|
918
|
+
throw new TypeError("options.rect.width, when provided, must be a non-negative integer.");
|
|
919
|
+
}
|
|
920
|
+
if (options.rect.height !== undefined && (!Number.isInteger(options.rect.height) || options.rect.height < 0)) {
|
|
921
|
+
throw new TypeError("options.rect.height, when provided, must be a non-negative integer.");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
var createDefaultPlaneLayout = (format, codedWidth, codedHeight) => {
|
|
926
|
+
const planes = getPlaneConfigs(format);
|
|
927
|
+
const layouts = [];
|
|
928
|
+
let currentOffset = 0;
|
|
929
|
+
for (const plane of planes) {
|
|
930
|
+
const planeWidth = Math.ceil(codedWidth / plane.widthDivisor);
|
|
931
|
+
const planeHeight = Math.ceil(codedHeight / plane.heightDivisor);
|
|
932
|
+
const stride = planeWidth * plane.sampleBytes;
|
|
933
|
+
const planeSize = stride * planeHeight;
|
|
934
|
+
layouts.push({
|
|
935
|
+
offset: currentOffset,
|
|
936
|
+
stride
|
|
937
|
+
});
|
|
938
|
+
currentOffset += planeSize;
|
|
939
|
+
}
|
|
940
|
+
return layouts;
|
|
941
|
+
};
|
|
942
|
+
var getPlaneConfigs = (format) => {
|
|
943
|
+
const yuv = (yBytes, uvBytes, subX, subY, hasAlpha) => {
|
|
944
|
+
const configs = [
|
|
945
|
+
{ sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 },
|
|
946
|
+
{ sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY },
|
|
947
|
+
{ sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY }
|
|
948
|
+
];
|
|
949
|
+
if (hasAlpha) {
|
|
950
|
+
configs.push({ sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 });
|
|
951
|
+
}
|
|
952
|
+
return configs;
|
|
953
|
+
};
|
|
954
|
+
switch (format) {
|
|
955
|
+
case "I420":
|
|
956
|
+
return yuv(1, 1, 2, 2, false);
|
|
957
|
+
case "I420P10":
|
|
958
|
+
case "I420P12":
|
|
959
|
+
return yuv(2, 2, 2, 2, false);
|
|
960
|
+
case "I420A":
|
|
961
|
+
return yuv(1, 1, 2, 2, true);
|
|
962
|
+
case "I420AP10":
|
|
963
|
+
case "I420AP12":
|
|
964
|
+
return yuv(2, 2, 2, 2, true);
|
|
965
|
+
case "I422":
|
|
966
|
+
return yuv(1, 1, 2, 1, false);
|
|
967
|
+
case "I422P10":
|
|
968
|
+
case "I422P12":
|
|
969
|
+
return yuv(2, 2, 2, 1, false);
|
|
970
|
+
case "I422A":
|
|
971
|
+
return yuv(1, 1, 2, 1, true);
|
|
972
|
+
case "I422AP10":
|
|
973
|
+
case "I422AP12":
|
|
974
|
+
return yuv(2, 2, 2, 1, true);
|
|
975
|
+
case "I444":
|
|
976
|
+
return yuv(1, 1, 1, 1, false);
|
|
977
|
+
case "I444P10":
|
|
978
|
+
case "I444P12":
|
|
979
|
+
return yuv(2, 2, 1, 1, false);
|
|
980
|
+
case "I444A":
|
|
981
|
+
return yuv(1, 1, 1, 1, true);
|
|
982
|
+
case "I444AP10":
|
|
983
|
+
case "I444AP12":
|
|
984
|
+
return yuv(2, 2, 1, 1, true);
|
|
985
|
+
case "NV12":
|
|
986
|
+
return [
|
|
987
|
+
{ sampleBytes: 1, widthDivisor: 1, heightDivisor: 1 },
|
|
988
|
+
{ sampleBytes: 2, widthDivisor: 2, heightDivisor: 2 }
|
|
989
|
+
];
|
|
990
|
+
case "RGBA":
|
|
991
|
+
case "RGBX":
|
|
992
|
+
case "BGRA":
|
|
993
|
+
case "BGRX":
|
|
994
|
+
return [{ sampleBytes: 4, widthDivisor: 1, heightDivisor: 1 }];
|
|
995
|
+
default:
|
|
996
|
+
assertNever(format);
|
|
997
|
+
assert(false);
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
var AUDIO_SAMPLE_FORMATS = new Set([
|
|
1001
|
+
"f32",
|
|
1002
|
+
"f32-planar",
|
|
1003
|
+
"s16",
|
|
1004
|
+
"s16-planar",
|
|
1005
|
+
"s32",
|
|
1006
|
+
"s32-planar",
|
|
1007
|
+
"u8",
|
|
1008
|
+
"u8-planar"
|
|
1009
|
+
]);
|
|
1010
|
+
// src/index.ts
|
|
1011
|
+
/*!
|
|
1012
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
1013
|
+
*
|
|
1014
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
1015
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
1016
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1017
|
+
*/
|
|
1018
|
+
|
|
1019
|
+
// packages/mpeg4/src/index.ts
|
|
1020
|
+
/*!
|
|
1021
|
+
* Copyright (c) 2025-present, Vanilagy and contributors (Wiedy Mi)
|
|
1022
|
+
*
|
|
1023
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
1024
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
1025
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1026
|
+
*/
|
|
1027
|
+
var decodeWorkerUrl = new URL("./decode.worker.ts", import.meta.url);
|
|
1028
|
+
var encodeWorkerUrl = new URL("./encode.worker.ts", import.meta.url);
|
|
1029
|
+
var createWorker = (url) => {
|
|
1030
|
+
return new Worker(url, { type: "module" });
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
class Mpeg4Decoder extends CustomVideoDecoder {
|
|
1034
|
+
worker = null;
|
|
1035
|
+
nextMessageId = 0;
|
|
1036
|
+
pendingMessages = new Map;
|
|
1037
|
+
static supports(codec, _config) {
|
|
1038
|
+
return codec === "mpeg4";
|
|
1039
|
+
}
|
|
1040
|
+
async init() {
|
|
1041
|
+
this.worker = createWorker(decodeWorkerUrl);
|
|
1042
|
+
const onMessage = (event) => {
|
|
1043
|
+
const data = event.data;
|
|
1044
|
+
const pending = this.pendingMessages.get(data.id);
|
|
1045
|
+
assert2(pending !== undefined);
|
|
1046
|
+
this.pendingMessages.delete(data.id);
|
|
1047
|
+
if (data.success) {
|
|
1048
|
+
pending.resolve(data.data);
|
|
1049
|
+
} else {
|
|
1050
|
+
pending.reject(data.error);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
this.worker.addEventListener("message", onMessage);
|
|
1054
|
+
await this.sendCommand({
|
|
1055
|
+
type: "init",
|
|
1056
|
+
data: {
|
|
1057
|
+
width: this.config.codedWidth,
|
|
1058
|
+
height: this.config.codedHeight
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
async decode(packet, _meta) {
|
|
1063
|
+
const frameData = packet.data.slice().buffer;
|
|
1064
|
+
const result = await this.sendCommand({
|
|
1065
|
+
type: "decode",
|
|
1066
|
+
data: {
|
|
1067
|
+
frameData
|
|
1068
|
+
}
|
|
1069
|
+
}, [frameData]);
|
|
1070
|
+
if (!result || !("yuvData" in result)) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const videoFrame = new VideoFrame(new Uint8Array(result.yuvData), {
|
|
1074
|
+
format: "I420",
|
|
1075
|
+
codedWidth: result.width,
|
|
1076
|
+
codedHeight: result.height,
|
|
1077
|
+
timestamp: packet.timestamp * 1e6
|
|
1078
|
+
});
|
|
1079
|
+
const videoSample = new VideoSample(videoFrame);
|
|
1080
|
+
this.onSample(videoSample);
|
|
1081
|
+
}
|
|
1082
|
+
async flush() {
|
|
1083
|
+
await this.sendCommand({ type: "flush" });
|
|
1084
|
+
}
|
|
1085
|
+
close() {
|
|
1086
|
+
if (this.worker) {
|
|
1087
|
+
this.sendCommand({ type: "close" });
|
|
1088
|
+
this.worker.terminate();
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
sendCommand(command, transferables) {
|
|
1092
|
+
return new Promise((resolve, reject) => {
|
|
1093
|
+
const id = this.nextMessageId++;
|
|
1094
|
+
this.pendingMessages.set(id, { resolve, reject });
|
|
1095
|
+
assert2(this.worker !== null);
|
|
1096
|
+
if (transferables) {
|
|
1097
|
+
this.worker.postMessage({ id, command }, transferables);
|
|
1098
|
+
} else {
|
|
1099
|
+
this.worker.postMessage({ id, command });
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
class Mpeg4Encoder extends CustomVideoEncoder {
|
|
1106
|
+
worker = null;
|
|
1107
|
+
nextMessageId = 0;
|
|
1108
|
+
pendingMessages = new Map;
|
|
1109
|
+
frameCount = 0;
|
|
1110
|
+
static supports(codec, _config) {
|
|
1111
|
+
return codec === "mpeg4";
|
|
1112
|
+
}
|
|
1113
|
+
async init() {
|
|
1114
|
+
this.worker = createWorker(encodeWorkerUrl);
|
|
1115
|
+
const onMessage = (event) => {
|
|
1116
|
+
const data = event.data;
|
|
1117
|
+
const pending = this.pendingMessages.get(data.id);
|
|
1118
|
+
assert2(pending !== undefined);
|
|
1119
|
+
this.pendingMessages.delete(data.id);
|
|
1120
|
+
if (data.success) {
|
|
1121
|
+
pending.resolve(data.data);
|
|
1122
|
+
} else {
|
|
1123
|
+
pending.reject(data.error);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
this.worker.addEventListener("message", onMessage);
|
|
1127
|
+
const fpsNum = Math.round((this.config.framerate ?? 30) * 1000);
|
|
1128
|
+
const fpsDen = 1000;
|
|
1129
|
+
await this.sendCommand({
|
|
1130
|
+
type: "init",
|
|
1131
|
+
data: {
|
|
1132
|
+
width: this.config.width,
|
|
1133
|
+
height: this.config.height,
|
|
1134
|
+
bitrate: this.config.bitrate ?? 2000000,
|
|
1135
|
+
fpsNum,
|
|
1136
|
+
fpsDen
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
async encode(videoSample, options) {
|
|
1141
|
+
const yuvSize = videoSample.codedWidth * videoSample.codedHeight * 3 / 2;
|
|
1142
|
+
const yuvData = new ArrayBuffer(yuvSize);
|
|
1143
|
+
const yuvBytes = new Uint8Array(yuvData);
|
|
1144
|
+
await videoSample.copyTo(yuvBytes);
|
|
1145
|
+
const result = await this.sendCommand({
|
|
1146
|
+
type: "encode",
|
|
1147
|
+
data: {
|
|
1148
|
+
yuvData,
|
|
1149
|
+
forceKeyframe: options.keyFrame ?? false
|
|
1150
|
+
}
|
|
1151
|
+
}, [yuvData]);
|
|
1152
|
+
assert2(result && "encodedData" in result);
|
|
1153
|
+
const encodedPacket = new EncodedPacket(new Uint8Array(result.encodedData), options.keyFrame ? "key" : "delta", videoSample.timestamp, videoSample.duration, this.frameCount++);
|
|
1154
|
+
this.onPacket(encodedPacket, this.frameCount === 1 ? {
|
|
1155
|
+
decoderConfig: {
|
|
1156
|
+
codec: "mpeg4",
|
|
1157
|
+
codedWidth: this.config.width,
|
|
1158
|
+
codedHeight: this.config.height
|
|
1159
|
+
}
|
|
1160
|
+
} : undefined);
|
|
1161
|
+
}
|
|
1162
|
+
async flush() {}
|
|
1163
|
+
close() {
|
|
1164
|
+
if (this.worker) {
|
|
1165
|
+
this.sendCommand({ type: "close" });
|
|
1166
|
+
this.worker.terminate();
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
sendCommand(command, transferables) {
|
|
1170
|
+
return new Promise((resolve, reject) => {
|
|
1171
|
+
const id = this.nextMessageId++;
|
|
1172
|
+
this.pendingMessages.set(id, { resolve, reject });
|
|
1173
|
+
assert2(this.worker !== null);
|
|
1174
|
+
if (transferables) {
|
|
1175
|
+
this.worker.postMessage({ id, command }, transferables);
|
|
1176
|
+
} else {
|
|
1177
|
+
this.worker.postMessage({ id, command });
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
var registerMpeg4Decoder = () => {
|
|
1183
|
+
registerDecoder(Mpeg4Decoder);
|
|
1184
|
+
};
|
|
1185
|
+
var registerMpeg4Encoder = () => {
|
|
1186
|
+
registerEncoder(Mpeg4Encoder);
|
|
1187
|
+
};
|
|
1188
|
+
function assert2(x) {
|
|
1189
|
+
if (!x) {
|
|
1190
|
+
throw new Error("Assertion failed.");
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
export {
|
|
1194
|
+
registerMpeg4Encoder,
|
|
1195
|
+
registerMpeg4Decoder,
|
|
1196
|
+
Mpeg4Encoder,
|
|
1197
|
+
Mpeg4Decoder
|
|
1198
|
+
};
|