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 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 instanceof ArrayBuffer) {
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.buffer, source.byteOffset, source.byteLength);
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 instanceof ArrayBuffer) {
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.buffer, source.byteOffset, source.byteLength);
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' && navigator.vendor?.match(/apple/i));
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' && navigator.vendor?.includes('Google Inc'));
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
- const isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === AvcNalUnitType.IDR);
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 || ArrayBuffer.isView(data)) {
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 (!('format' in init) || typeof init.format !== 'string') {
2601
- throw new TypeError('init.format must be a string.');
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 VideoColorSpace(init.colorSpace);
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 VideoColorSpace({
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
- return new VideoSample(this._data.slice(), {
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
- /** Returns the number of bytes required to hold this video sample's pixel data. */
2771
- allocationSize() {
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
- /** Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView. */
2787
- async copyTo(destination) {
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
- await this._data.copyTo(destination);
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 (destIsPlanar) {
3268
- if (destFormat === 'f32-planar') {
3269
- // Simple, since the browser must support f32-planar, we can just delegate here
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
- // Destination is interleaved.
3298
- // Allocate a temporary Float32Array to hold one channel's worth of data.
3299
- const numCh = numChannels;
3300
- const temp = new Float32Array(copyFrameCount);
3301
- for (let ch = 0; ch < numCh; ch++) {
3302
- this._data.copyTo(temp, {
3303
- planeIndex: ch,
3304
- frameOffset,
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 = new DataView(uint8Data.buffer, uint8Data.byteOffset, uint8Data.byteLength);
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 || packet.type === 'delta') {
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 || nextPacket.type === 'delta') {
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: onError,
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: this.onError,
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: onError,
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 = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
7500
- const keyFrameSampleIndex = sampleIndex === -1
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._getPartialLengthFromRangeResponse(response);
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
- break;
14715
+ continue; // Cleanup happens in next iteration
14146
14716
  }
14147
14717
  const { done, value } = readResult;
14148
14718
  if (done) {
14149
- this._orchestrator.forgetWorker(worker);
14150
- if (worker.currentPos < worker.targetPos) {
14151
- throw new Error('Response stream reader stopped unexpectedly before all requested data was read.');
14152
- }
14153
- worker.running = false;
14154
- return;
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
- _getPartialLengthFromRangeResponse(response) {
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
- else {
14177
- throw new Error(`Invalid Content-Range header: ${contentRange}`);
14178
- }
14746
+ }
14747
+ const contentLength = response.headers.get('Content-Length');
14748
+ if (contentLength) {
14749
+ return Number(contentLength);
14179
14750
  }
14180
14751
  else {
14181
- const contentLength = response.headers.get('Content-Length');
14182
- if (contentLength) {
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["DECODE_FRAME"] = 2] = "DECODE_FRAME";
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, time, videoFrame, sink, sample, audioChunks, sampleRate, audioTimeStamp, audioDuration, sink, start, end, samples, _m, samples_2, samples_2_1, sample, numberOfChannels, numberOfFrames, timestamp, duration, channels, ch, tmp, e_6_1, transferList_2, res, gop, i;
15293
- var _o, e_2, _p, _q, _r, e_3, _s, _t, _u, e_4, _v, _w, _x, e_5, _y, _z, _0, e_6, _1, _2;
15294
- return __generator(this, function (_3) {
15295
- switch (_3.label) {
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 = _3.sent();
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
- _3.label = 2;
15924
+ _z.label = 2;
15362
15925
  case 2:
15363
- _3.trys.push([2, 4, , 5]);
15926
+ _z.trys.push([2, 4, , 5]);
15364
15927
  return [4 /*yield*/, input.computeDuration()];
15365
15928
  case 3:
15366
- data = _3.sent();
15929
+ data = _z.sent();
15367
15930
  meta.duration = data * 1e3;
15368
15931
  return [3 /*break*/, 5];
15369
15932
  case 4:
15370
- e_1 = _3.sent();
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 = _3.sent();
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 = _3.sent();
15942
+ duration = _z.sent();
15380
15943
  sink = new EncodedPacketSink(videoTrack);
15381
- _3.label = 8;
15944
+ _z.label = 8;
15382
15945
  case 8:
15383
- _3.trys.push([8, 13, 14, 19]);
15946
+ _z.trys.push([8, 13, 14, 19]);
15384
15947
  _b = true, _c = __asyncValues(sink.packets(undefined, undefined, { metadataOnly: true }));
15385
- _3.label = 9;
15948
+ _z.label = 9;
15386
15949
  case 9: return [4 /*yield*/, _c.next()];
15387
15950
  case 10:
15388
- if (!(_d = _3.sent(), _o = _d.done, !_o)) return [3 /*break*/, 12];
15389
- _q = _d.value;
15951
+ if (!(_d = _z.sent(), _m = _d.done, !_m)) return [3 /*break*/, 12];
15952
+ _p = _d.value;
15390
15953
  _b = false;
15391
- packet = _q;
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
- _3.label = 11;
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 = _3.sent();
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
- _3.trys.push([14, , 17, 18]);
15424
- if (!(!_b && !_o && (_p = _c.return))) return [3 /*break*/, 16];
15425
- return [4 /*yield*/, _p.call(_c)];
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
- _3.sent();
15428
- _3.label = 16;
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 = _3.sent();
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
- _3.label = 21;
16022
+ _z.label = 21;
15460
16023
  case 21: return [4 /*yield*/, input.getPrimaryAudioTrack()];
15461
16024
  case 22:
15462
- audioTrack = _3.sent();
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 = _3.sent();
16030
+ duration = _z.sent();
15468
16031
  return [4 /*yield*/, audioTrack.getFirstTimestamp()];
15469
16032
  case 24:
15470
- timestamp = _3.sent();
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
- _3.label = 25;
16050
+ _z.label = 25;
15488
16051
  case 25:
15489
- _3.trys.push([25, 30, 31, 36]);
16052
+ _z.trys.push([25, 30, 31, 36]);
15490
16053
  _e = true, _f = __asyncValues(sink.packets(undefined, undefined, { metadataOnly: true }));
15491
- _3.label = 26;
16054
+ _z.label = 26;
15492
16055
  case 26: return [4 /*yield*/, _f.next()];
15493
16056
  case 27:
15494
- if (!(_g = _3.sent(), _r = _g.done, !_r)) return [3 /*break*/, 29];
15495
- _t = _g.value;
16057
+ if (!(_g = _z.sent(), _q = _g.done, !_q)) return [3 /*break*/, 29];
16058
+ _s = _g.value;
15496
16059
  _e = false;
15497
- packet = _t;
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
- _3.label = 28;
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 = _3.sent();
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
- _3.trys.push([31, , 34, 35]);
15533
- if (!(!_e && !_r && (_s = _f.return))) return [3 /*break*/, 33];
15534
- return [4 /*yield*/, _s.call(_f)];
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
- _3.sent();
15537
- _3.label = 33;
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
- _3.label = 37;
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
- _3.sent();
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
- _3.label = 40;
16165
+ _z.label = 40;
15603
16166
  case 40:
15604
- _3.trys.push([40, 45, 46, 51]);
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
- _3.label = 41;
16169
+ _z.label = 41;
15607
16170
  case 41: return [4 /*yield*/, _j.next()];
15608
16171
  case 42:
15609
- if (!(_k = _3.sent(), _u = _k.done, !_u)) return [3 /*break*/, 44];
15610
- _w = _k.value;
16172
+ if (!(_k = _z.sent(), _t = _k.done, !_t)) return [3 /*break*/, 44];
16173
+ _v = _k.value;
15611
16174
  _h = false;
15612
- sample = _w;
16175
+ sample = _v;
15613
16176
  videoFrames.push(sample.toVideoFrame());
15614
16177
  sample.close();
15615
- _3.label = 43;
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 = _3.sent();
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
- _3.trys.push([46, , 49, 50]);
15626
- if (!(!_h && !_u && (_v = _j.return))) return [3 /*break*/, 48];
15627
- return [4 /*yield*/, _v.call(_j)];
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
- _3.sent();
15630
- _3.label = 48;
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
- _3.label = 52;
16210
+ _z.label = 52;
15648
16211
  case 52:
15649
- _3.trys.push([52, 57, 58, 63]);
16212
+ _z.trys.push([52, 57, 58, 63]);
15650
16213
  _l = true, samples_1 = __asyncValues(samples);
15651
- _3.label = 53;
16214
+ _z.label = 53;
15652
16215
  case 53: return [4 /*yield*/, samples_1.next()];
15653
16216
  case 54:
15654
- if (!(samples_1_1 = _3.sent(), _x = samples_1_1.done, !_x)) return [3 /*break*/, 56];
15655
- _z = samples_1_1.value;
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 = _z;
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
- _3.label = 55;
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 = _3.sent();
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
- _3.trys.push([58, , 61, 62]);
15696
- if (!(!_l && !_x && (_y = samples_1.return))) return [3 /*break*/, 60];
15697
- return [4 /*yield*/, _y.call(samples_1)];
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
- _3.sent();
15700
- _3.label = 60;
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
- _3.label = 80;
15872
- case 80: return [2 /*return*/];
16317
+ _z.label = 65;
16318
+ case 65: return [2 /*return*/];
15873
16319
  }
15874
16320
  });
15875
16321
  }); };