krono-flow 0.0.1 → 0.0.3
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/decoder.js +770 -324
- package/dist/decoder.js.map +1 -1
- package/dist/decoder.min.js +24 -24
- package/dist/decoder.min.js.map +1 -1
- package/dist/encoder.js +15824 -565
- package/dist/encoder.js.map +1 -1
- package/dist/encoder.min.js +168 -15
- package/dist/encoder.min.js.map +1 -1
- package/dist/index.js +10197 -1925
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +35 -35
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +46595 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +5 -6
package/dist/decoder.js
CHANGED
|
@@ -205,22 +205,22 @@
|
|
|
205
205
|
if (source.constructor === Uint8Array) { // We want a true Uint8Array, not something that extends it like Buffer
|
|
206
206
|
return source;
|
|
207
207
|
}
|
|
208
|
-
else if (source
|
|
209
|
-
return new Uint8Array(source);
|
|
208
|
+
else if (ArrayBuffer.isView(source)) {
|
|
209
|
+
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
210
210
|
}
|
|
211
211
|
else {
|
|
212
|
-
return new Uint8Array(source
|
|
212
|
+
return new Uint8Array(source);
|
|
213
213
|
}
|
|
214
214
|
};
|
|
215
215
|
const toDataView = (source) => {
|
|
216
216
|
if (source.constructor === DataView) {
|
|
217
217
|
return source;
|
|
218
218
|
}
|
|
219
|
-
else if (source
|
|
220
|
-
return new DataView(source);
|
|
219
|
+
else if (ArrayBuffer.isView(source)) {
|
|
220
|
+
return new DataView(source.buffer, source.byteOffset, source.byteLength);
|
|
221
221
|
}
|
|
222
222
|
else {
|
|
223
|
-
return new DataView(source
|
|
223
|
+
return new DataView(source);
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
226
|
const textDecoder = /* #__PURE__ */ new TextDecoder();
|
|
@@ -388,6 +388,16 @@
|
|
|
388
388
|
// then correctly interprets the sign bit.
|
|
389
389
|
return getUint24(view, byteOffset, littleEndian) << 8 >> 8;
|
|
390
390
|
};
|
|
391
|
+
const setUint24 = (view, byteOffset, value, littleEndian) => {
|
|
392
|
+
// Ensure the value is within 24-bit unsigned range (0 to 16777215)
|
|
393
|
+
value = value >>> 0; // Convert to unsigned 32-bit
|
|
394
|
+
value = value & 0xFFFFFF; // Mask to 24 bits
|
|
395
|
+
{
|
|
396
|
+
view.setUint8(byteOffset, (value >>> 16) & 0xFF);
|
|
397
|
+
view.setUint8(byteOffset + 1, (value >>> 8) & 0xFF);
|
|
398
|
+
view.setUint8(byteOffset + 2, value & 0xFF);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
391
401
|
const clamp = (value, min, max) => {
|
|
392
402
|
return Math.max(min, Math.min(max, value));
|
|
393
403
|
};
|
|
@@ -460,13 +470,16 @@
|
|
|
460
470
|
}
|
|
461
471
|
return headers;
|
|
462
472
|
};
|
|
463
|
-
const retriedFetch = async (fetchFn, url, requestInit, getRetryDelay) => {
|
|
473
|
+
const retriedFetch = async (fetchFn, url, requestInit, getRetryDelay, shouldStop) => {
|
|
464
474
|
let attempts = 0;
|
|
465
475
|
while (true) {
|
|
466
476
|
try {
|
|
467
477
|
return await fetchFn(url, requestInit);
|
|
468
478
|
}
|
|
469
479
|
catch (error) {
|
|
480
|
+
if (shouldStop()) {
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
470
483
|
attempts++;
|
|
471
484
|
const retryDelayInSeconds = getRetryDelay(attempts, error, url);
|
|
472
485
|
if (retryDelayInSeconds === null) {
|
|
@@ -479,6 +492,9 @@
|
|
|
479
492
|
if (retryDelayInSeconds > 0) {
|
|
480
493
|
await new Promise(resolve => setTimeout(resolve, 1000 * retryDelayInSeconds));
|
|
481
494
|
}
|
|
495
|
+
if (shouldStop()) {
|
|
496
|
+
throw error;
|
|
497
|
+
}
|
|
482
498
|
}
|
|
483
499
|
}
|
|
484
500
|
};
|
|
@@ -496,7 +512,11 @@
|
|
|
496
512
|
return isWebKitCache;
|
|
497
513
|
}
|
|
498
514
|
// This even returns true for WebKit-wrapping browsers such as Chrome on iOS
|
|
499
|
-
return isWebKitCache = !!(typeof navigator !== 'undefined'
|
|
515
|
+
return isWebKitCache = !!(typeof navigator !== 'undefined'
|
|
516
|
+
&& (navigator.vendor?.match(/apple/i)
|
|
517
|
+
// Or, in workers:
|
|
518
|
+
|| (/AppleWebKit/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent))
|
|
519
|
+
|| /\b(iPad|iPhone|iPod)\b/.test(navigator.userAgent)));
|
|
500
520
|
};
|
|
501
521
|
let isFirefoxCache = null;
|
|
502
522
|
const isFirefox = () => {
|
|
@@ -510,7 +530,22 @@
|
|
|
510
530
|
if (isChromiumCache !== null) {
|
|
511
531
|
return isChromiumCache;
|
|
512
532
|
}
|
|
513
|
-
return isChromiumCache = !!(typeof navigator !== 'undefined'
|
|
533
|
+
return isChromiumCache = !!(typeof navigator !== 'undefined'
|
|
534
|
+
&& (navigator.vendor?.includes('Google Inc') || /Chrome/.test(navigator.userAgent)));
|
|
535
|
+
};
|
|
536
|
+
let chromiumVersionCache = null;
|
|
537
|
+
const getChromiumVersion = () => {
|
|
538
|
+
if (chromiumVersionCache !== null) {
|
|
539
|
+
return chromiumVersionCache;
|
|
540
|
+
}
|
|
541
|
+
if (typeof navigator === 'undefined') {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const match = /\bChrome\/(\d+)/.exec(navigator.userAgent);
|
|
545
|
+
if (!match) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
return chromiumVersionCache = Number(match[1]);
|
|
514
549
|
};
|
|
515
550
|
/** Acts like `??` except the condition is -1 and not null/undefined. */
|
|
516
551
|
const coalesceIndex = (a, b) => {
|
|
@@ -907,6 +942,7 @@
|
|
|
907
942
|
var AvcNalUnitType;
|
|
908
943
|
(function (AvcNalUnitType) {
|
|
909
944
|
AvcNalUnitType[AvcNalUnitType["IDR"] = 5] = "IDR";
|
|
945
|
+
AvcNalUnitType[AvcNalUnitType["SEI"] = 6] = "SEI";
|
|
910
946
|
AvcNalUnitType[AvcNalUnitType["SPS"] = 7] = "SPS";
|
|
911
947
|
AvcNalUnitType[AvcNalUnitType["PPS"] = 8] = "PPS";
|
|
912
948
|
AvcNalUnitType[AvcNalUnitType["SPS_EXT"] = 13] = "SPS_EXT";
|
|
@@ -1014,6 +1050,45 @@
|
|
|
1014
1050
|
}
|
|
1015
1051
|
return new Uint8Array(result);
|
|
1016
1052
|
};
|
|
1053
|
+
const ANNEX_B_START_CODE = new Uint8Array([0, 0, 0, 1]);
|
|
1054
|
+
const concatNalUnitsInAnnexB = (nalUnits) => {
|
|
1055
|
+
const totalLength = nalUnits.reduce((a, b) => a + ANNEX_B_START_CODE.byteLength + b.byteLength, 0);
|
|
1056
|
+
const result = new Uint8Array(totalLength);
|
|
1057
|
+
let offset = 0;
|
|
1058
|
+
for (const nalUnit of nalUnits) {
|
|
1059
|
+
result.set(ANNEX_B_START_CODE, offset);
|
|
1060
|
+
offset += ANNEX_B_START_CODE.byteLength;
|
|
1061
|
+
result.set(nalUnit, offset);
|
|
1062
|
+
offset += nalUnit.byteLength;
|
|
1063
|
+
}
|
|
1064
|
+
return result;
|
|
1065
|
+
};
|
|
1066
|
+
const concatNalUnitsInLengthPrefixed = (nalUnits, lengthSize) => {
|
|
1067
|
+
const totalLength = nalUnits.reduce((a, b) => a + lengthSize + b.byteLength, 0);
|
|
1068
|
+
const result = new Uint8Array(totalLength);
|
|
1069
|
+
let offset = 0;
|
|
1070
|
+
for (const nalUnit of nalUnits) {
|
|
1071
|
+
const dataView = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
|
1072
|
+
switch (lengthSize) {
|
|
1073
|
+
case 1:
|
|
1074
|
+
dataView.setUint8(offset, nalUnit.byteLength);
|
|
1075
|
+
break;
|
|
1076
|
+
case 2:
|
|
1077
|
+
dataView.setUint16(offset, nalUnit.byteLength, false);
|
|
1078
|
+
break;
|
|
1079
|
+
case 3:
|
|
1080
|
+
setUint24(dataView, offset, nalUnit.byteLength);
|
|
1081
|
+
break;
|
|
1082
|
+
case 4:
|
|
1083
|
+
dataView.setUint32(offset, nalUnit.byteLength, false);
|
|
1084
|
+
break;
|
|
1085
|
+
}
|
|
1086
|
+
offset += lengthSize;
|
|
1087
|
+
result.set(nalUnit, offset);
|
|
1088
|
+
offset += nalUnit.byteLength;
|
|
1089
|
+
}
|
|
1090
|
+
return result;
|
|
1091
|
+
};
|
|
1017
1092
|
const extractAvcNalUnits = (packetData, decoderConfig) => {
|
|
1018
1093
|
if (decoderConfig.description) {
|
|
1019
1094
|
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
|
|
@@ -1027,6 +1102,19 @@
|
|
|
1027
1102
|
return findNalUnitsInAnnexB(packetData);
|
|
1028
1103
|
}
|
|
1029
1104
|
};
|
|
1105
|
+
const concatAvcNalUnits = (nalUnits, decoderConfig) => {
|
|
1106
|
+
if (decoderConfig.description) {
|
|
1107
|
+
// Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
|
|
1108
|
+
const bytes = toUint8Array(decoderConfig.description);
|
|
1109
|
+
const lengthSizeMinusOne = bytes[4] & 0b11;
|
|
1110
|
+
const lengthSize = (lengthSizeMinusOne + 1);
|
|
1111
|
+
return concatNalUnitsInLengthPrefixed(nalUnits, lengthSize);
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
// Stream is in Annex B format
|
|
1115
|
+
return concatNalUnitsInAnnexB(nalUnits);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1030
1118
|
const extractNalUnitTypeForAvc = (data) => {
|
|
1031
1119
|
return data[0] & 0x1F;
|
|
1032
1120
|
};
|
|
@@ -1901,6 +1989,60 @@
|
|
|
1901
1989
|
}
|
|
1902
1990
|
}
|
|
1903
1991
|
}
|
|
1992
|
+
// Frame size
|
|
1993
|
+
const frameWidthBitsMinus1 = bitstream.readBits(4);
|
|
1994
|
+
const frameHeightBitsMinus1 = bitstream.readBits(4);
|
|
1995
|
+
const n1 = frameWidthBitsMinus1 + 1;
|
|
1996
|
+
bitstream.skipBits(n1); // max_frame_width_minus_1
|
|
1997
|
+
const n2 = frameHeightBitsMinus1 + 1;
|
|
1998
|
+
bitstream.skipBits(n2); // max_frame_height_minus_1
|
|
1999
|
+
// Frame IDs
|
|
2000
|
+
let frameIdNumbersPresentFlag = 0;
|
|
2001
|
+
if (reducedStillPictureHeader) {
|
|
2002
|
+
frameIdNumbersPresentFlag = 0;
|
|
2003
|
+
}
|
|
2004
|
+
else {
|
|
2005
|
+
frameIdNumbersPresentFlag = bitstream.readBits(1);
|
|
2006
|
+
}
|
|
2007
|
+
if (frameIdNumbersPresentFlag) {
|
|
2008
|
+
bitstream.skipBits(4); // delta_frame_id_length_minus_2
|
|
2009
|
+
bitstream.skipBits(3); // additional_frame_id_length_minus_1
|
|
2010
|
+
}
|
|
2011
|
+
bitstream.skipBits(1); // use_128x128_superblock
|
|
2012
|
+
bitstream.skipBits(1); // enable_filter_intra
|
|
2013
|
+
bitstream.skipBits(1); // enable_intra_edge_filter
|
|
2014
|
+
if (!reducedStillPictureHeader) {
|
|
2015
|
+
bitstream.skipBits(1); // enable_interintra_compound
|
|
2016
|
+
bitstream.skipBits(1); // enable_masked_compound
|
|
2017
|
+
bitstream.skipBits(1); // enable_warped_motion
|
|
2018
|
+
bitstream.skipBits(1); // enable_dual_filter
|
|
2019
|
+
const enableOrderHint = bitstream.readBits(1);
|
|
2020
|
+
if (enableOrderHint) {
|
|
2021
|
+
bitstream.skipBits(1); // enable_jnt_comp
|
|
2022
|
+
bitstream.skipBits(1); // enable_ref_frame_mvs
|
|
2023
|
+
}
|
|
2024
|
+
const seqChooseScreenContentTools = bitstream.readBits(1);
|
|
2025
|
+
let seqForceScreenContentTools = 0;
|
|
2026
|
+
if (seqChooseScreenContentTools) {
|
|
2027
|
+
seqForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS
|
|
2028
|
+
}
|
|
2029
|
+
else {
|
|
2030
|
+
seqForceScreenContentTools = bitstream.readBits(1);
|
|
2031
|
+
}
|
|
2032
|
+
if (seqForceScreenContentTools > 0) {
|
|
2033
|
+
const seqChooseIntegerMv = bitstream.readBits(1);
|
|
2034
|
+
if (!seqChooseIntegerMv) {
|
|
2035
|
+
bitstream.skipBits(1); // seq_force_integer_mv
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (enableOrderHint) {
|
|
2039
|
+
bitstream.skipBits(3); // order_hint_bits_minus_1
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
bitstream.skipBits(1); // enable_superres
|
|
2043
|
+
bitstream.skipBits(1); // enable_cdef
|
|
2044
|
+
bitstream.skipBits(1); // enable_restoration
|
|
2045
|
+
// color_config()
|
|
1904
2046
|
const highBitdepth = bitstream.readBits(1);
|
|
1905
2047
|
let bitDepth = 8;
|
|
1906
2048
|
if (seqProfile === 2 && highBitdepth) {
|
|
@@ -2075,7 +2217,59 @@
|
|
|
2075
2217
|
case 'avc':
|
|
2076
2218
|
{
|
|
2077
2219
|
const nalUnits = extractAvcNalUnits(packetData, decoderConfig);
|
|
2078
|
-
|
|
2220
|
+
let isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === AvcNalUnitType.IDR);
|
|
2221
|
+
if (!isKeyframe && (!isChromium() || getChromiumVersion() >= 144)) {
|
|
2222
|
+
// In addition to IDR, Recovery Point SEI also counts as a valid H.264 keyframe by current consensus.
|
|
2223
|
+
// See https://github.com/w3c/webcodecs/issues/650 for the relevant discussion. WebKit and Firefox have
|
|
2224
|
+
// always supported them, but Chromium hasn't, therefore the (admittedly dirty) version check.
|
|
2225
|
+
for (const nalUnit of nalUnits) {
|
|
2226
|
+
const type = extractNalUnitTypeForAvc(nalUnit);
|
|
2227
|
+
if (type !== AvcNalUnitType.SEI) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
const bytes = removeEmulationPreventionBytes(nalUnit);
|
|
2231
|
+
let pos = 1; // Skip NALU header
|
|
2232
|
+
// sei_rbsp()
|
|
2233
|
+
do {
|
|
2234
|
+
// sei_message()
|
|
2235
|
+
let payloadType = 0;
|
|
2236
|
+
while (true) {
|
|
2237
|
+
const nextByte = bytes[pos++];
|
|
2238
|
+
if (nextByte === undefined)
|
|
2239
|
+
break;
|
|
2240
|
+
payloadType += nextByte;
|
|
2241
|
+
if (nextByte < 255) {
|
|
2242
|
+
break;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
let payloadSize = 0;
|
|
2246
|
+
while (true) {
|
|
2247
|
+
const nextByte = bytes[pos++];
|
|
2248
|
+
if (nextByte === undefined)
|
|
2249
|
+
break;
|
|
2250
|
+
payloadSize += nextByte;
|
|
2251
|
+
if (nextByte < 255) {
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
// sei_payload()
|
|
2256
|
+
const PAYLOAD_TYPE_RECOVERY_POINT = 6;
|
|
2257
|
+
if (payloadType === PAYLOAD_TYPE_RECOVERY_POINT) {
|
|
2258
|
+
const bitstream = new Bitstream(bytes);
|
|
2259
|
+
bitstream.pos = 8 * pos;
|
|
2260
|
+
const recoveryFrameCount = readExpGolomb(bitstream);
|
|
2261
|
+
const exactMatchFlag = bitstream.readBits(1);
|
|
2262
|
+
if (recoveryFrameCount === 0 && exactMatchFlag === 1) {
|
|
2263
|
+
// https://github.com/w3c/webcodecs/pull/910
|
|
2264
|
+
// "recovery_frame_cnt == 0 and exact_match_flag=1 in the SEI recovery payload"
|
|
2265
|
+
isKeyframe = true;
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
pos += payloadSize;
|
|
2270
|
+
} while (pos < bytes.length - 1);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2079
2273
|
return isKeyframe ? 'key' : 'delta';
|
|
2080
2274
|
}
|
|
2081
2275
|
case 'hevc':
|
|
@@ -2560,6 +2754,79 @@
|
|
|
2560
2754
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
2561
2755
|
*/
|
|
2562
2756
|
polyfillSymbolDispose();
|
|
2757
|
+
// Let's manually handle logging the garbage collection errors that are typically logged by the browser. This way, they
|
|
2758
|
+
// also kick for audio samples (which is normally not the case), making sure any incorrect code is quickly caught.
|
|
2759
|
+
let lastVideoGcErrorLog = -Infinity;
|
|
2760
|
+
let lastAudioGcErrorLog = -Infinity;
|
|
2761
|
+
let finalizationRegistry = null;
|
|
2762
|
+
if (typeof FinalizationRegistry !== 'undefined') {
|
|
2763
|
+
finalizationRegistry = new FinalizationRegistry((value) => {
|
|
2764
|
+
const now = Date.now();
|
|
2765
|
+
if (value.type === 'video') {
|
|
2766
|
+
if (now - lastVideoGcErrorLog >= 1000) {
|
|
2767
|
+
// This error is annoying but oh so important
|
|
2768
|
+
console.error(`A VideoSample was garbage collected without first being closed. For proper resource management,`
|
|
2769
|
+
+ ` make sure to call close() on all your VideoSamples as soon as you're done using them.`);
|
|
2770
|
+
lastVideoGcErrorLog = now;
|
|
2771
|
+
}
|
|
2772
|
+
if (typeof VideoFrame !== 'undefined' && value.data instanceof VideoFrame) {
|
|
2773
|
+
value.data.close(); // Prevent the browser error since we're logging our own
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
else {
|
|
2777
|
+
if (now - lastAudioGcErrorLog >= 1000) {
|
|
2778
|
+
console.error(`An AudioSample was garbage collected without first being closed. For proper resource management,`
|
|
2779
|
+
+ ` make sure to call close() on all your AudioSamples as soon as you're done using them.`);
|
|
2780
|
+
lastAudioGcErrorLog = now;
|
|
2781
|
+
}
|
|
2782
|
+
if (typeof AudioData !== 'undefined' && value.data instanceof AudioData) {
|
|
2783
|
+
value.data.close();
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* The list of {@link VideoSample} pixel formats.
|
|
2790
|
+
* @group Samples
|
|
2791
|
+
* @public
|
|
2792
|
+
*/
|
|
2793
|
+
const VIDEO_SAMPLE_PIXEL_FORMATS = [
|
|
2794
|
+
// 4:2:0 Y, U, V
|
|
2795
|
+
'I420',
|
|
2796
|
+
'I420P10',
|
|
2797
|
+
'I420P12',
|
|
2798
|
+
// 4:2:0 Y, U, V, A
|
|
2799
|
+
'I420A',
|
|
2800
|
+
'I420AP10',
|
|
2801
|
+
'I420AP12',
|
|
2802
|
+
// 4:2:2 Y, U, V
|
|
2803
|
+
'I422',
|
|
2804
|
+
'I422P10',
|
|
2805
|
+
'I422P12',
|
|
2806
|
+
// 4:2:2 Y, U, V, A
|
|
2807
|
+
'I422A',
|
|
2808
|
+
'I422AP10',
|
|
2809
|
+
'I422AP12',
|
|
2810
|
+
// 4:4:4 Y, U, V
|
|
2811
|
+
'I444',
|
|
2812
|
+
'I444P10',
|
|
2813
|
+
'I444P12',
|
|
2814
|
+
// 4:4:4 Y, U, V, A
|
|
2815
|
+
'I444A',
|
|
2816
|
+
'I444AP10',
|
|
2817
|
+
'I444AP12',
|
|
2818
|
+
// 4:2:0 Y, UV
|
|
2819
|
+
'NV12',
|
|
2820
|
+
// 4:4:4 RGBA
|
|
2821
|
+
'RGBA',
|
|
2822
|
+
// 4:4:4 RGBX (opaque)
|
|
2823
|
+
'RGBX',
|
|
2824
|
+
// 4:4:4 BGRA
|
|
2825
|
+
'BGRA',
|
|
2826
|
+
// 4:4:4 BGRX (opaque)
|
|
2827
|
+
'BGRX',
|
|
2828
|
+
];
|
|
2829
|
+
const VIDEO_SAMPLE_PIXEL_FORMATS_SET = new Set(VIDEO_SAMPLE_PIXEL_FORMATS);
|
|
2563
2830
|
/**
|
|
2564
2831
|
* Represents a raw, unencoded video sample (frame). Mainly used as an expressive wrapper around WebCodecs API's
|
|
2565
2832
|
* [`VideoFrame`](https://developer.mozilla.org/en-US/docs/Web/API/VideoFrame), but can also be used standalone.
|
|
@@ -2593,12 +2860,14 @@
|
|
|
2593
2860
|
constructor(data, init) {
|
|
2594
2861
|
/** @internal */
|
|
2595
2862
|
this._closed = false;
|
|
2596
|
-
if (data instanceof ArrayBuffer
|
|
2863
|
+
if (data instanceof ArrayBuffer
|
|
2864
|
+
|| (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
|
|
2865
|
+
|| ArrayBuffer.isView(data)) {
|
|
2597
2866
|
if (!init || typeof init !== 'object') {
|
|
2598
2867
|
throw new TypeError('init must be an object.');
|
|
2599
2868
|
}
|
|
2600
|
-
if (
|
|
2601
|
-
throw new TypeError('init.format must be
|
|
2869
|
+
if (init.format === undefined || !VIDEO_SAMPLE_PIXEL_FORMATS_SET.has(init.format)) {
|
|
2870
|
+
throw new TypeError('init.format must be one of: ' + VIDEO_SAMPLE_PIXEL_FORMATS.join(', '));
|
|
2602
2871
|
}
|
|
2603
2872
|
if (!Number.isInteger(init.codedWidth) || init.codedWidth <= 0) {
|
|
2604
2873
|
throw new TypeError('init.codedWidth must be a positive integer.');
|
|
@@ -2616,13 +2885,14 @@
|
|
|
2616
2885
|
throw new TypeError('init.duration, when provided, must be a non-negative number.');
|
|
2617
2886
|
}
|
|
2618
2887
|
this._data = toUint8Array(data).slice(); // Copy it
|
|
2888
|
+
this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
|
|
2619
2889
|
this.format = init.format;
|
|
2620
2890
|
this.codedWidth = init.codedWidth;
|
|
2621
2891
|
this.codedHeight = init.codedHeight;
|
|
2622
2892
|
this.rotation = init.rotation ?? 0;
|
|
2623
2893
|
this.timestamp = init.timestamp;
|
|
2624
2894
|
this.duration = init.duration ?? 0;
|
|
2625
|
-
this.colorSpace = new
|
|
2895
|
+
this.colorSpace = new VideoSampleColorSpace(init.colorSpace);
|
|
2626
2896
|
}
|
|
2627
2897
|
else if (typeof VideoFrame !== 'undefined' && data instanceof VideoFrame) {
|
|
2628
2898
|
if (init?.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
@@ -2635,6 +2905,7 @@
|
|
|
2635
2905
|
throw new TypeError('init.duration, when provided, must be a non-negative number.');
|
|
2636
2906
|
}
|
|
2637
2907
|
this._data = data;
|
|
2908
|
+
this._layout = null;
|
|
2638
2909
|
this.format = data.format;
|
|
2639
2910
|
// Copying the display dimensions here, assuming no innate VideoFrame rotation
|
|
2640
2911
|
this.codedWidth = data.displayWidth;
|
|
@@ -2644,7 +2915,7 @@
|
|
|
2644
2915
|
this.rotation = init?.rotation ?? 0;
|
|
2645
2916
|
this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
|
|
2646
2917
|
this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
|
|
2647
|
-
this.colorSpace = data.colorSpace;
|
|
2918
|
+
this.colorSpace = new VideoSampleColorSpace(data.colorSpace);
|
|
2648
2919
|
}
|
|
2649
2920
|
else if ((typeof HTMLImageElement !== 'undefined' && data instanceof HTMLImageElement)
|
|
2650
2921
|
|| (typeof SVGImageElement !== 'undefined' && data instanceof SVGImageElement)
|
|
@@ -2698,13 +2969,14 @@
|
|
|
2698
2969
|
// Draw it to a canvas
|
|
2699
2970
|
context.drawImage(data, 0, 0);
|
|
2700
2971
|
this._data = canvas;
|
|
2972
|
+
this._layout = null;
|
|
2701
2973
|
this.format = 'RGBX';
|
|
2702
2974
|
this.codedWidth = width;
|
|
2703
2975
|
this.codedHeight = height;
|
|
2704
2976
|
this.rotation = init.rotation ?? 0;
|
|
2705
2977
|
this.timestamp = init.timestamp;
|
|
2706
2978
|
this.duration = init.duration ?? 0;
|
|
2707
|
-
this.colorSpace = new
|
|
2979
|
+
this.colorSpace = new VideoSampleColorSpace({
|
|
2708
2980
|
matrix: 'rgb',
|
|
2709
2981
|
primaries: 'bt709',
|
|
2710
2982
|
transfer: 'iec61966-2-1',
|
|
@@ -2714,6 +2986,7 @@
|
|
|
2714
2986
|
else {
|
|
2715
2987
|
throw new TypeError('Invalid data type: Must be a BufferSource or CanvasImageSource.');
|
|
2716
2988
|
}
|
|
2989
|
+
finalizationRegistry?.register(this, { type: 'video', data: this._data }, this);
|
|
2717
2990
|
}
|
|
2718
2991
|
/** Clones this video sample. */
|
|
2719
2992
|
clone() {
|
|
@@ -2729,8 +3002,10 @@
|
|
|
2729
3002
|
});
|
|
2730
3003
|
}
|
|
2731
3004
|
else if (this._data instanceof Uint8Array) {
|
|
2732
|
-
|
|
3005
|
+
assert(this._layout);
|
|
3006
|
+
return new VideoSample(this._data, {
|
|
2733
3007
|
format: this.format,
|
|
3008
|
+
layout: this._layout,
|
|
2734
3009
|
codedWidth: this.codedWidth,
|
|
2735
3010
|
codedHeight: this.codedHeight,
|
|
2736
3011
|
timestamp: this.timestamp,
|
|
@@ -2759,6 +3034,7 @@
|
|
|
2759
3034
|
if (this._closed) {
|
|
2760
3035
|
return;
|
|
2761
3036
|
}
|
|
3037
|
+
finalizationRegistry?.unregister(this);
|
|
2762
3038
|
if (isVideoFrame(this._data)) {
|
|
2763
3039
|
this._data.close();
|
|
2764
3040
|
}
|
|
@@ -2767,14 +3043,34 @@
|
|
|
2767
3043
|
}
|
|
2768
3044
|
this._closed = true;
|
|
2769
3045
|
}
|
|
2770
|
-
/**
|
|
2771
|
-
|
|
3046
|
+
/**
|
|
3047
|
+
* Returns the number of bytes required to hold this video sample's pixel data. Throws if `format` is `null`;
|
|
3048
|
+
* specify an explicit RGB format in the options in this case.
|
|
3049
|
+
*/
|
|
3050
|
+
allocationSize(options = {}) {
|
|
3051
|
+
validateVideoFrameCopyToOptions(options);
|
|
2772
3052
|
if (this._closed) {
|
|
2773
3053
|
throw new Error('VideoSample is closed.');
|
|
2774
3054
|
}
|
|
3055
|
+
if ((options.format ?? this.format) === null) {
|
|
3056
|
+
throw new Error('Cannot get allocation size when format is null. Please manually provide an RGB pixel format in the'
|
|
3057
|
+
+ ' options instead.');
|
|
3058
|
+
}
|
|
2775
3059
|
assert(this._data !== null);
|
|
3060
|
+
if (!isVideoFrame(this._data)) {
|
|
3061
|
+
if (options.colorSpace
|
|
3062
|
+
|| (options.format && options.format !== this.format)
|
|
3063
|
+
|| options.layout
|
|
3064
|
+
|| options.rect) {
|
|
3065
|
+
// Temporarily convert to VideoFrame to get it done
|
|
3066
|
+
const videoFrame = this.toVideoFrame();
|
|
3067
|
+
const size = videoFrame.allocationSize(options);
|
|
3068
|
+
videoFrame.close();
|
|
3069
|
+
return size;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
2776
3072
|
if (isVideoFrame(this._data)) {
|
|
2777
|
-
return this._data.allocationSize();
|
|
3073
|
+
return this._data.allocationSize(options);
|
|
2778
3074
|
}
|
|
2779
3075
|
else if (this._data instanceof Uint8Array) {
|
|
2780
3076
|
return this._data.byteLength;
|
|
@@ -2783,21 +3079,44 @@
|
|
|
2783
3079
|
return this.codedWidth * this.codedHeight * 4; // RGBX
|
|
2784
3080
|
}
|
|
2785
3081
|
}
|
|
2786
|
-
/**
|
|
2787
|
-
|
|
3082
|
+
/**
|
|
3083
|
+
* Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView. Throws if `format` is `null`;
|
|
3084
|
+
* specify an explicit RGB format in the options in this case.
|
|
3085
|
+
* @returns The byte layout of the planes of the copied data.
|
|
3086
|
+
*/
|
|
3087
|
+
async copyTo(destination, options = {}) {
|
|
2788
3088
|
if (!isAllowSharedBufferSource(destination)) {
|
|
2789
3089
|
throw new TypeError('destination must be an ArrayBuffer or an ArrayBuffer view.');
|
|
2790
3090
|
}
|
|
3091
|
+
validateVideoFrameCopyToOptions(options);
|
|
2791
3092
|
if (this._closed) {
|
|
2792
3093
|
throw new Error('VideoSample is closed.');
|
|
2793
3094
|
}
|
|
3095
|
+
if ((options.format ?? this.format) === null) {
|
|
3096
|
+
throw new Error('Cannot copy video sample data when format is null. Please manually provide an RGB pixel format in the'
|
|
3097
|
+
+ ' options instead.');
|
|
3098
|
+
}
|
|
2794
3099
|
assert(this._data !== null);
|
|
3100
|
+
if (!isVideoFrame(this._data)) {
|
|
3101
|
+
if (options.colorSpace
|
|
3102
|
+
|| (options.format && options.format !== this.format)
|
|
3103
|
+
|| options.layout
|
|
3104
|
+
|| options.rect) {
|
|
3105
|
+
// Temporarily convert to VideoFrame to get it done
|
|
3106
|
+
const videoFrame = this.toVideoFrame();
|
|
3107
|
+
const layout = await videoFrame.copyTo(destination, options);
|
|
3108
|
+
videoFrame.close();
|
|
3109
|
+
return layout;
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
2795
3112
|
if (isVideoFrame(this._data)) {
|
|
2796
|
-
|
|
3113
|
+
return this._data.copyTo(destination, options);
|
|
2797
3114
|
}
|
|
2798
3115
|
else if (this._data instanceof Uint8Array) {
|
|
3116
|
+
assert(this._layout);
|
|
2799
3117
|
const dest = toUint8Array(destination);
|
|
2800
3118
|
dest.set(this._data);
|
|
3119
|
+
return this._layout;
|
|
2801
3120
|
}
|
|
2802
3121
|
else {
|
|
2803
3122
|
const canvas = this._data;
|
|
@@ -2806,6 +3125,10 @@
|
|
|
2806
3125
|
const imageData = context.getImageData(0, 0, this.codedWidth, this.codedHeight);
|
|
2807
3126
|
const dest = toUint8Array(destination);
|
|
2808
3127
|
dest.set(imageData.data);
|
|
3128
|
+
return [{
|
|
3129
|
+
offset: 0,
|
|
3130
|
+
stride: 4 * this.codedWidth,
|
|
3131
|
+
}];
|
|
2809
3132
|
}
|
|
2810
3133
|
}
|
|
2811
3134
|
/**
|
|
@@ -3064,6 +3387,29 @@
|
|
|
3064
3387
|
this.close();
|
|
3065
3388
|
}
|
|
3066
3389
|
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Describes the color space of a {@link VideoSample}. Corresponds to the WebCodecs API's VideoColorSpace.
|
|
3392
|
+
* @group Samples
|
|
3393
|
+
* @public
|
|
3394
|
+
*/
|
|
3395
|
+
class VideoSampleColorSpace {
|
|
3396
|
+
/** Creates a new VideoSampleColorSpace. */
|
|
3397
|
+
constructor(init) {
|
|
3398
|
+
this.primaries = init?.primaries ?? null;
|
|
3399
|
+
this.transfer = init?.transfer ?? null;
|
|
3400
|
+
this.matrix = init?.matrix ?? null;
|
|
3401
|
+
this.fullRange = init?.fullRange ?? null;
|
|
3402
|
+
}
|
|
3403
|
+
/** Serializes the color space to a JSON object. */
|
|
3404
|
+
toJSON() {
|
|
3405
|
+
return {
|
|
3406
|
+
primaries: this.primaries,
|
|
3407
|
+
transfer: this.transfer,
|
|
3408
|
+
matrix: this.matrix,
|
|
3409
|
+
fullRange: this.fullRange,
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3067
3413
|
const isVideoFrame = (x) => {
|
|
3068
3414
|
return typeof VideoFrame !== 'undefined' && x instanceof VideoFrame;
|
|
3069
3415
|
};
|
|
@@ -3092,6 +3438,133 @@
|
|
|
3092
3438
|
throw new TypeError(prefix + 'crop.height must be a non-negative integer.');
|
|
3093
3439
|
}
|
|
3094
3440
|
};
|
|
3441
|
+
const validateVideoFrameCopyToOptions = (options) => {
|
|
3442
|
+
if (!options || typeof options !== 'object') {
|
|
3443
|
+
throw new TypeError('options must be an object.');
|
|
3444
|
+
}
|
|
3445
|
+
if (options.colorSpace !== undefined && !['display-p3', 'srgb'].includes(options.colorSpace)) {
|
|
3446
|
+
throw new TypeError('options.colorSpace, when provided, must be \'display-p3\' or \'srgb\'.');
|
|
3447
|
+
}
|
|
3448
|
+
if (options.format !== undefined && typeof options.format !== 'string') {
|
|
3449
|
+
throw new TypeError('options.format, when provided, must be a string.');
|
|
3450
|
+
}
|
|
3451
|
+
if (options.layout !== undefined) {
|
|
3452
|
+
if (!Array.isArray(options.layout)) {
|
|
3453
|
+
throw new TypeError('options.layout, when provided, must be an array.');
|
|
3454
|
+
}
|
|
3455
|
+
for (const plane of options.layout) {
|
|
3456
|
+
if (!plane || typeof plane !== 'object') {
|
|
3457
|
+
throw new TypeError('Each entry in options.layout must be an object.');
|
|
3458
|
+
}
|
|
3459
|
+
if (!Number.isInteger(plane.offset) || plane.offset < 0) {
|
|
3460
|
+
throw new TypeError('plane.offset must be a non-negative integer.');
|
|
3461
|
+
}
|
|
3462
|
+
if (!Number.isInteger(plane.stride) || plane.stride < 0) {
|
|
3463
|
+
throw new TypeError('plane.stride must be a non-negative integer.');
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (options.rect !== undefined) {
|
|
3468
|
+
if (!options.rect || typeof options.rect !== 'object') {
|
|
3469
|
+
throw new TypeError('options.rect, when provided, must be an object.');
|
|
3470
|
+
}
|
|
3471
|
+
if (options.rect.x !== undefined && (!Number.isInteger(options.rect.x) || options.rect.x < 0)) {
|
|
3472
|
+
throw new TypeError('options.rect.x, when provided, must be a non-negative integer.');
|
|
3473
|
+
}
|
|
3474
|
+
if (options.rect.y !== undefined && (!Number.isInteger(options.rect.y) || options.rect.y < 0)) {
|
|
3475
|
+
throw new TypeError('options.rect.y, when provided, must be a non-negative integer.');
|
|
3476
|
+
}
|
|
3477
|
+
if (options.rect.width !== undefined && (!Number.isInteger(options.rect.width) || options.rect.width < 0)) {
|
|
3478
|
+
throw new TypeError('options.rect.width, when provided, must be a non-negative integer.');
|
|
3479
|
+
}
|
|
3480
|
+
if (options.rect.height !== undefined && (!Number.isInteger(options.rect.height) || options.rect.height < 0)) {
|
|
3481
|
+
throw new TypeError('options.rect.height, when provided, must be a non-negative integer.');
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
/** Implements logic from WebCodecs § 9.4.6 "Compute Layout and Allocation Size" */
|
|
3486
|
+
const createDefaultPlaneLayout = (format, codedWidth, codedHeight) => {
|
|
3487
|
+
const planes = getPlaneConfigs(format);
|
|
3488
|
+
const layouts = [];
|
|
3489
|
+
let currentOffset = 0;
|
|
3490
|
+
for (const plane of planes) {
|
|
3491
|
+
// Per § 9.8, dimensions are usually "rounded up to the nearest integer".
|
|
3492
|
+
const planeWidth = Math.ceil(codedWidth / plane.widthDivisor);
|
|
3493
|
+
const planeHeight = Math.ceil(codedHeight / plane.heightDivisor);
|
|
3494
|
+
const stride = planeWidth * plane.sampleBytes;
|
|
3495
|
+
// Tight packing
|
|
3496
|
+
const planeSize = stride * planeHeight;
|
|
3497
|
+
layouts.push({
|
|
3498
|
+
offset: currentOffset,
|
|
3499
|
+
stride: stride,
|
|
3500
|
+
});
|
|
3501
|
+
currentOffset += planeSize;
|
|
3502
|
+
}
|
|
3503
|
+
return layouts;
|
|
3504
|
+
};
|
|
3505
|
+
/** Helper to retrieve plane configurations based on WebCodecs § 9.8 Pixel Format definitions. */
|
|
3506
|
+
const getPlaneConfigs = (format) => {
|
|
3507
|
+
// Helper for standard YUV planes
|
|
3508
|
+
const yuv = (yBytes, uvBytes, subX, subY, hasAlpha) => {
|
|
3509
|
+
const configs = [
|
|
3510
|
+
{ sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 },
|
|
3511
|
+
{ sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY },
|
|
3512
|
+
{ sampleBytes: uvBytes, widthDivisor: subX, heightDivisor: subY },
|
|
3513
|
+
];
|
|
3514
|
+
if (hasAlpha) {
|
|
3515
|
+
// Match luma dimensions
|
|
3516
|
+
configs.push({ sampleBytes: yBytes, widthDivisor: 1, heightDivisor: 1 });
|
|
3517
|
+
}
|
|
3518
|
+
return configs;
|
|
3519
|
+
};
|
|
3520
|
+
switch (format) {
|
|
3521
|
+
case 'I420':
|
|
3522
|
+
return yuv(1, 1, 2, 2, false);
|
|
3523
|
+
case 'I420P10':
|
|
3524
|
+
case 'I420P12':
|
|
3525
|
+
return yuv(2, 2, 2, 2, false);
|
|
3526
|
+
case 'I420A':
|
|
3527
|
+
return yuv(1, 1, 2, 2, true);
|
|
3528
|
+
case 'I420AP10':
|
|
3529
|
+
case 'I420AP12':
|
|
3530
|
+
return yuv(2, 2, 2, 2, true);
|
|
3531
|
+
case 'I422':
|
|
3532
|
+
return yuv(1, 1, 2, 1, false);
|
|
3533
|
+
case 'I422P10':
|
|
3534
|
+
case 'I422P12':
|
|
3535
|
+
return yuv(2, 2, 2, 1, false);
|
|
3536
|
+
case 'I422A':
|
|
3537
|
+
return yuv(1, 1, 2, 1, true);
|
|
3538
|
+
case 'I422AP10':
|
|
3539
|
+
case 'I422AP12':
|
|
3540
|
+
return yuv(2, 2, 2, 1, true);
|
|
3541
|
+
case 'I444':
|
|
3542
|
+
return yuv(1, 1, 1, 1, false);
|
|
3543
|
+
case 'I444P10':
|
|
3544
|
+
case 'I444P12':
|
|
3545
|
+
return yuv(2, 2, 1, 1, false);
|
|
3546
|
+
case 'I444A':
|
|
3547
|
+
return yuv(1, 1, 1, 1, true);
|
|
3548
|
+
case 'I444AP10':
|
|
3549
|
+
case 'I444AP12':
|
|
3550
|
+
return yuv(2, 2, 1, 1, true);
|
|
3551
|
+
case 'NV12':
|
|
3552
|
+
return [
|
|
3553
|
+
{ sampleBytes: 1, widthDivisor: 1, heightDivisor: 1 },
|
|
3554
|
+
{ sampleBytes: 2, widthDivisor: 2, heightDivisor: 2 }, // Interleaved U and V
|
|
3555
|
+
];
|
|
3556
|
+
case 'RGBA':
|
|
3557
|
+
case 'RGBX':
|
|
3558
|
+
case 'BGRA':
|
|
3559
|
+
case 'BGRX':
|
|
3560
|
+
return [
|
|
3561
|
+
{ sampleBytes: 4, widthDivisor: 1, heightDivisor: 1 },
|
|
3562
|
+
];
|
|
3563
|
+
default:
|
|
3564
|
+
assertNever(format);
|
|
3565
|
+
assert(false);
|
|
3566
|
+
}
|
|
3567
|
+
};
|
|
3095
3568
|
const AUDIO_SAMPLE_FORMATS = new Set(['f32', 'f32-planar', 's16', 's16-planar', 's32', 's32-planar', 'u8', 'u8-planar']);
|
|
3096
3569
|
/**
|
|
3097
3570
|
* Represents a raw, unencoded audio sample. Mainly used as an expressive wrapper around WebCodecs API's
|
|
@@ -3170,6 +3643,7 @@
|
|
|
3170
3643
|
}
|
|
3171
3644
|
this._data = dataBuffer;
|
|
3172
3645
|
}
|
|
3646
|
+
finalizationRegistry?.register(this, { type: 'audio', data: this._data }, this);
|
|
3173
3647
|
}
|
|
3174
3648
|
/** Returns the number of bytes required to hold the audio sample's data as specified by the given options. */
|
|
3175
3649
|
allocationSize(options) {
|
|
@@ -3235,6 +3709,7 @@
|
|
|
3235
3709
|
throw new Error('AudioSample is closed.');
|
|
3236
3710
|
}
|
|
3237
3711
|
const { planeIndex, format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options;
|
|
3712
|
+
const srcFormat = this.format;
|
|
3238
3713
|
const destFormat = format ?? this.format;
|
|
3239
3714
|
if (!destFormat)
|
|
3240
3715
|
throw new Error('Destination format not determined');
|
|
@@ -3264,60 +3739,24 @@
|
|
|
3264
3739
|
const destView = toDataView(destination);
|
|
3265
3740
|
const writeFn = getWriteFunction(destFormat);
|
|
3266
3741
|
if (isAudioData(this._data)) {
|
|
3267
|
-
if (
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
this._data.copyTo(destination, {
|
|
3271
|
-
planeIndex,
|
|
3272
|
-
frameOffset,
|
|
3273
|
-
frameCount: copyFrameCount,
|
|
3274
|
-
format: 'f32-planar',
|
|
3275
|
-
});
|
|
3276
|
-
}
|
|
3277
|
-
else {
|
|
3278
|
-
// Allocate temporary buffer for f32-planar data
|
|
3279
|
-
const tempBuffer = new ArrayBuffer(copyFrameCount * 4);
|
|
3280
|
-
const tempArray = new Float32Array(tempBuffer);
|
|
3281
|
-
this._data.copyTo(tempArray, {
|
|
3282
|
-
planeIndex,
|
|
3283
|
-
frameOffset,
|
|
3284
|
-
frameCount: copyFrameCount,
|
|
3285
|
-
format: 'f32-planar',
|
|
3286
|
-
});
|
|
3287
|
-
// Convert each f32 sample to destination format
|
|
3288
|
-
const tempView = new DataView(tempBuffer);
|
|
3289
|
-
for (let i = 0; i < copyFrameCount; i++) {
|
|
3290
|
-
const destOffset = i * destBytesPerSample;
|
|
3291
|
-
const sample = tempView.getFloat32(i * 4, true);
|
|
3292
|
-
writeFn(destView, destOffset, sample);
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3742
|
+
if (isWebKit() && numChannels > 2 && destFormat !== srcFormat) {
|
|
3743
|
+
// WebKit bug workaround
|
|
3744
|
+
doAudioDataCopyToWebKitWorkaround(this._data, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount);
|
|
3295
3745
|
}
|
|
3296
3746
|
else {
|
|
3297
|
-
//
|
|
3298
|
-
//
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
frameCount: copyFrameCount,
|
|
3306
|
-
format: 'f32-planar',
|
|
3307
|
-
});
|
|
3308
|
-
for (let i = 0; i < copyFrameCount; i++) {
|
|
3309
|
-
const destIndex = i * numCh + ch;
|
|
3310
|
-
const destOffset = destIndex * destBytesPerSample;
|
|
3311
|
-
writeFn(destView, destOffset, temp[i]);
|
|
3312
|
-
}
|
|
3313
|
-
}
|
|
3747
|
+
// Per spec, only f32-planar conversion must be supported, but in practice, all browsers support all
|
|
3748
|
+
// destination formats, so let's just delegate here:
|
|
3749
|
+
this._data.copyTo(destination, {
|
|
3750
|
+
planeIndex,
|
|
3751
|
+
frameOffset,
|
|
3752
|
+
frameCount: copyFrameCount,
|
|
3753
|
+
format: destFormat,
|
|
3754
|
+
});
|
|
3314
3755
|
}
|
|
3315
3756
|
}
|
|
3316
3757
|
else {
|
|
3317
|
-
// Branch for Uint8Array data (non-AudioData)
|
|
3318
3758
|
const uint8Data = this._data;
|
|
3319
|
-
const srcView =
|
|
3320
|
-
const srcFormat = this.format;
|
|
3759
|
+
const srcView = toDataView(uint8Data);
|
|
3321
3760
|
const readFn = getReadFunction(srcFormat);
|
|
3322
3761
|
const srcBytesPerSample = getBytesPerSample(srcFormat);
|
|
3323
3762
|
const srcIsPlanar = formatIsPlanar(srcFormat);
|
|
@@ -3381,6 +3820,7 @@
|
|
|
3381
3820
|
if (this._closed) {
|
|
3382
3821
|
return;
|
|
3383
3822
|
}
|
|
3823
|
+
finalizationRegistry?.unregister(this);
|
|
3384
3824
|
if (isAudioData(this._data)) {
|
|
3385
3825
|
this._data.close();
|
|
3386
3826
|
}
|
|
@@ -3441,7 +3881,9 @@
|
|
|
3441
3881
|
numberOfFrames: this.numberOfFrames,
|
|
3442
3882
|
numberOfChannels: this.numberOfChannels,
|
|
3443
3883
|
timestamp: this.microsecondTimestamp,
|
|
3444
|
-
data: this._data
|
|
3884
|
+
data: this._data.buffer instanceof ArrayBuffer
|
|
3885
|
+
? this._data.buffer
|
|
3886
|
+
: this._data.slice(), // In the case of SharedArrayBuffer, convert to ArrayBuffer
|
|
3445
3887
|
});
|
|
3446
3888
|
}
|
|
3447
3889
|
}
|
|
@@ -3607,6 +4049,99 @@
|
|
|
3607
4049
|
const isAudioData = (x) => {
|
|
3608
4050
|
return typeof AudioData !== 'undefined' && x instanceof AudioData;
|
|
3609
4051
|
};
|
|
4052
|
+
/**
|
|
4053
|
+
* WebKit has a bug where calling AudioData.copyTo with a format different from the source format
|
|
4054
|
+
* crashes the tab when there are more than 2 channels. This function works around that by always
|
|
4055
|
+
* copying with the source format and then manually converting to the destination format.
|
|
4056
|
+
*
|
|
4057
|
+
* See https://bugs.webkit.org/show_bug.cgi?id=302521.
|
|
4058
|
+
*/
|
|
4059
|
+
const doAudioDataCopyToWebKitWorkaround = (audioData, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount) => {
|
|
4060
|
+
const readFn = getReadFunction(srcFormat);
|
|
4061
|
+
const writeFn = getWriteFunction(destFormat);
|
|
4062
|
+
const srcBytesPerSample = getBytesPerSample(srcFormat);
|
|
4063
|
+
const destBytesPerSample = getBytesPerSample(destFormat);
|
|
4064
|
+
const srcIsPlanar = formatIsPlanar(srcFormat);
|
|
4065
|
+
const destIsPlanar = formatIsPlanar(destFormat);
|
|
4066
|
+
if (destIsPlanar) {
|
|
4067
|
+
if (srcIsPlanar) {
|
|
4068
|
+
// src planar -> dest planar: copy single plane and convert
|
|
4069
|
+
const data = new ArrayBuffer(copyFrameCount * srcBytesPerSample);
|
|
4070
|
+
const dataView = toDataView(data);
|
|
4071
|
+
audioData.copyTo(data, {
|
|
4072
|
+
planeIndex,
|
|
4073
|
+
frameOffset,
|
|
4074
|
+
frameCount: copyFrameCount,
|
|
4075
|
+
format: srcFormat,
|
|
4076
|
+
});
|
|
4077
|
+
for (let i = 0; i < copyFrameCount; i++) {
|
|
4078
|
+
const srcOffset = i * srcBytesPerSample;
|
|
4079
|
+
const destOffset = i * destBytesPerSample;
|
|
4080
|
+
const sample = readFn(dataView, srcOffset);
|
|
4081
|
+
writeFn(destView, destOffset, sample);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
else {
|
|
4085
|
+
// src interleaved -> dest planar: copy all interleaved data, extract one channel
|
|
4086
|
+
const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample);
|
|
4087
|
+
const dataView = toDataView(data);
|
|
4088
|
+
audioData.copyTo(data, {
|
|
4089
|
+
planeIndex: 0,
|
|
4090
|
+
frameOffset,
|
|
4091
|
+
frameCount: copyFrameCount,
|
|
4092
|
+
format: srcFormat,
|
|
4093
|
+
});
|
|
4094
|
+
for (let i = 0; i < copyFrameCount; i++) {
|
|
4095
|
+
const srcOffset = (i * numChannels + planeIndex) * srcBytesPerSample;
|
|
4096
|
+
const destOffset = i * destBytesPerSample;
|
|
4097
|
+
const sample = readFn(dataView, srcOffset);
|
|
4098
|
+
writeFn(destView, destOffset, sample);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
else {
|
|
4103
|
+
if (srcIsPlanar) {
|
|
4104
|
+
// src planar -> dest interleaved: copy each plane and interleave
|
|
4105
|
+
const planeSize = copyFrameCount * srcBytesPerSample;
|
|
4106
|
+
const data = new ArrayBuffer(planeSize);
|
|
4107
|
+
const dataView = toDataView(data);
|
|
4108
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
4109
|
+
audioData.copyTo(data, {
|
|
4110
|
+
planeIndex: ch,
|
|
4111
|
+
frameOffset,
|
|
4112
|
+
frameCount: copyFrameCount,
|
|
4113
|
+
format: srcFormat,
|
|
4114
|
+
});
|
|
4115
|
+
for (let i = 0; i < copyFrameCount; i++) {
|
|
4116
|
+
const srcOffset = i * srcBytesPerSample;
|
|
4117
|
+
const destOffset = (i * numChannels + ch) * destBytesPerSample;
|
|
4118
|
+
const sample = readFn(dataView, srcOffset);
|
|
4119
|
+
writeFn(destView, destOffset, sample);
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
else {
|
|
4124
|
+
// src interleaved -> dest interleaved: copy all and convert
|
|
4125
|
+
const data = new ArrayBuffer(copyFrameCount * numChannels * srcBytesPerSample);
|
|
4126
|
+
const dataView = toDataView(data);
|
|
4127
|
+
audioData.copyTo(data, {
|
|
4128
|
+
planeIndex: 0,
|
|
4129
|
+
frameOffset,
|
|
4130
|
+
frameCount: copyFrameCount,
|
|
4131
|
+
format: srcFormat,
|
|
4132
|
+
});
|
|
4133
|
+
for (let i = 0; i < copyFrameCount; i++) {
|
|
4134
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
4135
|
+
const idx = i * numChannels + ch;
|
|
4136
|
+
const srcOffset = idx * srcBytesPerSample;
|
|
4137
|
+
const destOffset = idx * destBytesPerSample;
|
|
4138
|
+
const sample = readFn(dataView, srcOffset);
|
|
4139
|
+
writeFn(destView, destOffset, sample);
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
};
|
|
3610
4145
|
|
|
3611
4146
|
/*!
|
|
3612
4147
|
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
@@ -3727,9 +4262,10 @@
|
|
|
3727
4262
|
return this._track._backing.getKeyPacket(timestamp, options);
|
|
3728
4263
|
}
|
|
3729
4264
|
const packet = await this._track._backing.getKeyPacket(timestamp, options);
|
|
3730
|
-
if (!packet
|
|
4265
|
+
if (!packet) {
|
|
3731
4266
|
return packet;
|
|
3732
4267
|
}
|
|
4268
|
+
assert(packet.type === 'key');
|
|
3733
4269
|
const determinedType = await this._track.determinePacketType(packet);
|
|
3734
4270
|
if (determinedType === 'delta') {
|
|
3735
4271
|
// Try returning the previous key packet (in hopes that it's actually a key packet)
|
|
@@ -3755,9 +4291,10 @@
|
|
|
3755
4291
|
return this._track._backing.getNextKeyPacket(packet, options);
|
|
3756
4292
|
}
|
|
3757
4293
|
const nextPacket = await this._track._backing.getNextKeyPacket(packet, options);
|
|
3758
|
-
if (!nextPacket
|
|
4294
|
+
if (!nextPacket) {
|
|
3759
4295
|
return nextPacket;
|
|
3760
4296
|
}
|
|
4297
|
+
assert(nextPacket.type === 'key');
|
|
3761
4298
|
const determinedType = await this._track.determinePacketType(nextPacket);
|
|
3762
4299
|
if (determinedType === 'delta') {
|
|
3763
4300
|
// Try returning the next key packet (in hopes that it's actually a key packet)
|
|
@@ -3899,7 +4436,6 @@
|
|
|
3899
4436
|
let outOfBandError = null;
|
|
3900
4437
|
// The following is the "pump" process that keeps pumping packets into the decoder
|
|
3901
4438
|
(async () => {
|
|
3902
|
-
const decoderError = new Error();
|
|
3903
4439
|
const decoder = await this._createDecoder((sample) => {
|
|
3904
4440
|
onQueueDequeue();
|
|
3905
4441
|
if (sample.timestamp >= endTimestamp) {
|
|
@@ -3933,7 +4469,6 @@
|
|
|
3933
4469
|
}
|
|
3934
4470
|
}, (error) => {
|
|
3935
4471
|
if (!outOfBandError) {
|
|
3936
|
-
error.stack = decoderError.stack; // Provide a more useful stack trace
|
|
3937
4472
|
outOfBandError = error;
|
|
3938
4473
|
onQueueNotEmpty();
|
|
3939
4474
|
}
|
|
@@ -3941,9 +4476,6 @@
|
|
|
3941
4476
|
const packetSink = this._createPacketSink();
|
|
3942
4477
|
const keyPacket = await packetSink.getKeyPacket(startTimestamp, { verifyKeyPackets: true })
|
|
3943
4478
|
?? await packetSink.getFirstPacket();
|
|
3944
|
-
if (!keyPacket) {
|
|
3945
|
-
return;
|
|
3946
|
-
}
|
|
3947
4479
|
let currentPacket = keyPacket;
|
|
3948
4480
|
let endPacket = undefined;
|
|
3949
4481
|
if (endTimestamp < Infinity) {
|
|
@@ -3961,7 +4493,7 @@
|
|
|
3961
4493
|
endPacket = keyPacket;
|
|
3962
4494
|
}
|
|
3963
4495
|
}
|
|
3964
|
-
const packets = packetSink.packets(keyPacket, endPacket);
|
|
4496
|
+
const packets = packetSink.packets(keyPacket ?? undefined, endPacket);
|
|
3965
4497
|
await packets.next(); // Skip the start packet as we already have it
|
|
3966
4498
|
while (currentPacket && !ended && !this._track.input._disposed) {
|
|
3967
4499
|
const maxQueueSize = computeMaxQueueSize(sampleQueue.length);
|
|
@@ -4064,7 +4596,6 @@
|
|
|
4064
4596
|
};
|
|
4065
4597
|
// The following is the "pump" process that keeps pumping packets into the decoder
|
|
4066
4598
|
(async () => {
|
|
4067
|
-
const decoderError = new Error();
|
|
4068
4599
|
const decoder = await this._createDecoder((sample) => {
|
|
4069
4600
|
onQueueDequeue();
|
|
4070
4601
|
if (terminated) {
|
|
@@ -4089,7 +4620,6 @@
|
|
|
4089
4620
|
}
|
|
4090
4621
|
}, (error) => {
|
|
4091
4622
|
if (!outOfBandError) {
|
|
4092
|
-
error.stack = decoderError.stack; // Provide a more useful stack trace
|
|
4093
4623
|
outOfBandError = error;
|
|
4094
4624
|
onQueueNotEmpty();
|
|
4095
4625
|
}
|
|
@@ -4302,6 +4832,7 @@
|
|
|
4302
4832
|
}
|
|
4303
4833
|
}
|
|
4304
4834
|
}
|
|
4835
|
+
const stack = new Error('Decoding error').stack;
|
|
4305
4836
|
this.decoder = new VideoDecoder({
|
|
4306
4837
|
output: (frame) => {
|
|
4307
4838
|
try {
|
|
@@ -4311,7 +4842,10 @@
|
|
|
4311
4842
|
this.onError(error);
|
|
4312
4843
|
}
|
|
4313
4844
|
},
|
|
4314
|
-
error:
|
|
4845
|
+
error: (error) => {
|
|
4846
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
4847
|
+
this.onError(error);
|
|
4848
|
+
},
|
|
4315
4849
|
});
|
|
4316
4850
|
this.decoder.configure(this.decoderConfig);
|
|
4317
4851
|
}
|
|
@@ -4332,7 +4866,6 @@
|
|
|
4332
4866
|
}
|
|
4333
4867
|
this.raslSkipped = true;
|
|
4334
4868
|
}
|
|
4335
|
-
this.currentPacketIndex++;
|
|
4336
4869
|
if (this.customDecoder) {
|
|
4337
4870
|
this.customDecoderQueueSize++;
|
|
4338
4871
|
void this.customDecoderCallSerializer
|
|
@@ -4344,9 +4877,21 @@
|
|
|
4344
4877
|
if (!isWebKit()) {
|
|
4345
4878
|
insertSorted(this.inputTimestamps, packet.timestamp, x => x);
|
|
4346
4879
|
}
|
|
4880
|
+
// Workaround for https://issues.chromium.org/issues/470109459
|
|
4881
|
+
if (isChromium() && this.currentPacketIndex === 0 && this.codec === 'avc') {
|
|
4882
|
+
const nalUnits = extractAvcNalUnits(packet.data, this.decoderConfig);
|
|
4883
|
+
const filteredNalUnits = nalUnits.filter((x) => {
|
|
4884
|
+
const type = extractNalUnitTypeForAvc(x);
|
|
4885
|
+
// These trip up Chromium's key frame detection, so let's strip them
|
|
4886
|
+
return !(type >= 20 && type <= 31);
|
|
4887
|
+
});
|
|
4888
|
+
const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
|
|
4889
|
+
packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
|
|
4890
|
+
}
|
|
4347
4891
|
this.decoder.decode(packet.toEncodedVideoChunk());
|
|
4348
4892
|
this.decodeAlphaData(packet);
|
|
4349
4893
|
}
|
|
4894
|
+
this.currentPacketIndex++;
|
|
4350
4895
|
}
|
|
4351
4896
|
decodeAlphaData(packet) {
|
|
4352
4897
|
if (!packet.sideData.alpha || this.mergerCreationFailed) {
|
|
@@ -4392,6 +4937,7 @@
|
|
|
4392
4937
|
}
|
|
4393
4938
|
}
|
|
4394
4939
|
};
|
|
4940
|
+
const stack = new Error('Decoding error').stack;
|
|
4395
4941
|
this.alphaDecoder = new VideoDecoder({
|
|
4396
4942
|
output: (frame) => {
|
|
4397
4943
|
try {
|
|
@@ -4401,7 +4947,10 @@
|
|
|
4401
4947
|
this.onError(error);
|
|
4402
4948
|
}
|
|
4403
4949
|
},
|
|
4404
|
-
error:
|
|
4950
|
+
error: (error) => {
|
|
4951
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
4952
|
+
this.onError(error);
|
|
4953
|
+
},
|
|
4405
4954
|
});
|
|
4406
4955
|
this.alphaDecoder.configure(this.decoderConfig);
|
|
4407
4956
|
}
|
|
@@ -4786,6 +5335,7 @@
|
|
|
4786
5335
|
void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
|
|
4787
5336
|
}
|
|
4788
5337
|
else {
|
|
5338
|
+
const stack = new Error('Decoding error').stack;
|
|
4789
5339
|
this.decoder = new AudioDecoder({
|
|
4790
5340
|
output: (data) => {
|
|
4791
5341
|
try {
|
|
@@ -4795,7 +5345,10 @@
|
|
|
4795
5345
|
this.onError(error);
|
|
4796
5346
|
}
|
|
4797
5347
|
},
|
|
4798
|
-
error:
|
|
5348
|
+
error: (error) => {
|
|
5349
|
+
error.stack = stack; // Provide a more useful stack trace, the default one sucks
|
|
5350
|
+
this.onError(error);
|
|
5351
|
+
},
|
|
4799
5352
|
});
|
|
4800
5353
|
this.decoder.configure(decoderConfig);
|
|
4801
5354
|
}
|
|
@@ -7496,11 +8049,8 @@
|
|
|
7496
8049
|
async getKeyPacket(timestamp, options) {
|
|
7497
8050
|
const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
|
|
7498
8051
|
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
|
7499
|
-
const sampleIndex =
|
|
7500
|
-
const
|
|
7501
|
-
? -1
|
|
7502
|
-
: getRelevantKeyframeIndexForSample(sampleTable, sampleIndex);
|
|
7503
|
-
const regularPacket = await this.fetchPacketForSampleIndex(keyFrameSampleIndex, options);
|
|
8052
|
+
const sampleIndex = getKeyframeSampleIndexForTimestamp(sampleTable, timestampInTimescale);
|
|
8053
|
+
const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
|
|
7504
8054
|
if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
|
|
7505
8055
|
// Prefer the non-fragmented packet
|
|
7506
8056
|
return regularPacket;
|
|
@@ -7807,6 +8357,32 @@
|
|
|
7807
8357
|
+ Math.min(Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta), entry.count - 1);
|
|
7808
8358
|
}
|
|
7809
8359
|
};
|
|
8360
|
+
const getKeyframeSampleIndexForTimestamp = (sampleTable, timescaleUnits) => {
|
|
8361
|
+
if (!sampleTable.keySampleIndices) {
|
|
8362
|
+
// Every sample is a keyframe
|
|
8363
|
+
return getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
|
8364
|
+
}
|
|
8365
|
+
if (sampleTable.presentationTimestamps) {
|
|
8366
|
+
const index = binarySearchLessOrEqual(sampleTable.presentationTimestamps, timescaleUnits, x => x.presentationTimestamp);
|
|
8367
|
+
if (index === -1) {
|
|
8368
|
+
return -1;
|
|
8369
|
+
}
|
|
8370
|
+
// Walk the samples in presentation order until we find one that's a keyframe
|
|
8371
|
+
for (let i = index; i >= 0; i--) {
|
|
8372
|
+
const sampleIndex = sampleTable.presentationTimestamps[i].sampleIndex;
|
|
8373
|
+
const isKeyFrame = binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1;
|
|
8374
|
+
if (isKeyFrame) {
|
|
8375
|
+
return sampleIndex;
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
return -1;
|
|
8379
|
+
}
|
|
8380
|
+
else {
|
|
8381
|
+
const sampleIndex = getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
|
8382
|
+
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
|
8383
|
+
return sampleTable.keySampleIndices[index] ?? -1;
|
|
8384
|
+
}
|
|
8385
|
+
};
|
|
7810
8386
|
const getSampleInfo = (sampleTable, sampleIndex) => {
|
|
7811
8387
|
const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, x => x.startIndex);
|
|
7812
8388
|
const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
|
|
@@ -7869,13 +8445,6 @@
|
|
|
7869
8445
|
: true,
|
|
7870
8446
|
};
|
|
7871
8447
|
};
|
|
7872
|
-
const getRelevantKeyframeIndexForSample = (sampleTable, sampleIndex) => {
|
|
7873
|
-
if (!sampleTable.keySampleIndices) {
|
|
7874
|
-
return sampleIndex;
|
|
7875
|
-
}
|
|
7876
|
-
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
|
7877
|
-
return sampleTable.keySampleIndices[index] ?? -1;
|
|
7878
|
-
};
|
|
7879
8448
|
const getNextKeyframeIndexForSample = (sampleTable, sampleIndex) => {
|
|
7880
8449
|
if (!sampleTable.keySampleIndices) {
|
|
7881
8450
|
return sampleIndex + 1;
|
|
@@ -13984,6 +14553,8 @@
|
|
|
13984
14553
|
// If user is offline, it is probably not a CORS error.
|
|
13985
14554
|
const isOnline = typeof navigator !== 'undefined' && typeof navigator.onLine === 'boolean' ? navigator.onLine : true;
|
|
13986
14555
|
if (isOnline && originOfSrc !== null && originOfSrc !== window.location.origin) {
|
|
14556
|
+
console.warn(`Request will not be retried because a CORS error was suspected due to different origins. You can`
|
|
14557
|
+
+ ` modify this behavior by providing your own function for the 'getRetryDelay' option.`);
|
|
13987
14558
|
return null;
|
|
13988
14559
|
}
|
|
13989
14560
|
}
|
|
@@ -14049,7 +14620,7 @@
|
|
|
14049
14620
|
Range: 'bytes=0-',
|
|
14050
14621
|
},
|
|
14051
14622
|
signal: abortController.signal,
|
|
14052
|
-
}), this._getRetryDelay);
|
|
14623
|
+
}), this._getRetryDelay, () => this._disposed);
|
|
14053
14624
|
if (!response.ok) {
|
|
14054
14625
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
14055
14626
|
throw new Error(`Error fetching ${String(this._url)}: ${response.status} ${response.statusText}`);
|
|
@@ -14057,7 +14628,7 @@
|
|
|
14057
14628
|
let worker;
|
|
14058
14629
|
let fileSize;
|
|
14059
14630
|
if (response.status === 206) {
|
|
14060
|
-
fileSize = this.
|
|
14631
|
+
fileSize = this._getTotalLengthFromRangeResponse(response);
|
|
14061
14632
|
worker = this._orchestrator.createWorker(0, Math.min(fileSize, URL_SOURCE_MIN_LOAD_AMOUNT));
|
|
14062
14633
|
}
|
|
14063
14634
|
else {
|
|
@@ -14099,7 +14670,7 @@
|
|
|
14099
14670
|
Range: `bytes=${worker.currentPos}-`,
|
|
14100
14671
|
},
|
|
14101
14672
|
signal: abortController.signal,
|
|
14102
|
-
}), this._getRetryDelay);
|
|
14673
|
+
}), this._getRetryDelay, () => this._disposed);
|
|
14103
14674
|
}
|
|
14104
14675
|
assert(response);
|
|
14105
14676
|
if (!response.ok) {
|
|
@@ -14110,11 +14681,6 @@
|
|
|
14110
14681
|
throw new Error('HTTP server did not respond with 206 Partial Content to a range request. To enable efficient media'
|
|
14111
14682
|
+ ' file streaming across a network, please make sure your server supports range requests.');
|
|
14112
14683
|
}
|
|
14113
|
-
const length = this._getPartialLengthFromRangeResponse(response);
|
|
14114
|
-
const required = worker.targetPos - worker.currentPos;
|
|
14115
|
-
if (length < required) {
|
|
14116
|
-
throw new Error(`HTTP response unexpectedly too short: Needed at least ${required} bytes, got only ${length}.`);
|
|
14117
|
-
}
|
|
14118
14684
|
if (!response.body) {
|
|
14119
14685
|
throw new Error('Missing HTTP response body stream. The used fetch function must provide the response body as a'
|
|
14120
14686
|
+ ' ReadableStream.');
|
|
@@ -14131,6 +14697,10 @@
|
|
|
14131
14697
|
readResult = await reader.read();
|
|
14132
14698
|
}
|
|
14133
14699
|
catch (error) {
|
|
14700
|
+
if (this._disposed) {
|
|
14701
|
+
// No need to try to retry
|
|
14702
|
+
throw error;
|
|
14703
|
+
}
|
|
14134
14704
|
const retryDelayInSeconds = this._getRetryDelay(1, error, this._url);
|
|
14135
14705
|
if (retryDelayInSeconds !== null) {
|
|
14136
14706
|
console.error('Error while reading response stream. Attempting to resume.', error);
|
|
@@ -14142,50 +14712,45 @@
|
|
|
14142
14712
|
}
|
|
14143
14713
|
}
|
|
14144
14714
|
if (worker.aborted) {
|
|
14145
|
-
|
|
14715
|
+
continue; // Cleanup happens in next iteration
|
|
14146
14716
|
}
|
|
14147
14717
|
const { done, value } = readResult;
|
|
14148
14718
|
if (done) {
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14719
|
+
if (worker.currentPos >= worker.targetPos) {
|
|
14720
|
+
// All data was delivered, we're good
|
|
14721
|
+
this._orchestrator.forgetWorker(worker);
|
|
14722
|
+
worker.running = false;
|
|
14723
|
+
return;
|
|
14724
|
+
}
|
|
14725
|
+
// The response stopped early, before the target. This can happen if server decides to cap range
|
|
14726
|
+
// requests arbitrarily, even if the request had an uncapped end. In this case, let's fetch the rest
|
|
14727
|
+
// of the data using a new request.
|
|
14728
|
+
break;
|
|
14155
14729
|
}
|
|
14156
14730
|
this.onread?.(worker.currentPos, worker.currentPos + value.length);
|
|
14157
14731
|
this._orchestrator.supplyWorkerData(worker, value);
|
|
14158
14732
|
}
|
|
14159
|
-
if (worker.aborted) {
|
|
14160
|
-
break;
|
|
14161
|
-
}
|
|
14162
14733
|
}
|
|
14163
|
-
worker.running = false;
|
|
14164
14734
|
// The previous UrlSource had logic for circumventing https://issues.chromium.org/issues/436025873; I haven't
|
|
14165
14735
|
// been able to observe this bug with the new UrlSource (maybe because we're using response streaming), so the
|
|
14166
14736
|
// logic for that has vanished for now. Leaving a comment here if this becomes relevant again.
|
|
14167
14737
|
}
|
|
14168
14738
|
/** @internal */
|
|
14169
|
-
|
|
14739
|
+
_getTotalLengthFromRangeResponse(response) {
|
|
14170
14740
|
const contentRange = response.headers.get('Content-Range');
|
|
14171
14741
|
if (contentRange) {
|
|
14172
14742
|
const match = /\/(\d+)/.exec(contentRange);
|
|
14173
14743
|
if (match) {
|
|
14174
14744
|
return Number(match[1]);
|
|
14175
14745
|
}
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14746
|
+
}
|
|
14747
|
+
const contentLength = response.headers.get('Content-Length');
|
|
14748
|
+
if (contentLength) {
|
|
14749
|
+
return Number(contentLength);
|
|
14179
14750
|
}
|
|
14180
14751
|
else {
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
return Number(contentLength);
|
|
14184
|
-
}
|
|
14185
|
-
else {
|
|
14186
|
-
throw new Error('Partial HTTP response (status 206) must surface either Content-Range or'
|
|
14187
|
-
+ ' Content-Length header.');
|
|
14188
|
-
}
|
|
14752
|
+
throw new Error('Partial HTTP response (status 206) must surface either Content-Range or'
|
|
14753
|
+
+ ' Content-Length header.');
|
|
14189
14754
|
}
|
|
14190
14755
|
}
|
|
14191
14756
|
/** @internal */
|
|
@@ -15188,7 +15753,6 @@
|
|
|
15188
15753
|
if (cache) {
|
|
15189
15754
|
return [2 /*return*/, { status: 206, arrayBuffer: cache.arrayBuffer }];
|
|
15190
15755
|
}
|
|
15191
|
-
console.log('miss', id);
|
|
15192
15756
|
return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { cache: 'force-cache', headers: {
|
|
15193
15757
|
'Cache-Control': 'max-age=31536000',
|
|
15194
15758
|
Range: "bytes=".concat(start, "-").concat(end),
|
|
@@ -15216,6 +15780,7 @@
|
|
|
15216
15780
|
return __generator(this, function (_a) {
|
|
15217
15781
|
switch (_a.label) {
|
|
15218
15782
|
case 0:
|
|
15783
|
+
if (!!(options === null || options === void 0 ? void 0 : options.indexedDB)) return [3 /*break*/, 4];
|
|
15219
15784
|
return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { cache: 'force-cache', headers: {
|
|
15220
15785
|
'Cache-Control': 'max-age=31536000',
|
|
15221
15786
|
Range: "bytes=".concat(start, "-").concat(end),
|
|
@@ -15262,15 +15827,13 @@
|
|
|
15262
15827
|
(function (DecoderType) {
|
|
15263
15828
|
DecoderType[DecoderType["META"] = 0] = "META";
|
|
15264
15829
|
DecoderType[DecoderType["DECODE"] = 1] = "DECODE";
|
|
15265
|
-
DecoderType[DecoderType["
|
|
15266
|
-
DecoderType[DecoderType["RELEASE"] = 3] = "RELEASE";
|
|
15830
|
+
DecoderType[DecoderType["RELEASE"] = 2] = "RELEASE";
|
|
15267
15831
|
})(exports.DecoderType || (exports.DecoderType = {}));
|
|
15268
15832
|
exports.DecoderEvent = void 0;
|
|
15269
15833
|
(function (DecoderEvent) {
|
|
15270
15834
|
DecoderEvent["META"] = "meta";
|
|
15271
15835
|
DecoderEvent["ERROR"] = "error";
|
|
15272
15836
|
DecoderEvent["DECODED"] = "decoded";
|
|
15273
|
-
DecoderEvent["DECODED_FRAME"] = "decoded_frame";
|
|
15274
15837
|
})(exports.DecoderEvent || (exports.DecoderEvent = {}));
|
|
15275
15838
|
exports.GOPState = void 0;
|
|
15276
15839
|
(function (GOPState) {
|
|
@@ -15289,10 +15852,10 @@
|
|
|
15289
15852
|
}
|
|
15290
15853
|
var FILE_HASH = {};
|
|
15291
15854
|
var onMessage = function (e) { return __awaiter(void 0, void 0, void 0, function () {
|
|
15292
|
-
var _a, url, id, type, isWorker, onError, fileData, headResponse, cl, fileSize_1, meta, source, input, data, e_1, videoTrack, duration, sink, _b, _c, _d, packet, len_1, last, e_2_1, len, last, timestamp, audioTrack, duration, timestamp, gopMinDuration, sink, timestamp_1, isFirst, sequenceNumber, _e, _f, _g, packet, diff, len_2, last, e_3_1, len, last, simpleGOPList, res, gop, videoFrames, sink, _h, _j, _k, sample, e_4_1, audioChunks, sampleRate, audioTimeStamp, audioDuration, sink, start, end, samples, _l, samples_1, samples_1_1, sample, numberOfChannels, numberOfFrames, timestamp, duration, channels, ch, tmp, e_5_1, transferList_1, res, gop,
|
|
15293
|
-
var
|
|
15294
|
-
return __generator(this, function (
|
|
15295
|
-
switch (
|
|
15855
|
+
var _a, url, id, type, isWorker, onError, fileData, headResponse, cl, fileSize_1, meta, source, input, data, e_1, videoTrack, duration, sink, _b, _c, _d, packet, len_1, last, e_2_1, len, last, timestamp, audioTrack, duration, timestamp, gopMinDuration, sink, timestamp_1, isFirst, sequenceNumber, _e, _f, _g, packet, diff, len_2, last, e_3_1, len, last, simpleGOPList, res, gop, videoFrames, sink, _h, _j, _k, sample, e_4_1, audioChunks, sampleRate, audioTimeStamp, audioDuration, sink, start, end, samples, _l, samples_1, samples_1_1, sample, numberOfChannels, numberOfFrames, timestamp, duration, channels, ch, tmp, e_5_1, transferList_1, res, gop, i;
|
|
15856
|
+
var _m, e_2, _o, _p, _q, e_3, _r, _s, _t, e_4, _u, _v, _w, e_5, _x, _y;
|
|
15857
|
+
return __generator(this, function (_z) {
|
|
15858
|
+
switch (_z.label) {
|
|
15296
15859
|
case 0:
|
|
15297
15860
|
_a = e.data, url = _a.url, id = _a.id, type = _a.type, isWorker = _a.isWorker;
|
|
15298
15861
|
onError = function (e) {
|
|
@@ -15315,7 +15878,7 @@
|
|
|
15315
15878
|
if (!(type === exports.DecoderType.META)) return [3 /*break*/, 38];
|
|
15316
15879
|
return [4 /*yield*/, fetch(url, { method: 'HEAD' })];
|
|
15317
15880
|
case 1:
|
|
15318
|
-
headResponse =
|
|
15881
|
+
headResponse = _z.sent();
|
|
15319
15882
|
cl = headResponse.headers.get('content-length');
|
|
15320
15883
|
if (!cl || headResponse.status !== 200 && headResponse.status !== 304) {
|
|
15321
15884
|
return [2 /*return*/, onError('Unknown content-length')];
|
|
@@ -15336,7 +15899,7 @@
|
|
|
15336
15899
|
var arrayBuffer;
|
|
15337
15900
|
return __generator(this, function (_a) {
|
|
15338
15901
|
switch (_a.label) {
|
|
15339
|
-
case 0: return [4 /*yield*/, loadRange(url, start, end - 1, fileSize_1)];
|
|
15902
|
+
case 0: return [4 /*yield*/, loadRange(url, start, end - 1, fileSize_1, { indexedDB: e.data.indexedDB })];
|
|
15340
15903
|
case 1:
|
|
15341
15904
|
arrayBuffer = (_a.sent()).arrayBuffer;
|
|
15342
15905
|
if (!arrayBuffer) {
|
|
@@ -15358,37 +15921,37 @@
|
|
|
15358
15921
|
formats: ALL_FORMATS,
|
|
15359
15922
|
source: source,
|
|
15360
15923
|
});
|
|
15361
|
-
|
|
15924
|
+
_z.label = 2;
|
|
15362
15925
|
case 2:
|
|
15363
|
-
|
|
15926
|
+
_z.trys.push([2, 4, , 5]);
|
|
15364
15927
|
return [4 /*yield*/, input.computeDuration()];
|
|
15365
15928
|
case 3:
|
|
15366
|
-
data =
|
|
15929
|
+
data = _z.sent();
|
|
15367
15930
|
meta.duration = data * 1e3;
|
|
15368
15931
|
return [3 /*break*/, 5];
|
|
15369
15932
|
case 4:
|
|
15370
|
-
e_1 =
|
|
15933
|
+
e_1 = _z.sent();
|
|
15371
15934
|
return [2 /*return*/, onError(e_1.message)];
|
|
15372
15935
|
case 5: return [4 /*yield*/, input.getPrimaryVideoTrack()];
|
|
15373
15936
|
case 6:
|
|
15374
|
-
videoTrack =
|
|
15937
|
+
videoTrack = _z.sent();
|
|
15375
15938
|
if (!videoTrack) return [3 /*break*/, 21];
|
|
15376
15939
|
fileData.videoTrack = videoTrack;
|
|
15377
15940
|
return [4 /*yield*/, videoTrack.computeDuration()];
|
|
15378
15941
|
case 7:
|
|
15379
|
-
duration =
|
|
15942
|
+
duration = _z.sent();
|
|
15380
15943
|
sink = new EncodedPacketSink(videoTrack);
|
|
15381
|
-
|
|
15944
|
+
_z.label = 8;
|
|
15382
15945
|
case 8:
|
|
15383
|
-
|
|
15946
|
+
_z.trys.push([8, 13, 14, 19]);
|
|
15384
15947
|
_b = true, _c = __asyncValues(sink.packets(undefined, undefined, { metadataOnly: true }));
|
|
15385
|
-
|
|
15948
|
+
_z.label = 9;
|
|
15386
15949
|
case 9: return [4 /*yield*/, _c.next()];
|
|
15387
15950
|
case 10:
|
|
15388
|
-
if (!(_d =
|
|
15389
|
-
|
|
15951
|
+
if (!(_d = _z.sent(), _m = _d.done, !_m)) return [3 /*break*/, 12];
|
|
15952
|
+
_p = _d.value;
|
|
15390
15953
|
_b = false;
|
|
15391
|
-
packet =
|
|
15954
|
+
packet = _p;
|
|
15392
15955
|
if (packet.type === 'key') {
|
|
15393
15956
|
len_1 = fileData.gopList.length;
|
|
15394
15957
|
if (len_1) {
|
|
@@ -15410,22 +15973,22 @@
|
|
|
15410
15973
|
users: [],
|
|
15411
15974
|
});
|
|
15412
15975
|
}
|
|
15413
|
-
|
|
15976
|
+
_z.label = 11;
|
|
15414
15977
|
case 11:
|
|
15415
15978
|
_b = true;
|
|
15416
15979
|
return [3 /*break*/, 9];
|
|
15417
15980
|
case 12: return [3 /*break*/, 19];
|
|
15418
15981
|
case 13:
|
|
15419
|
-
e_2_1 =
|
|
15982
|
+
e_2_1 = _z.sent();
|
|
15420
15983
|
e_2 = { error: e_2_1 };
|
|
15421
15984
|
return [3 /*break*/, 19];
|
|
15422
15985
|
case 14:
|
|
15423
|
-
|
|
15424
|
-
if (!(!_b && !
|
|
15425
|
-
return [4 /*yield*/,
|
|
15986
|
+
_z.trys.push([14, , 17, 18]);
|
|
15987
|
+
if (!(!_b && !_m && (_o = _c.return))) return [3 /*break*/, 16];
|
|
15988
|
+
return [4 /*yield*/, _o.call(_c)];
|
|
15426
15989
|
case 15:
|
|
15427
|
-
|
|
15428
|
-
|
|
15990
|
+
_z.sent();
|
|
15991
|
+
_z.label = 16;
|
|
15429
15992
|
case 16: return [3 /*break*/, 18];
|
|
15430
15993
|
case 17:
|
|
15431
15994
|
if (e_2) throw e_2.error;
|
|
@@ -15439,7 +16002,7 @@
|
|
|
15439
16002
|
}
|
|
15440
16003
|
return [4 /*yield*/, videoTrack.getFirstTimestamp()];
|
|
15441
16004
|
case 20:
|
|
15442
|
-
timestamp =
|
|
16005
|
+
timestamp = _z.sent();
|
|
15443
16006
|
meta.video = {
|
|
15444
16007
|
id: videoTrack.id,
|
|
15445
16008
|
languageCode: videoTrack.languageCode,
|
|
@@ -15456,18 +16019,18 @@
|
|
|
15456
16019
|
timestamp: timestamp * 1e3,
|
|
15457
16020
|
duration: duration * 1e3,
|
|
15458
16021
|
};
|
|
15459
|
-
|
|
16022
|
+
_z.label = 21;
|
|
15460
16023
|
case 21: return [4 /*yield*/, input.getPrimaryAudioTrack()];
|
|
15461
16024
|
case 22:
|
|
15462
|
-
audioTrack =
|
|
16025
|
+
audioTrack = _z.sent();
|
|
15463
16026
|
if (!audioTrack) return [3 /*break*/, 37];
|
|
15464
16027
|
fileData.audioTrack = audioTrack;
|
|
15465
16028
|
return [4 /*yield*/, audioTrack.computeDuration()];
|
|
15466
16029
|
case 23:
|
|
15467
|
-
duration =
|
|
16030
|
+
duration = _z.sent();
|
|
15468
16031
|
return [4 /*yield*/, audioTrack.getFirstTimestamp()];
|
|
15469
16032
|
case 24:
|
|
15470
|
-
timestamp =
|
|
16033
|
+
timestamp = _z.sent();
|
|
15471
16034
|
meta.audio = {
|
|
15472
16035
|
id: audioTrack.id,
|
|
15473
16036
|
languageCode: audioTrack.languageCode,
|
|
@@ -15484,17 +16047,17 @@
|
|
|
15484
16047
|
timestamp_1 = -1;
|
|
15485
16048
|
isFirst = true;
|
|
15486
16049
|
sequenceNumber = 0;
|
|
15487
|
-
|
|
16050
|
+
_z.label = 25;
|
|
15488
16051
|
case 25:
|
|
15489
|
-
|
|
16052
|
+
_z.trys.push([25, 30, 31, 36]);
|
|
15490
16053
|
_e = true, _f = __asyncValues(sink.packets(undefined, undefined, { metadataOnly: true }));
|
|
15491
|
-
|
|
16054
|
+
_z.label = 26;
|
|
15492
16055
|
case 26: return [4 /*yield*/, _f.next()];
|
|
15493
16056
|
case 27:
|
|
15494
|
-
if (!(_g =
|
|
15495
|
-
|
|
16057
|
+
if (!(_g = _z.sent(), _q = _g.done, !_q)) return [3 /*break*/, 29];
|
|
16058
|
+
_s = _g.value;
|
|
15496
16059
|
_e = false;
|
|
15497
|
-
packet =
|
|
16060
|
+
packet = _s;
|
|
15498
16061
|
if (timestamp_1 === -1) {
|
|
15499
16062
|
timestamp_1 = packet.timestamp;
|
|
15500
16063
|
}
|
|
@@ -15519,22 +16082,22 @@
|
|
|
15519
16082
|
timestamp_1 = packet.timestamp;
|
|
15520
16083
|
}
|
|
15521
16084
|
sequenceNumber++;
|
|
15522
|
-
|
|
16085
|
+
_z.label = 28;
|
|
15523
16086
|
case 28:
|
|
15524
16087
|
_e = true;
|
|
15525
16088
|
return [3 /*break*/, 26];
|
|
15526
16089
|
case 29: return [3 /*break*/, 36];
|
|
15527
16090
|
case 30:
|
|
15528
|
-
e_3_1 =
|
|
16091
|
+
e_3_1 = _z.sent();
|
|
15529
16092
|
e_3 = { error: e_3_1 };
|
|
15530
16093
|
return [3 /*break*/, 36];
|
|
15531
16094
|
case 31:
|
|
15532
|
-
|
|
15533
|
-
if (!(!_e && !
|
|
15534
|
-
return [4 /*yield*/,
|
|
16095
|
+
_z.trys.push([31, , 34, 35]);
|
|
16096
|
+
if (!(!_e && !_q && (_r = _f.return))) return [3 /*break*/, 33];
|
|
16097
|
+
return [4 /*yield*/, _r.call(_f)];
|
|
15535
16098
|
case 32:
|
|
15536
|
-
|
|
15537
|
-
|
|
16099
|
+
_z.sent();
|
|
16100
|
+
_z.label = 33;
|
|
15538
16101
|
case 33: return [3 /*break*/, 35];
|
|
15539
16102
|
case 34:
|
|
15540
16103
|
if (e_3) throw e_3.error;
|
|
@@ -15546,7 +16109,7 @@
|
|
|
15546
16109
|
last = fileData.gopList[len - 1];
|
|
15547
16110
|
last.duration = last.audioDuration = duration * 1e3 - last.timestamp;
|
|
15548
16111
|
}
|
|
15549
|
-
|
|
16112
|
+
_z.label = 37;
|
|
15550
16113
|
case 37:
|
|
15551
16114
|
simpleGOPList = fileData.gopList.map(function (item) {
|
|
15552
16115
|
return {
|
|
@@ -15586,7 +16149,7 @@
|
|
|
15586
16149
|
return [4 /*yield*/, sleep(100)];
|
|
15587
16150
|
case 39:
|
|
15588
16151
|
// 截流,先等待一段时间,防止如频繁拖动时间轴,再检查是否被release移除users
|
|
15589
|
-
|
|
16152
|
+
_z.sent();
|
|
15590
16153
|
if (!gop.users.includes(id)) {
|
|
15591
16154
|
return [2 /*return*/];
|
|
15592
16155
|
}
|
|
@@ -15599,35 +16162,35 @@
|
|
|
15599
16162
|
videoFrames = [];
|
|
15600
16163
|
if (!fileData.videoTrack) return [3 /*break*/, 51];
|
|
15601
16164
|
sink = new VideoSampleSink(fileData.videoTrack);
|
|
15602
|
-
|
|
16165
|
+
_z.label = 40;
|
|
15603
16166
|
case 40:
|
|
15604
|
-
|
|
16167
|
+
_z.trys.push([40, 45, 46, 51]);
|
|
15605
16168
|
_h = true, _j = __asyncValues(sink.samples(gop.timestamp * 1e-3, (gop.timestamp + gop.duration) * 1e-3));
|
|
15606
|
-
|
|
16169
|
+
_z.label = 41;
|
|
15607
16170
|
case 41: return [4 /*yield*/, _j.next()];
|
|
15608
16171
|
case 42:
|
|
15609
|
-
if (!(_k =
|
|
15610
|
-
|
|
16172
|
+
if (!(_k = _z.sent(), _t = _k.done, !_t)) return [3 /*break*/, 44];
|
|
16173
|
+
_v = _k.value;
|
|
15611
16174
|
_h = false;
|
|
15612
|
-
sample =
|
|
16175
|
+
sample = _v;
|
|
15613
16176
|
videoFrames.push(sample.toVideoFrame());
|
|
15614
16177
|
sample.close();
|
|
15615
|
-
|
|
16178
|
+
_z.label = 43;
|
|
15616
16179
|
case 43:
|
|
15617
16180
|
_h = true;
|
|
15618
16181
|
return [3 /*break*/, 41];
|
|
15619
16182
|
case 44: return [3 /*break*/, 51];
|
|
15620
16183
|
case 45:
|
|
15621
|
-
e_4_1 =
|
|
16184
|
+
e_4_1 = _z.sent();
|
|
15622
16185
|
e_4 = { error: e_4_1 };
|
|
15623
16186
|
return [3 /*break*/, 51];
|
|
15624
16187
|
case 46:
|
|
15625
|
-
|
|
15626
|
-
if (!(!_h && !
|
|
15627
|
-
return [4 /*yield*/,
|
|
16188
|
+
_z.trys.push([46, , 49, 50]);
|
|
16189
|
+
if (!(!_h && !_t && (_u = _j.return))) return [3 /*break*/, 48];
|
|
16190
|
+
return [4 /*yield*/, _u.call(_j)];
|
|
15628
16191
|
case 47:
|
|
15629
|
-
|
|
15630
|
-
|
|
16192
|
+
_z.sent();
|
|
16193
|
+
_z.label = 48;
|
|
15631
16194
|
case 48: return [3 /*break*/, 50];
|
|
15632
16195
|
case 49:
|
|
15633
16196
|
if (e_4) throw e_4.error;
|
|
@@ -15644,17 +16207,17 @@
|
|
|
15644
16207
|
start = gop.timestamp * 1e-3;
|
|
15645
16208
|
end = (gop.timestamp + gop.duration) * 1e-3;
|
|
15646
16209
|
samples = sink.samples(start, end);
|
|
15647
|
-
|
|
16210
|
+
_z.label = 52;
|
|
15648
16211
|
case 52:
|
|
15649
|
-
|
|
16212
|
+
_z.trys.push([52, 57, 58, 63]);
|
|
15650
16213
|
_l = true, samples_1 = __asyncValues(samples);
|
|
15651
|
-
|
|
16214
|
+
_z.label = 53;
|
|
15652
16215
|
case 53: return [4 /*yield*/, samples_1.next()];
|
|
15653
16216
|
case 54:
|
|
15654
|
-
if (!(samples_1_1 =
|
|
15655
|
-
|
|
16217
|
+
if (!(samples_1_1 = _z.sent(), _w = samples_1_1.done, !_w)) return [3 /*break*/, 56];
|
|
16218
|
+
_y = samples_1_1.value;
|
|
15656
16219
|
_l = false;
|
|
15657
|
-
sample =
|
|
16220
|
+
sample = _y;
|
|
15658
16221
|
sampleRate = sample.sampleRate;
|
|
15659
16222
|
numberOfChannels = sample.numberOfChannels, numberOfFrames = sample.numberOfFrames, timestamp = sample.timestamp, duration = sample.duration;
|
|
15660
16223
|
// 位于2个gop之间的sample归属上一个gop
|
|
@@ -15682,22 +16245,22 @@
|
|
|
15682
16245
|
duration: duration * 1e3,
|
|
15683
16246
|
});
|
|
15684
16247
|
sample.close();
|
|
15685
|
-
|
|
16248
|
+
_z.label = 55;
|
|
15686
16249
|
case 55:
|
|
15687
16250
|
_l = true;
|
|
15688
16251
|
return [3 /*break*/, 53];
|
|
15689
16252
|
case 56: return [3 /*break*/, 63];
|
|
15690
16253
|
case 57:
|
|
15691
|
-
e_5_1 =
|
|
16254
|
+
e_5_1 = _z.sent();
|
|
15692
16255
|
e_5 = { error: e_5_1 };
|
|
15693
16256
|
return [3 /*break*/, 63];
|
|
15694
16257
|
case 58:
|
|
15695
|
-
|
|
15696
|
-
if (!(!_l && !
|
|
15697
|
-
return [4 /*yield*/,
|
|
16258
|
+
_z.trys.push([58, , 61, 62]);
|
|
16259
|
+
if (!(!_l && !_w && (_x = samples_1.return))) return [3 /*break*/, 60];
|
|
16260
|
+
return [4 /*yield*/, _x.call(samples_1)];
|
|
15698
16261
|
case 59:
|
|
15699
|
-
|
|
15700
|
-
|
|
16262
|
+
_z.sent();
|
|
16263
|
+
_z.label = 60;
|
|
15701
16264
|
case 60: return [3 /*break*/, 62];
|
|
15702
16265
|
case 61:
|
|
15703
16266
|
if (e_5) throw e_5.error;
|
|
@@ -15737,124 +16300,6 @@
|
|
|
15737
16300
|
}
|
|
15738
16301
|
return [2 /*return*/, { data: res }];
|
|
15739
16302
|
case 64:
|
|
15740
|
-
if (!(type === exports.DecoderType.DECODE_FRAME)) return [3 /*break*/, 79];
|
|
15741
|
-
gop = fileData.gopList[e.data.index];
|
|
15742
|
-
// 理论不会,预防,只有加载成功后才会进入解码状态
|
|
15743
|
-
if (!gop || gop.state === exports.GOPState.ERROR) {
|
|
15744
|
-
return [2 /*return*/];
|
|
15745
|
-
}
|
|
15746
|
-
time = e.data.time;
|
|
15747
|
-
videoFrame = void 0;
|
|
15748
|
-
if (!fileData.videoTrack) return [3 /*break*/, 66];
|
|
15749
|
-
sink = new VideoSampleSink(fileData.videoTrack);
|
|
15750
|
-
return [4 /*yield*/, sink.getSample(time * 1e-3)];
|
|
15751
|
-
case 65:
|
|
15752
|
-
sample = _3.sent();
|
|
15753
|
-
if (sample) {
|
|
15754
|
-
videoFrame = sample.toVideoFrame();
|
|
15755
|
-
sample.close();
|
|
15756
|
-
}
|
|
15757
|
-
_3.label = 66;
|
|
15758
|
-
case 66:
|
|
15759
|
-
audioChunks = [];
|
|
15760
|
-
sampleRate = 0;
|
|
15761
|
-
audioTimeStamp = -1;
|
|
15762
|
-
audioDuration = 0;
|
|
15763
|
-
if (!(fileData.audioTrack && !e.data.mute && !gop.didAudioChunk)) return [3 /*break*/, 78];
|
|
15764
|
-
gop.didAudioChunk = true;
|
|
15765
|
-
sink = new AudioSampleSink(fileData.audioTrack);
|
|
15766
|
-
start = gop.timestamp * 1e-3;
|
|
15767
|
-
end = (gop.timestamp + gop.duration) * 1e-3;
|
|
15768
|
-
samples = sink.samples(start, end);
|
|
15769
|
-
_3.label = 67;
|
|
15770
|
-
case 67:
|
|
15771
|
-
_3.trys.push([67, 72, 73, 78]);
|
|
15772
|
-
_m = true, samples_2 = __asyncValues(samples);
|
|
15773
|
-
_3.label = 68;
|
|
15774
|
-
case 68: return [4 /*yield*/, samples_2.next()];
|
|
15775
|
-
case 69:
|
|
15776
|
-
if (!(samples_2_1 = _3.sent(), _0 = samples_2_1.done, !_0)) return [3 /*break*/, 71];
|
|
15777
|
-
_2 = samples_2_1.value;
|
|
15778
|
-
_m = false;
|
|
15779
|
-
sample = _2;
|
|
15780
|
-
sampleRate = sample.sampleRate;
|
|
15781
|
-
numberOfChannels = sample.numberOfChannels, numberOfFrames = sample.numberOfFrames, timestamp = sample.timestamp, duration = sample.duration;
|
|
15782
|
-
// 位于2个gop之间的sample归属上一个gop
|
|
15783
|
-
if (timestamp >= end && gop.index < fileData.gopList.length - 1 || timestamp < start) {
|
|
15784
|
-
return [3 /*break*/, 70];
|
|
15785
|
-
}
|
|
15786
|
-
if (audioTimeStamp === -1) {
|
|
15787
|
-
audioTimeStamp = timestamp * 1e3;
|
|
15788
|
-
}
|
|
15789
|
-
audioDuration = timestamp * 1e3 - audioTimeStamp + duration * 1e3;
|
|
15790
|
-
channels = [];
|
|
15791
|
-
for (ch = 0; ch < numberOfChannels; ch++) {
|
|
15792
|
-
tmp = new Float32Array(numberOfFrames);
|
|
15793
|
-
// audioBuffer只支持f32
|
|
15794
|
-
sample.copyTo(tmp, { planeIndex: ch, format: 'f32-planar' });
|
|
15795
|
-
channels.push(tmp);
|
|
15796
|
-
}
|
|
15797
|
-
audioChunks.push({
|
|
15798
|
-
format: 'f32-planar',
|
|
15799
|
-
channels: channels,
|
|
15800
|
-
sampleRate: sampleRate,
|
|
15801
|
-
numberOfFrames: numberOfFrames,
|
|
15802
|
-
numberOfChannels: numberOfChannels,
|
|
15803
|
-
timestamp: timestamp * 1e3,
|
|
15804
|
-
duration: duration * 1e3,
|
|
15805
|
-
});
|
|
15806
|
-
sample.close();
|
|
15807
|
-
_3.label = 70;
|
|
15808
|
-
case 70:
|
|
15809
|
-
_m = true;
|
|
15810
|
-
return [3 /*break*/, 68];
|
|
15811
|
-
case 71: return [3 /*break*/, 78];
|
|
15812
|
-
case 72:
|
|
15813
|
-
e_6_1 = _3.sent();
|
|
15814
|
-
e_6 = { error: e_6_1 };
|
|
15815
|
-
return [3 /*break*/, 78];
|
|
15816
|
-
case 73:
|
|
15817
|
-
_3.trys.push([73, , 76, 77]);
|
|
15818
|
-
if (!(!_m && !_0 && (_1 = samples_2.return))) return [3 /*break*/, 75];
|
|
15819
|
-
return [4 /*yield*/, _1.call(samples_2)];
|
|
15820
|
-
case 74:
|
|
15821
|
-
_3.sent();
|
|
15822
|
-
_3.label = 75;
|
|
15823
|
-
case 75: return [3 /*break*/, 77];
|
|
15824
|
-
case 76:
|
|
15825
|
-
if (e_6) throw e_6.error;
|
|
15826
|
-
return [7 /*endfinally*/];
|
|
15827
|
-
case 77: return [7 /*endfinally*/];
|
|
15828
|
-
case 78:
|
|
15829
|
-
transferList_2 = [];
|
|
15830
|
-
if (videoFrame) {
|
|
15831
|
-
transferList_2.push(videoFrame);
|
|
15832
|
-
}
|
|
15833
|
-
if (audioChunks) {
|
|
15834
|
-
audioChunks.forEach(function (item) {
|
|
15835
|
-
item.channels.forEach(function (item) {
|
|
15836
|
-
transferList_2.push(item.buffer);
|
|
15837
|
-
});
|
|
15838
|
-
});
|
|
15839
|
-
}
|
|
15840
|
-
res = {
|
|
15841
|
-
url: url,
|
|
15842
|
-
type: exports.DecoderEvent.DECODED_FRAME,
|
|
15843
|
-
data: {
|
|
15844
|
-
index: e.data.index,
|
|
15845
|
-
time: time,
|
|
15846
|
-
videoFrame: videoFrame,
|
|
15847
|
-
audioChunks: audioChunks,
|
|
15848
|
-
sampleRate: sampleRate,
|
|
15849
|
-
audioTimeStamp: audioTimeStamp,
|
|
15850
|
-
audioDuration: audioDuration,
|
|
15851
|
-
},
|
|
15852
|
-
};
|
|
15853
|
-
if (isWorker) {
|
|
15854
|
-
self.postMessage(res, transferList_2);
|
|
15855
|
-
}
|
|
15856
|
-
return [2 /*return*/, { data: res }];
|
|
15857
|
-
case 79:
|
|
15858
16303
|
if (type === exports.DecoderType.RELEASE) {
|
|
15859
16304
|
gop = fileData.gopList[e.data.index];
|
|
15860
16305
|
if (!gop) {
|
|
@@ -15866,10 +16311,11 @@
|
|
|
15866
16311
|
}
|
|
15867
16312
|
if (!gop.users.length) {
|
|
15868
16313
|
gop.state = exports.GOPState.NONE;
|
|
16314
|
+
gop.didAudioChunk = false;
|
|
15869
16315
|
}
|
|
15870
16316
|
}
|
|
15871
|
-
|
|
15872
|
-
case
|
|
16317
|
+
_z.label = 65;
|
|
16318
|
+
case 65: return [2 /*return*/];
|
|
15873
16319
|
}
|
|
15874
16320
|
});
|
|
15875
16321
|
}); };
|