mediabunny 1.49.0 → 1.50.1

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.
Files changed (58) hide show
  1. package/dist/bundles/mediabunny.cjs +359 -592
  2. package/dist/bundles/mediabunny.min.cjs +13 -99
  3. package/dist/bundles/mediabunny.min.mjs +13 -99
  4. package/dist/bundles/mediabunny.mjs +359 -592
  5. package/dist/bundles/mediabunny.node.cjs +359 -592
  6. package/dist/mediabunny.d.ts +12 -3
  7. package/dist/modules/src/codec-data.d.ts.map +1 -1
  8. package/dist/modules/src/codec-data.js +5 -0
  9. package/dist/modules/src/codec.d.ts +5 -2
  10. package/dist/modules/src/codec.d.ts.map +1 -1
  11. package/dist/modules/src/codec.js +57 -6
  12. package/dist/modules/src/conversion.d.ts.map +1 -1
  13. package/dist/modules/src/conversion.js +26 -16
  14. package/dist/modules/src/custom-coder.d.ts +8 -0
  15. package/dist/modules/src/custom-coder.d.ts.map +1 -1
  16. package/dist/modules/src/decode.js +1 -1
  17. package/dist/modules/src/encode.d.ts +3 -2
  18. package/dist/modules/src/encode.d.ts.map +1 -1
  19. package/dist/modules/src/encode.js +6 -5
  20. package/dist/modules/src/isobmff/isobmff-boxes.js +3 -1
  21. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +2 -1
  22. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  23. package/dist/modules/src/isobmff/isobmff-demuxer.js +8 -2
  24. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  25. package/dist/modules/src/matroska/ebml.js +1 -0
  26. package/dist/modules/src/matroska/matroska-demuxer.d.ts +2 -1
  27. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  28. package/dist/modules/src/matroska/matroska-demuxer.js +19 -3
  29. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  30. package/dist/modules/src/matroska/matroska-muxer.js +14 -23
  31. package/dist/modules/src/media-sink.d.ts +4 -17
  32. package/dist/modules/src/media-sink.d.ts.map +1 -1
  33. package/dist/modules/src/media-sink.js +149 -246
  34. package/dist/modules/src/media-source.d.ts +2 -21
  35. package/dist/modules/src/media-source.d.ts.map +1 -1
  36. package/dist/modules/src/media-source.js +69 -288
  37. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  38. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +1 -0
  39. package/dist/modules/src/sample.d.ts +1 -0
  40. package/dist/modules/src/sample.d.ts.map +1 -1
  41. package/dist/modules/src/sample.js +17 -8
  42. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +2 -2
  44. package/src/codec-data.ts +4 -0
  45. package/src/codec.ts +76 -5
  46. package/src/conversion.ts +33 -17
  47. package/src/custom-coder.ts +8 -0
  48. package/src/decode.ts +1 -1
  49. package/src/encode.ts +10 -8
  50. package/src/isobmff/isobmff-boxes.ts +7 -2
  51. package/src/isobmff/isobmff-demuxer.ts +11 -1
  52. package/src/matroska/ebml.ts +1 -0
  53. package/src/matroska/matroska-demuxer.ts +24 -1
  54. package/src/matroska/matroska-muxer.ts +18 -27
  55. package/src/media-sink.ts +171 -307
  56. package/src/media-source.ts +83 -351
  57. package/src/mpeg-ts/mpeg-ts-demuxer.ts +1 -0
  58. package/src/sample.ts +21 -10
@@ -1498,7 +1498,8 @@ var Mediabunny = (() => {
1498
1498
  "hevc",
1499
1499
  "vp9",
1500
1500
  "av1",
1501
- "vp8"
1501
+ "vp8",
1502
+ "prores"
1502
1503
  ];
1503
1504
  var PCM_AUDIO_CODECS = [
1504
1505
  "pcm-s16",
@@ -1699,7 +1700,35 @@ var Mediabunny = (() => {
1699
1700
  ];
1700
1701
  var VP9_DEFAULT_SUFFIX = ".01.01.01.01.00";
1701
1702
  var AV1_DEFAULT_SUFFIX = ".0.110.01.01.01.0";
1702
- var buildVideoCodecString = (codec, width, height, bitrate) => {
1703
+ var PRORES_FOURCCS = [
1704
+ "ap4x",
1705
+ // ProRes 4444 XQ
1706
+ "ap4h",
1707
+ // ProRes 4444
1708
+ "apch",
1709
+ // ProRes 422 High Quality
1710
+ "apcn",
1711
+ // ProRes 422 Standard Definition
1712
+ "apcs",
1713
+ // ProRes 422 LT
1714
+ "apco"
1715
+ // ProRes 422 Proxy
1716
+ ];
1717
+ var PRORES_PROFILE_TARGET_BITRATES = [
1718
+ { fourCc: "apco", bitrate: 45e6, alpha: false },
1719
+ // 422 Proxy
1720
+ { fourCc: "apcs", bitrate: 102e6, alpha: false },
1721
+ // 422 LT
1722
+ { fourCc: "apcn", bitrate: 147e6, alpha: false },
1723
+ // 422 Standard
1724
+ { fourCc: "apch", bitrate: 22e7, alpha: false },
1725
+ // 422 HQ
1726
+ { fourCc: "ap4h", bitrate: 33e7, alpha: true },
1727
+ // 4444
1728
+ { fourCc: "ap4x", bitrate: 5e8, alpha: true }
1729
+ // 4444 XQ
1730
+ ];
1731
+ var buildVideoCodecString = (codec, width, height, bitrate, alpha) => {
1703
1732
  if (codec === "avc") {
1704
1733
  const profileIndication = 100;
1705
1734
  const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
@@ -1740,8 +1769,24 @@ var Mediabunny = (() => {
1740
1769
  const level = levelInfo.level.toString().padStart(2, "0");
1741
1770
  const bitDepth = "08";
1742
1771
  return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
1772
+ } else if (codec === "prores") {
1773
+ const referencePixels = 1920 * 1080;
1774
+ const scaleFactor = Math.pow(width * height / referencePixels, 0.95);
1775
+ const candidates = PRORES_PROFILE_TARGET_BITRATES.filter((x) => x.alpha === alpha);
1776
+ let bestFourCc = candidates[0].fourCc;
1777
+ let smallestDifference = Infinity;
1778
+ for (const { fourCc, bitrate: targetBitrate } of candidates) {
1779
+ const difference = Math.abs(targetBitrate * scaleFactor - bitrate);
1780
+ if (difference < smallestDifference) {
1781
+ smallestDifference = difference;
1782
+ bestFourCc = fourCc;
1783
+ }
1784
+ }
1785
+ return bestFourCc;
1786
+ } else {
1787
+ assertNever(codec);
1743
1788
  }
1744
- throw new TypeError(`Unhandled codec '${codec}'.`);
1789
+ throw new TypeError(`Unhandled codec '${String(codec)}'.`);
1745
1790
  };
1746
1791
  var generateVp9CodecConfigurationFromCodecString = (codecString) => {
1747
1792
  const parts = codecString.split(".");
@@ -1787,7 +1832,16 @@ var Mediabunny = (() => {
1787
1832
  return [firstByte, secondByte, thirdByte, fourthByte];
1788
1833
  };
1789
1834
  var extractVideoCodecString = (trackInfo) => {
1790
- const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
1835
+ const {
1836
+ codec,
1837
+ codecDescription,
1838
+ colorSpace,
1839
+ avcCodecInfo,
1840
+ hevcCodecInfo,
1841
+ vp9CodecInfo,
1842
+ av1CodecInfo,
1843
+ proresFormat
1844
+ } = trackInfo;
1791
1845
  if (codec === "avc") {
1792
1846
  assert(trackInfo.avcType !== null);
1793
1847
  if (avcCodecInfo) {
@@ -1907,6 +1961,10 @@ var Mediabunny = (() => {
1907
1961
  string = string.slice(0, -AV1_DEFAULT_SUFFIX.length);
1908
1962
  }
1909
1963
  return string;
1964
+ } else if (codec === "prores") {
1965
+ return proresFormat ?? "apch";
1966
+ } else if (codec !== null) {
1967
+ assertNever(codec);
1910
1968
  }
1911
1969
  throw new TypeError(`Unhandled codec '${codec}'.`);
1912
1970
  };
@@ -2036,6 +2094,8 @@ var Mediabunny = (() => {
2036
2094
  return "vp9";
2037
2095
  } else if (codecString.startsWith("av01")) {
2038
2096
  return "av1";
2097
+ } else if (PRORES_FOURCCS.includes(codecString)) {
2098
+ return "prores";
2039
2099
  }
2040
2100
  if (codecString === "mp3" || codecString === "mp4a.69" || codecString === "mp4a.6B" || codecString === "mp4a.6b" || codecString === "mp4a.40.34") {
2041
2101
  return "mp3";
@@ -2098,7 +2158,7 @@ var Mediabunny = (() => {
2098
2158
  }
2099
2159
  return {};
2100
2160
  };
2101
- var VALID_VIDEO_CODEC_STRING_PREFIXES = ["avc1", "avc3", "hev1", "hvc1", "vp8", "vp09", "av01"];
2161
+ var VALID_VIDEO_CODEC_STRING_PREFIXES = ["avc1", "avc3", "hev1", "hvc1", "vp8", "vp09", "av01", ...PRORES_FOURCCS];
2102
2162
  var AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
2103
2163
  var HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
2104
2164
  var VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
@@ -2215,6 +2275,12 @@ var Mediabunny = (() => {
2215
2275
  'Video chunk metadata decoder configuration codec string for AV1 must be a valid AV1 codec string as specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.'
2216
2276
  );
2217
2277
  }
2278
+ } else if (PRORES_FOURCCS.some((x) => metadata.decoderConfig.codec.startsWith(x))) {
2279
+ if (!PRORES_FOURCCS.some((x) => metadata.decoderConfig.codec === x)) {
2280
+ throw new TypeError(
2281
+ `Video chunk metadata decoder configuration codec string for ProRes must be one of the valid ProRes four-character codes: ${PRORES_FOURCCS.join(", ")}.`
2282
+ );
2283
+ }
2218
2284
  }
2219
2285
  };
2220
2286
  var VALID_AUDIO_CODEC_STRING_PREFIXES = [
@@ -4338,6 +4404,11 @@ var Mediabunny = (() => {
4338
4404
  return null;
4339
4405
  }
4340
4406
  ;
4407
+ case "prores":
4408
+ {
4409
+ return "key";
4410
+ }
4411
+ ;
4341
4412
  default:
4342
4413
  {
4343
4414
  assertNever(codec);
@@ -6009,7 +6080,8 @@ var Mediabunny = (() => {
6009
6080
  avcCodecInfo: null,
6010
6081
  hevcCodecInfo: null,
6011
6082
  vp9CodecInfo: null,
6012
- av1CodecInfo: null
6083
+ av1CodecInfo: null,
6084
+ proresFormat: null
6013
6085
  };
6014
6086
  } else if (handlerType === "soun") {
6015
6087
  track.info = {
@@ -6084,6 +6156,9 @@ var Mediabunny = (() => {
6084
6156
  track.info.codec = "vp9";
6085
6157
  } else if (codecName === "av01") {
6086
6158
  track.info.codec = "av1";
6159
+ } else if (PRORES_FOURCCS.includes(lowercaseBoxName)) {
6160
+ track.info.codec = "prores";
6161
+ track.info.proresFormat = lowercaseBoxName;
6087
6162
  } else if (codecName === null) {
6088
6163
  Logging._warn(`Unknown encrypted video codec due to missing frma box.`);
6089
6164
  } else {
@@ -8046,7 +8121,7 @@ var Mediabunny = (() => {
8046
8121
  };
8047
8122
  }
8048
8123
  async canBeTransparent() {
8049
- return false;
8124
+ return this.internalTrack.info.codec === "prores" && (this.internalTrack.info.proresFormat === "ap4h" || this.internalTrack.info.proresFormat === "ap4x");
8050
8125
  }
8051
8126
  async getDecoderConfig() {
8052
8127
  if (!this.internalTrack.info.codec) {
@@ -8941,6 +9016,7 @@ var Mediabunny = (() => {
8941
9016
  "vp8": "V_VP8",
8942
9017
  "vp9": "V_VP9",
8943
9018
  "av1": "V_AV1",
9019
+ "prores": "V_PRORES",
8944
9020
  "aac": "A_AAC",
8945
9021
  "mp3": "A_MPEG/L3",
8946
9022
  "opus": "A_OPUS",
@@ -9604,6 +9680,13 @@ var Mediabunny = (() => {
9604
9680
  this.currentTrack.info.codec = "vp9";
9605
9681
  } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.av1) {
9606
9682
  this.currentTrack.info.codec = "av1";
9683
+ } else if (codecIdWithoutSuffix === CODEC_STRING_MAP.prores) {
9684
+ const format = this.currentTrack.codecPrivate ? textDecoder.decode(this.currentTrack.codecPrivate) : "";
9685
+ if (PRORES_FOURCCS.includes(format)) {
9686
+ this.currentTrack.info.codec = "prores";
9687
+ this.currentTrack.info.proresFormat = format;
9688
+ } else {
9689
+ }
9607
9690
  }
9608
9691
  const videoTrack = this.currentTrack;
9609
9692
  this.currentTrack.trackBacking = new MatroskaVideoTrackBacking(videoTrack);
@@ -9695,7 +9778,8 @@ var Mediabunny = (() => {
9695
9778
  codec: null,
9696
9779
  codecDescription: null,
9697
9780
  colorSpace: null,
9698
- alphaMode: false
9781
+ alphaMode: false,
9782
+ proresFormat: null
9699
9783
  };
9700
9784
  } else if (type === 2) {
9701
9785
  this.currentTrack.info = {
@@ -10878,7 +10962,7 @@ var Mediabunny = (() => {
10878
10962
  };
10879
10963
  }
10880
10964
  async canBeTransparent() {
10881
- return this.internalTrack.info.alphaMode;
10965
+ return this.internalTrack.info.alphaMode || this.internalTrack.info.codec === "prores" && (this.internalTrack.info.proresFormat === "ap4h" || this.internalTrack.info.proresFormat === "ap4x");
10882
10966
  }
10883
10967
  async getDecoderConfig() {
10884
10968
  if (!this.internalTrack.info.codec) {
@@ -10902,7 +10986,8 @@ var Mediabunny = (() => {
10902
10986
  avcCodecInfo: this.internalTrack.info.codec === "avc" && firstPacket ? extractAvcDecoderConfigurationRecord(firstPacket.data) : null,
10903
10987
  hevcCodecInfo: this.internalTrack.info.codec === "hevc" && firstPacket ? extractHevcDecoderConfigurationRecord(firstPacket.data) : null,
10904
10988
  vp9CodecInfo: this.internalTrack.info.codec === "vp9" && firstPacket ? extractVp9CodecInfoFromPacket(firstPacket.data) : null,
10905
- av1CodecInfo: this.internalTrack.info.codec === "av1" && firstPacket ? extractAv1CodecInfoFromPacket(firstPacket.data) : null
10989
+ av1CodecInfo: this.internalTrack.info.codec === "av1" && firstPacket ? extractAv1CodecInfoFromPacket(firstPacket.data) : null,
10990
+ proresFormat: this.internalTrack.info.proresFormat
10906
10991
  }),
10907
10992
  codedWidth: this.internalTrack.info.width,
10908
10993
  codedHeight: this.internalTrack.info.height,
@@ -13922,7 +14007,8 @@ var Mediabunny = (() => {
13922
14007
  avcCodecInfo: elementaryStream.info.avcCodecInfo,
13923
14008
  hevcCodecInfo: elementaryStream.info.hevcCodecInfo,
13924
14009
  vp9CodecInfo: null,
13925
- av1CodecInfo: null
14010
+ av1CodecInfo: null,
14011
+ proresFormat: null
13926
14012
  }),
13927
14013
  codedWidth: elementaryStream.info.width,
13928
14014
  codedHeight: elementaryStream.info.height,
@@ -19064,7 +19150,7 @@ var Mediabunny = (() => {
19064
19150
  ...options,
19065
19151
  codedWidth: options.codedWidth ?? 1280,
19066
19152
  codedHeight: options.codedHeight ?? 720,
19067
- codec: options.codec ?? buildVideoCodecString(codec, 1280, 720, 1e6)
19153
+ codec: options.codec ?? buildVideoCodecString(codec, 1280, 720, 1e6, false)
19068
19154
  };
19069
19155
  resolvedOptions.description ??= guessDescriptionForVideo(resolvedOptions);
19070
19156
  const key = JSON.stringify(resolvedOptions);
@@ -19272,12 +19358,11 @@ var Mediabunny = (() => {
19272
19358
  "init.displayWidth and init.displayHeight must be either both provided or both omitted."
19273
19359
  );
19274
19360
  }
19275
- this._data = init._doNotCopy ? toUint8Array(data) : toUint8Array(data).slice();
19276
- this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
19277
19361
  this.format = init.format;
19278
19362
  this.rotation = init.rotation ?? 0;
19279
19363
  this.timestamp = init.timestamp;
19280
19364
  this.duration = init.duration ?? 0;
19365
+ const layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
19281
19366
  let colorSpaceInit = init.colorSpace ?? null;
19282
19367
  if (colorSpaceInit === null) {
19283
19368
  if (this.format === "RGBA" || this.format === "RGBX" || this.format === "BGRA" || this.format === "BGRX") {
@@ -19296,7 +19381,6 @@ var Mediabunny = (() => {
19296
19381
  };
19297
19382
  }
19298
19383
  }
19299
- this.colorSpace = new VideoSampleColorSpace(colorSpaceInit);
19300
19384
  this.visibleRect = {
19301
19385
  left: init.visibleRect?.left ?? 0,
19302
19386
  top: init.visibleRect?.top ?? 0,
@@ -19310,6 +19394,9 @@ var Mediabunny = (() => {
19310
19394
  this.squarePixelWidth = this.visibleRect.width;
19311
19395
  this.squarePixelHeight = this.visibleRect.height;
19312
19396
  }
19397
+ this._data = init._doNotCopy ? toUint8Array(data) : toUint8Array(data).slice();
19398
+ this._layout = layout;
19399
+ this.colorSpace = new VideoSampleColorSpace(colorSpaceInit);
19313
19400
  } else if (typeof VideoFrame !== "undefined" && data instanceof VideoFrame) {
19314
19401
  if (init?.rotation !== void 0 && ![0, 90, 180, 270].includes(init.rotation)) {
19315
19402
  throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
@@ -19768,6 +19855,7 @@ var Mediabunny = (() => {
19768
19855
  timestamp: this.microsecondTimestamp,
19769
19856
  duration: this.microsecondDuration,
19770
19857
  colorSpace: this.colorSpace,
19858
+ visibleRect: this.visibleRect,
19771
19859
  displayWidth: this.squarePixelWidth,
19772
19860
  // Not display* since we're not passing rotation
19773
19861
  displayHeight: this.squarePixelHeight
@@ -19779,13 +19867,18 @@ var Mediabunny = (() => {
19779
19867
  // Drag 0 duration to undefined, glitches some codecs
19780
19868
  });
19781
19869
  } else if (this._data instanceof Uint8Array) {
19870
+ assert(this._layout);
19782
19871
  return new VideoFrame(this._data, {
19783
19872
  format: this.format,
19784
19873
  codedWidth: this.codedWidth,
19874
+ // This is technically wrong! codedWidth is a lie technically. But, since
19785
19875
  codedHeight: this.codedHeight,
19876
+ // we pass the layout (which contains the true coded width), we're good.
19877
+ layout: this._layout,
19786
19878
  timestamp: this.microsecondTimestamp,
19787
19879
  duration: this.microsecondDuration || void 0,
19788
19880
  colorSpace: this.colorSpace,
19881
+ visibleRect: this.visibleRect,
19789
19882
  displayWidth: this.squarePixelWidth,
19790
19883
  // Not display* since we're not passing rotation
19791
19884
  displayHeight: this.squarePixelHeight
@@ -21303,7 +21396,8 @@ var Mediabunny = (() => {
21303
21396
  options.codec,
21304
21397
  options.width,
21305
21398
  options.height,
21306
- resolvedBitrate
21399
+ resolvedBitrate,
21400
+ options.alpha === "keep"
21307
21401
  ),
21308
21402
  width: options.width,
21309
21403
  height: options.height,
@@ -21400,6 +21494,10 @@ var Mediabunny = (() => {
21400
21494
  /** @internal */
21401
21495
  _toVideoBitrate(codec, width, height) {
21402
21496
  const pixels = width * height;
21497
+ const referencePixels = 1920 * 1080;
21498
+ const referenceBitrate = 3e6;
21499
+ const scaleFactor = Math.pow(pixels / referencePixels, 0.95);
21500
+ const baseBitrate = referenceBitrate * scaleFactor;
21403
21501
  const codecEfficiencyFactors = {
21404
21502
  avc: 1,
21405
21503
  // H.264/AVC (baseline)
@@ -21409,13 +21507,11 @@ var Mediabunny = (() => {
21409
21507
  // Similar to HEVC
21410
21508
  av1: 0.4,
21411
21509
  // ~60% more efficient than AVC
21412
- vp8: 1.2
21510
+ vp8: 1.2,
21413
21511
  // Slightly less efficient than AVC
21512
+ prores: 22e7 / referenceBitrate
21513
+ // Apple ProRes white paper claims 220 Mbps for 1080p 422 HQ @30Hz
21414
21514
  };
21415
- const referencePixels = 1920 * 1080;
21416
- const referenceBitrate = 3e6;
21417
- const scaleFactor = Math.pow(pixels / referencePixels, 0.95);
21418
- const baseBitrate = referenceBitrate * scaleFactor;
21419
21515
  const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec];
21420
21516
  const finalBitrate = codecAdjustedBitrate * this._factor;
21421
21517
  return Math.ceil(finalBitrate / 1e3) * 1e3;
@@ -22009,6 +22105,7 @@ var Mediabunny = (() => {
22009
22105
  let ended = false;
22010
22106
  let terminated = false;
22011
22107
  let outOfBandError = null;
22108
+ let hasOutOfBandError = false;
22012
22109
  const timestamps = [];
22013
22110
  const maxQueueSize = () => Math.max(2, timestamps.length);
22014
22111
  (async () => {
@@ -22030,8 +22127,9 @@ var Mediabunny = (() => {
22030
22127
  ended = true;
22031
22128
  onQueueNotEmpty();
22032
22129
  })().catch((error) => {
22033
- if (!outOfBandError) {
22130
+ if (!hasOutOfBandError) {
22034
22131
  outOfBandError = error;
22132
+ hasOutOfBandError = true;
22035
22133
  onQueueNotEmpty();
22036
22134
  }
22037
22135
  });
@@ -22043,7 +22141,7 @@ var Mediabunny = (() => {
22043
22141
  throw new InputDisposedError();
22044
22142
  } else if (terminated) {
22045
22143
  return { value: void 0, done: true };
22046
- } else if (outOfBandError) {
22144
+ } else if (hasOutOfBandError) {
22047
22145
  throw outOfBandError;
22048
22146
  } else if (packetQueue.length > 0) {
22049
22147
  const value = packetQueue.shift();
@@ -22096,6 +22194,7 @@ var Mediabunny = (() => {
22096
22194
  let ended = false;
22097
22195
  let terminated = false;
22098
22196
  let outOfBandError = null;
22197
+ let hasOutOfBandError = false;
22099
22198
  const packetRetrievalOptions = {
22100
22199
  ...options,
22101
22200
  verifyKeyPackets: true,
@@ -22129,8 +22228,9 @@ var Mediabunny = (() => {
22129
22228
  ({ promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers());
22130
22229
  }
22131
22230
  }, (error) => {
22132
- if (!outOfBandError) {
22231
+ if (!hasOutOfBandError) {
22133
22232
  outOfBandError = error;
22233
+ hasOutOfBandError = true;
22134
22234
  onQueueNotEmpty();
22135
22235
  }
22136
22236
  });
@@ -22165,8 +22265,9 @@ var Mediabunny = (() => {
22165
22265
  decoderIsFlushed = true;
22166
22266
  onQueueNotEmpty();
22167
22267
  })().catch((error) => {
22168
- if (!outOfBandError) {
22268
+ if (!hasOutOfBandError) {
22169
22269
  outOfBandError = error;
22270
+ hasOutOfBandError = true;
22170
22271
  onQueueNotEmpty();
22171
22272
  }
22172
22273
  });
@@ -22185,7 +22286,7 @@ var Mediabunny = (() => {
22185
22286
  throw new InputDisposedError();
22186
22287
  } else if (terminated) {
22187
22288
  return { value: void 0, done: true };
22188
- } else if (outOfBandError) {
22289
+ } else if (hasOutOfBandError) {
22189
22290
  closeSamples();
22190
22291
  throw outOfBandError;
22191
22292
  } else if (sampleQueue.length > 0) {
@@ -22226,6 +22327,7 @@ var Mediabunny = (() => {
22226
22327
  let decoderIsFlushed = false;
22227
22328
  let terminated = false;
22228
22329
  let outOfBandError = null;
22330
+ let hasOutOfBandError = false;
22229
22331
  const pushToQueue = (sample) => {
22230
22332
  sampleQueue.push(sample);
22231
22333
  onQueueNotEmpty();
@@ -22256,8 +22358,9 @@ var Mediabunny = (() => {
22256
22358
  sample.close();
22257
22359
  }
22258
22360
  }, (error) => {
22259
- if (!outOfBandError) {
22361
+ if (!hasOutOfBandError) {
22260
22362
  outOfBandError = error;
22363
+ hasOutOfBandError = true;
22261
22364
  onQueueNotEmpty();
22262
22365
  }
22263
22366
  });
@@ -22327,8 +22430,9 @@ var Mediabunny = (() => {
22327
22430
  decoderIsFlushed = true;
22328
22431
  onQueueNotEmpty();
22329
22432
  })().catch((error) => {
22330
- if (!outOfBandError) {
22433
+ if (!hasOutOfBandError) {
22331
22434
  outOfBandError = error;
22435
+ hasOutOfBandError = true;
22332
22436
  onQueueNotEmpty();
22333
22437
  }
22334
22438
  });
@@ -22346,7 +22450,7 @@ var Mediabunny = (() => {
22346
22450
  throw new InputDisposedError();
22347
22451
  } else if (terminated) {
22348
22452
  return { value: void 0, done: true };
22349
- } else if (outOfBandError) {
22453
+ } else if (hasOutOfBandError) {
22350
22454
  closeSamples();
22351
22455
  throw outOfBandError;
22352
22456
  } else if (sampleQueue.length > 0) {
@@ -22411,7 +22515,8 @@ var Mediabunny = (() => {
22411
22515
  this.currentAlphaPacketIndex = 0;
22412
22516
  this.alphaRaslSkipped = false;
22413
22517
  // For HEVC stuff
22414
- this.frameHandlerSerializer = new CallSerializer();
22518
+ this.finalSamples = [];
22519
+ this.mergeAlphaPromises = [];
22415
22520
  const MatchingCustomDecoder = customVideoDecoders.find((x) => x.supports(codec, decoderConfig));
22416
22521
  if (MatchingCustomDecoder) {
22417
22522
  this.customDecoder = new MatchingCustomDecoder();
@@ -22423,18 +22528,19 @@ var Mediabunny = (() => {
22423
22528
  }
22424
22529
  this.finalizeAndEmitSample(sample);
22425
22530
  };
22426
- void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
22531
+ this.customDecoder.onError = (error) => {
22532
+ onError(error);
22533
+ };
22534
+ void this.customDecoderCallSerializer.call(() => this.customDecoder.init()).catch((error) => onError(error));
22427
22535
  } else {
22428
22536
  const colorHandler = (frame) => {
22429
- this.frameHandlerSerializer.call(async () => {
22430
- if (this.alphaQueue.length > 0) {
22431
- const alphaFrame = this.alphaQueue.shift();
22432
- assert(alphaFrame !== void 0);
22433
- await this.mergeAlpha(frame, alphaFrame);
22434
- } else {
22435
- this.colorQueue.push(frame);
22436
- }
22437
- }).catch((error) => this.onError(error));
22537
+ if (this.alphaQueue.length > 0) {
22538
+ const alphaFrame = this.alphaQueue.shift();
22539
+ assert(alphaFrame !== void 0);
22540
+ void this.mergeAlpha(frame, alphaFrame);
22541
+ } else {
22542
+ this.colorQueue.push(frame);
22543
+ }
22438
22544
  };
22439
22545
  if (codec === "avc" && this.decoderConfig.description && isChromium()) {
22440
22546
  const record = deserializeAvcDecoderConfigurationRecord(toUint8Array(this.decoderConfig.description));
@@ -22485,7 +22591,7 @@ var Mediabunny = (() => {
22485
22591
  }
22486
22592
  if (this.customDecoder) {
22487
22593
  this.customDecoderQueueSize++;
22488
- void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).then(() => this.customDecoderQueueSize--);
22594
+ void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--);
22489
22595
  } else {
22490
22596
  assert(this.decoder);
22491
22597
  if (!isWebKit()) {
@@ -22527,27 +22633,25 @@ var Mediabunny = (() => {
22527
22633
  }
22528
22634
  if (!this.alphaDecoder) {
22529
22635
  const alphaHandler = (frame) => {
22530
- this.frameHandlerSerializer.call(async () => {
22636
+ if (this.colorQueue.length > 0) {
22637
+ const colorFrame = this.colorQueue.shift();
22638
+ assert(colorFrame !== void 0);
22639
+ void this.mergeAlpha(colorFrame, frame);
22640
+ } else {
22641
+ this.alphaQueue.push(frame);
22642
+ }
22643
+ this.decodedAlphaChunkCount++;
22644
+ while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) {
22645
+ this.nullAlphaFrameQueue.shift();
22531
22646
  if (this.colorQueue.length > 0) {
22532
22647
  const colorFrame = this.colorQueue.shift();
22533
22648
  assert(colorFrame !== void 0);
22534
- await this.mergeAlpha(colorFrame, frame);
22649
+ void this.mergeAlpha(colorFrame, null);
22535
22650
  } else {
22536
- this.alphaQueue.push(frame);
22537
- }
22538
- this.decodedAlphaChunkCount++;
22539
- while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) {
22540
- this.nullAlphaFrameQueue.shift();
22541
- if (this.colorQueue.length > 0) {
22542
- const colorFrame = this.colorQueue.shift();
22543
- assert(colorFrame !== void 0);
22544
- await this.mergeAlpha(colorFrame, null);
22545
- } else {
22546
- this.alphaQueue.push(null);
22547
- }
22651
+ this.alphaQueue.push(null);
22548
22652
  }
22549
- this.alphaDecoderQueueSize--;
22550
- }).catch((error) => this.onError(error));
22653
+ }
22654
+ this.alphaDecoderQueueSize--;
22551
22655
  };
22552
22656
  const stack = new Error("Decoding error").stack;
22553
22657
  this.alphaDecoder = new VideoDecoder({
@@ -22630,15 +22734,29 @@ var Mediabunny = (() => {
22630
22734
  this.onSample(sample);
22631
22735
  }
22632
22736
  async mergeAlpha(color, alpha) {
22633
- if (!alpha) {
22634
- const finalSample2 = new VideoSample(color);
22635
- this.sampleHandler(finalSample2);
22636
- return;
22737
+ const resolver = promiseWithResolvers();
22738
+ this.mergeAlphaPromises.push(resolver.promise);
22739
+ const result = { sample: null };
22740
+ this.finalSamples.push(result);
22741
+ try {
22742
+ if (!alpha) {
22743
+ result.sample = new VideoSample(color);
22744
+ } else {
22745
+ assert(this.merger);
22746
+ const finalFrame = await this.merger.merge(color, alpha);
22747
+ result.sample = new VideoSample(finalFrame);
22748
+ }
22749
+ while (this.finalSamples.length > 0 && this.finalSamples[0].sample !== null) {
22750
+ const next = this.finalSamples.shift();
22751
+ this.sampleHandler(next.sample);
22752
+ }
22753
+ } catch (error) {
22754
+ removeItem(this.finalSamples, result);
22755
+ this.onError(error);
22756
+ } finally {
22757
+ removeItem(this.mergeAlphaPromises, resolver.promise);
22758
+ resolver.resolve();
22637
22759
  }
22638
- assert(this.merger);
22639
- const finalFrame = await this.merger.update(color, alpha);
22640
- const finalSample = new VideoSample(finalFrame);
22641
- this.sampleHandler(finalSample);
22642
22760
  }
22643
22761
  async flush() {
22644
22762
  if (this.customDecoder) {
@@ -22649,7 +22767,7 @@ var Mediabunny = (() => {
22649
22767
  this.decoder.flush(),
22650
22768
  this.alphaDecoder?.flush()
22651
22769
  ]);
22652
- await this.frameHandlerSerializer.currentPromise;
22770
+ await Promise.all(this.mergeAlphaPromises);
22653
22771
  this.colorQueue.forEach((x) => x.close());
22654
22772
  this.colorQueue.length = 0;
22655
22773
  this.alphaQueue.forEach((x) => x?.close());
@@ -22689,205 +22807,62 @@ var Mediabunny = (() => {
22689
22807
  this.sampleQueue.length = 0;
22690
22808
  }
22691
22809
  };
22692
- var mergerGpuUnavailable = false;
22693
- var _ColorAlphaMerger = class _ColorAlphaMerger {
22810
+ var mergerWorkerUrl = null;
22811
+ var ColorAlphaMerger = class {
22694
22812
  constructor() {
22695
- this.canvas = null;
22696
- this.gl = null;
22697
- this.program = null;
22698
- this.vao = null;
22699
- this.colorTexture = null;
22700
- this.alphaTexture = null;
22701
- this.worker = null;
22813
+ this.workers = [];
22814
+ this.nextWorkerIndex = 0;
22702
22815
  this.pendingRequests = /* @__PURE__ */ new Map();
22703
22816
  this.nextRequestId = 0;
22704
- const canMakeCanvas = typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined" && typeof document.createElement === "function";
22705
- if (!_ColorAlphaMerger.forceCpu && canMakeCanvas && !mergerGpuUnavailable) {
22706
- try {
22707
- if (typeof OffscreenCanvas !== "undefined") {
22708
- this.canvas = new OffscreenCanvas(300, 150);
22709
- } else {
22710
- this.canvas = document.createElement("canvas");
22711
- }
22712
- const gl = this.canvas.getContext("webgl2", {
22713
- premultipliedAlpha: false
22817
+ }
22818
+ merge(color, alpha) {
22819
+ if (this.workers.length === 0) {
22820
+ if (!mergerWorkerUrl) {
22821
+ const blob = new Blob(
22822
+ [`(${colorAlphaMergerWorkerCode.toString()})()`],
22823
+ { type: "application/javascript" }
22824
+ );
22825
+ mergerWorkerUrl = URL.createObjectURL(blob);
22826
+ }
22827
+ const poolSize = clamp(navigator.hardwareConcurrency, 1, 4);
22828
+ for (let i = 0; i < poolSize; i++) {
22829
+ const worker2 = new Worker(mergerWorkerUrl);
22830
+ worker2.addEventListener("message", (event) => {
22831
+ const data = event.data;
22832
+ const pending2 = this.pendingRequests.get(data.id);
22833
+ if (!pending2) {
22834
+ return;
22835
+ }
22836
+ this.pendingRequests.delete(data.id);
22837
+ if ("error" in data) {
22838
+ pending2.reject(new Error(data.error));
22839
+ } else {
22840
+ pending2.resolve(data.frame);
22841
+ }
22714
22842
  });
22715
- if (!gl) {
22716
- throw new Error("Couldn't acquire WebGL 2 context.");
22717
- }
22718
- this.gl = gl;
22719
- this.program = this.createProgram();
22720
- this.vao = this.createVAO();
22721
- this.colorTexture = this.createTexture();
22722
- this.alphaTexture = this.createTexture();
22723
- this.gl.useProgram(this.program);
22724
- this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_colorTexture"), 0);
22725
- this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_alphaTexture"), 1);
22726
- } catch (error) {
22727
- this.gl = null;
22728
- this.canvas = null;
22729
- mergerGpuUnavailable = true;
22730
- Logging._warn("Falling back to CPU for color/alpha merging.", error);
22843
+ worker2.addEventListener("error", (event) => {
22844
+ const error = new Error(event.message || "Color/alpha merge worker error.");
22845
+ for (const pending2 of this.pendingRequests.values()) {
22846
+ pending2.reject(error);
22847
+ }
22848
+ this.pendingRequests.clear();
22849
+ });
22850
+ this.workers.push(worker2);
22731
22851
  }
22732
22852
  }
22733
- }
22734
- async update(color, alpha) {
22735
- if (this.gl) {
22736
- return this.updateGpu(color, alpha);
22737
- } else {
22738
- return this.updateCpu(color, alpha);
22739
- }
22740
- }
22741
- createProgram() {
22742
- assert(this.gl);
22743
- const vertexShader = this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
22744
- in vec2 a_position;
22745
- in vec2 a_texCoord;
22746
- out vec2 v_texCoord;
22747
-
22748
- void main() {
22749
- gl_Position = vec4(a_position, 0.0, 1.0);
22750
- v_texCoord = a_texCoord;
22751
- }
22752
- `);
22753
- const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
22754
- precision highp float;
22755
-
22756
- uniform sampler2D u_colorTexture;
22757
- uniform sampler2D u_alphaTexture;
22758
- in vec2 v_texCoord;
22759
- out vec4 fragColor;
22760
-
22761
- void main() {
22762
- vec3 color = texture(u_colorTexture, v_texCoord).rgb;
22763
- float alpha = texture(u_alphaTexture, v_texCoord).r;
22764
- fragColor = vec4(color, alpha);
22765
- }
22766
- `);
22767
- const program = this.gl.createProgram();
22768
- this.gl.attachShader(program, vertexShader);
22769
- this.gl.attachShader(program, fragmentShader);
22770
- this.gl.linkProgram(program);
22771
- return program;
22772
- }
22773
- createShader(type, source) {
22774
- assert(this.gl);
22775
- const shader = this.gl.createShader(type);
22776
- this.gl.shaderSource(shader, source);
22777
- this.gl.compileShader(shader);
22778
- return shader;
22779
- }
22780
- createVAO() {
22781
- assert(this.gl);
22782
- assert(this.program);
22783
- const vao = this.gl.createVertexArray();
22784
- this.gl.bindVertexArray(vao);
22785
- const vertices = new Float32Array([
22786
- -1,
22787
- -1,
22788
- 0,
22789
- 1,
22790
- 1,
22791
- -1,
22792
- 1,
22793
- 1,
22794
- -1,
22795
- 1,
22796
- 0,
22797
- 0,
22798
- 1,
22799
- 1,
22800
- 1,
22801
- 0
22802
- ]);
22803
- const buffer = this.gl.createBuffer();
22804
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
22805
- this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
22806
- const positionLocation = this.gl.getAttribLocation(this.program, "a_position");
22807
- const texCoordLocation = this.gl.getAttribLocation(this.program, "a_texCoord");
22808
- this.gl.enableVertexAttribArray(positionLocation);
22809
- this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 16, 0);
22810
- this.gl.enableVertexAttribArray(texCoordLocation);
22811
- this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 16, 8);
22812
- return vao;
22813
- }
22814
- createTexture() {
22815
- assert(this.gl);
22816
- const texture = this.gl.createTexture();
22817
- this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
22818
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
22819
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
22820
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
22821
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
22822
- return texture;
22823
- }
22824
- updateGpu(color, alpha) {
22825
- assert(this.gl);
22826
- assert(this.canvas);
22827
- if (color.displayWidth !== this.canvas.width || color.displayHeight !== this.canvas.height) {
22828
- this.canvas.width = color.displayWidth;
22829
- this.canvas.height = color.displayHeight;
22830
- }
22831
- this.gl.activeTexture(this.gl.TEXTURE0);
22832
- this.gl.bindTexture(this.gl.TEXTURE_2D, this.colorTexture);
22833
- this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, color);
22834
- this.gl.activeTexture(this.gl.TEXTURE1);
22835
- this.gl.bindTexture(this.gl.TEXTURE_2D, this.alphaTexture);
22836
- this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, alpha);
22837
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
22838
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
22839
- this.gl.bindVertexArray(this.vao);
22840
- this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
22841
- const finalFrame = new VideoFrame(this.canvas, {
22842
- timestamp: color.timestamp,
22843
- duration: color.duration ?? void 0
22844
- });
22845
- color.close();
22846
- alpha.close();
22847
- return finalFrame;
22848
- }
22849
- updateCpu(color, alpha) {
22850
- if (!this.worker) {
22851
- const blob = new Blob(
22852
- [`(${colorAlphaMergerWorkerCode.toString()})()`],
22853
- { type: "application/javascript" }
22854
- );
22855
- const url2 = URL.createObjectURL(blob);
22856
- this.worker = new Worker(url2);
22857
- URL.revokeObjectURL(url2);
22858
- this.worker.addEventListener("message", (event) => {
22859
- const data = event.data;
22860
- const pending2 = this.pendingRequests.get(data.id);
22861
- if (!pending2) {
22862
- return;
22863
- }
22864
- this.pendingRequests.delete(data.id);
22865
- if ("error" in data) {
22866
- pending2.reject(new Error(data.error));
22867
- } else {
22868
- pending2.resolve(data.frame);
22869
- }
22870
- });
22871
- this.worker.addEventListener("error", (event) => {
22872
- const error = new Error(event.message || "Color/alpha merge worker error.");
22873
- for (const pending2 of this.pendingRequests.values()) {
22874
- pending2.reject(error);
22875
- }
22876
- this.pendingRequests.clear();
22877
- });
22878
- }
22879
22853
  const id = this.nextRequestId++;
22880
22854
  const pending = promiseWithResolvers();
22881
22855
  this.pendingRequests.set(id, pending);
22882
- this.worker.postMessage({ id, color, alpha }, { transfer: [color, alpha] });
22856
+ const worker = this.workers[this.nextWorkerIndex];
22857
+ this.nextWorkerIndex = (this.nextWorkerIndex + 1) % this.workers.length;
22858
+ worker.postMessage({ id, color, alpha }, { transfer: [color, alpha] });
22883
22859
  return pending.promise;
22884
22860
  }
22885
22861
  close() {
22886
- this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
22887
- this.gl = null;
22888
- this.canvas = null;
22889
- this.worker?.terminate();
22890
- this.worker = null;
22862
+ for (const worker of this.workers) {
22863
+ worker.terminate();
22864
+ }
22865
+ this.workers.length = 0;
22891
22866
  const error = new Error("Color/alpha merger closed.");
22892
22867
  for (const pending of this.pendingRequests.values()) {
22893
22868
  pending.reject(error);
@@ -22895,8 +22870,6 @@ var Mediabunny = (() => {
22895
22870
  this.pendingRequests.clear();
22896
22871
  }
22897
22872
  };
22898
- _ColorAlphaMerger.forceCpu = true;
22899
- var ColorAlphaMerger = _ColorAlphaMerger;
22900
22873
  var colorAlphaMergerWorkerCode = () => {
22901
22874
  let cpuAlphaBuffer = null;
22902
22875
  let cpuColorBuffer = null;
@@ -22930,18 +22903,18 @@ var Mediabunny = (() => {
22930
22903
  `CPU color/alpha merging requires the alpha frame to have the same bit depth as the color frame (color: '${format}', alpha: '${alphaFormat}').`
22931
22904
  );
22932
22905
  }
22933
- const width = color.codedWidth;
22934
- const height = color.codedHeight;
22935
22906
  if (format === "RGBX" || format === "RGBA" || format === "BGRX" || format === "BGRA") {
22936
- return await mergeInterleavedRgba(color, alpha, width, height, format);
22907
+ return await mergeInterleavedRgba(color, alpha, format);
22937
22908
  } else if (format === "I420" || format === "I420P10" || format === "I420P12" || format === "I422" || format === "I422P10" || format === "I422P12" || format === "I444" || format === "I444P10" || format === "I444P12") {
22938
- return await mergePlanarYuv(color, alpha, width, height, format);
22909
+ return await mergePlanarYuv(color, alpha, format);
22939
22910
  } else if (format === "NV12") {
22940
- return await mergeNv12(color, alpha, width, height);
22911
+ return await mergeNv12(color, alpha);
22941
22912
  }
22942
22913
  throw new Error(`CPU color/alpha merging does not support format '${format}'.`);
22943
22914
  };
22944
- const mergeInterleavedRgba = async (color, alpha, width, height, format) => {
22915
+ const mergeInterleavedRgba = async (color, alpha, format) => {
22916
+ const width = color.visibleRect?.width ?? color.codedWidth;
22917
+ const height = color.visibleRect?.height ?? color.codedHeight;
22945
22918
  const pixelCount = width * height;
22946
22919
  const output = new Uint8Array(pixelCount * 4);
22947
22920
  await color.copyTo(output);
@@ -22960,7 +22933,9 @@ var Mediabunny = (() => {
22960
22933
  };
22961
22934
  return new VideoFrame(output, init);
22962
22935
  };
22963
- const mergePlanarYuv = async (color, alpha, width, height, format) => {
22936
+ const mergePlanarYuv = async (color, alpha, format) => {
22937
+ const width = color.visibleRect?.width ?? color.codedWidth;
22938
+ const height = color.visibleRect?.height ?? color.codedHeight;
22964
22939
  const is10 = format.includes("P10");
22965
22940
  const is12 = format.includes("P12");
22966
22941
  const bytesPerSample = is10 || is12 ? 2 : 1;
@@ -22998,7 +22973,9 @@ var Mediabunny = (() => {
22998
22973
  };
22999
22974
  return new VideoFrame(output, init);
23000
22975
  };
23001
- const mergeNv12 = async (color, alpha, width, height) => {
22976
+ const mergeNv12 = async (color, alpha) => {
22977
+ const width = color.visibleRect?.width ?? color.codedWidth;
22978
+ const height = color.visibleRect?.height ?? color.codedHeight;
23002
22979
  const ySize = width * height;
23003
22980
  const chromaW = Math.ceil(width / 2);
23004
22981
  const chromaH = Math.ceil(height / 2);
@@ -23361,7 +23338,10 @@ var Mediabunny = (() => {
23361
23338
  }
23362
23339
  sampleHandler(sample);
23363
23340
  };
23364
- void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
23341
+ this.customDecoder.onError = (error) => {
23342
+ onError(error);
23343
+ };
23344
+ void this.customDecoderCallSerializer.call(() => this.customDecoder.init()).catch((error) => onError(error));
23365
23345
  } else {
23366
23346
  const stack = new Error("Decoding error").stack;
23367
23347
  this.decoder = new AudioDecoder({
@@ -23391,7 +23371,7 @@ var Mediabunny = (() => {
23391
23371
  decode(packet) {
23392
23372
  if (this.customDecoder) {
23393
23373
  this.customDecoderQueueSize++;
23394
- void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).then(() => this.customDecoderQueueSize--);
23374
+ void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).catch((error) => this.onError(error)).finally(() => this.customDecoderQueueSize--);
23395
23375
  } else {
23396
23376
  assert(this.decoder);
23397
23377
  this.expectedFirstTimestamp ??= packet.timestamp;
@@ -27129,7 +27109,7 @@ var Mediabunny = (() => {
27129
27109
  i16(65535)
27130
27110
  // Pre-defined
27131
27111
  ], [
27132
- VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec](trackData),
27112
+ VIDEO_CODEC_TO_CONFIGURATION_BOX[trackData.track.source._codec]?.(trackData) ?? null,
27133
27113
  pasp(trackData),
27134
27114
  colorSpaceIsComplete(trackData.info.decoderConfig.colorSpace) ? colr(trackData) : null
27135
27115
  ]);
@@ -28124,6 +28104,8 @@ var Mediabunny = (() => {
28124
28104
  return "vp09";
28125
28105
  case "av1":
28126
28106
  return "av01";
28107
+ case "prores":
28108
+ return fullCodecString;
28127
28109
  }
28128
28110
  };
28129
28111
  var VIDEO_CODEC_TO_CONFIGURATION_BOX = {
@@ -28131,7 +28113,8 @@ var Mediabunny = (() => {
28131
28113
  hevc: hvcC,
28132
28114
  vp8: vpcC,
28133
28115
  vp9: vpcC,
28134
- av1: av1C
28116
+ av1: av1C,
28117
+ prores: null
28135
28118
  };
28136
28119
  var audioCodecToBoxName = (codec, isQuickTime) => {
28137
28120
  switch (codec) {
@@ -30088,6 +30071,7 @@ var Mediabunny = (() => {
30088
30071
  { id: 156 /* FlagLacing */, data: 0 },
30089
30072
  { id: 2274716 /* Language */, data: trackData.track.metadata.languageCode ?? UNDETERMINED_LANGUAGE },
30090
30073
  { id: 134 /* CodecID */, data: codecId },
30074
+ trackData.codecPrivate ? { id: 25506 /* CodecPrivate */, data: toUint8Array(trackData.codecPrivate) } : null,
30091
30075
  { id: 22186 /* CodecDelay */, data: 0 },
30092
30076
  { id: 22203 /* SeekPreRoll */, data: seekPreRollNs },
30093
30077
  trackData.track.metadata.name !== void 0 ? { id: 21358 /* Name */, data: new EBMLUnicodeString(trackData.track.metadata.name) } : null,
@@ -30100,10 +30084,6 @@ var Mediabunny = (() => {
30100
30084
  videoSpecificTrackInfo(trackData) {
30101
30085
  const { frameRate, rotation } = trackData.track.metadata;
30102
30086
  const elements = [
30103
- trackData.info.decoderConfig.description ? {
30104
- id: 25506 /* CodecPrivate */,
30105
- data: toUint8Array(trackData.info.decoderConfig.description)
30106
- } : null,
30107
30087
  frameRate ? {
30108
30088
  id: 2352003 /* DefaultDuration */,
30109
30089
  data: 1e9 / frameRate
@@ -30163,10 +30143,6 @@ var Mediabunny = (() => {
30163
30143
  audioSpecificTrackInfo(trackData) {
30164
30144
  const pcmInfo = PCM_AUDIO_CODECS.includes(trackData.track.source._codec) ? parsePcmCodec(trackData.track.source._codec) : null;
30165
30145
  return [
30166
- trackData.info.decoderConfig.description ? {
30167
- id: 25506 /* CodecPrivate */,
30168
- data: toUint8Array(trackData.info.decoderConfig.description)
30169
- } : null,
30170
30146
  { id: 225 /* Audio */, data: [
30171
30147
  { id: 181 /* SamplingFrequency */, data: new EBMLFloat32(trackData.info.sampleRate) },
30172
30148
  { id: 159 /* Channels */, data: trackData.info.numberOfChannels },
@@ -30174,10 +30150,9 @@ var Mediabunny = (() => {
30174
30150
  ] }
30175
30151
  ];
30176
30152
  }
30153
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
30177
30154
  subtitleSpecificTrackInfo(trackData) {
30178
- return [
30179
- { id: 25506 /* CodecPrivate */, data: textEncoder.encode(trackData.info.config.description) }
30180
- ];
30155
+ return [];
30181
30156
  }
30182
30157
  maybeCreateTags() {
30183
30158
  const simpleTags = [];
@@ -30466,22 +30441,19 @@ var Mediabunny = (() => {
30466
30441
  },
30467
30442
  chunkQueue: [],
30468
30443
  lastWrittenMsTimestamp: null,
30444
+ codecPrivate: meta.decoderConfig.description ?? null,
30469
30445
  closed: false
30470
30446
  };
30471
30447
  if (track.source._codec === "vp9") {
30472
- newTrackData.info.decoderConfig = {
30473
- ...newTrackData.info.decoderConfig,
30474
- description: new Uint8Array(
30475
- generateVp9CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)
30476
- )
30477
- };
30448
+ newTrackData.codecPrivate = new Uint8Array(
30449
+ generateVp9CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)
30450
+ );
30478
30451
  } else if (track.source._codec === "av1") {
30479
- newTrackData.info.decoderConfig = {
30480
- ...newTrackData.info.decoderConfig,
30481
- description: new Uint8Array(
30482
- generateAv1CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)
30483
- )
30484
- };
30452
+ newTrackData.codecPrivate = new Uint8Array(
30453
+ generateAv1CodecConfigurationFromCodecString(newTrackData.info.decoderConfig.codec)
30454
+ );
30455
+ } else if (track.source._codec === "prores") {
30456
+ newTrackData.codecPrivate = textEncoder.encode(meta.decoderConfig.codec);
30485
30457
  }
30486
30458
  this.trackDatas.push(newTrackData);
30487
30459
  this.trackDatas.sort((a, b) => a.track.id - b.track.id);
@@ -30530,6 +30502,7 @@ var Mediabunny = (() => {
30530
30502
  },
30531
30503
  chunkQueue: [],
30532
30504
  lastWrittenMsTimestamp: null,
30505
+ codecPrivate: decoderConfig.description ?? null,
30533
30506
  closed: false
30534
30507
  };
30535
30508
  this.trackDatas.push(newTrackData);
@@ -30555,6 +30528,7 @@ var Mediabunny = (() => {
30555
30528
  },
30556
30529
  chunkQueue: [],
30557
30530
  lastWrittenMsTimestamp: null,
30531
+ codecPrivate: textEncoder.encode(meta.config.description),
30558
30532
  closed: false
30559
30533
  };
30560
30534
  this.trackDatas.push(newTrackData);
@@ -32628,8 +32602,15 @@ ${cue.notes ?? ""}`;
32628
32602
  * So, we keep track of the encoder error and throw it as soon as we get the chance.
32629
32603
  */
32630
32604
  this.error = null;
32631
- this.closed = false;
32605
+ this.errorSet = false;
32632
32606
  this.lastMuxerPromise = Promise.resolve();
32607
+ this.closed = false;
32608
+ }
32609
+ setError(error) {
32610
+ if (!this.errorSet) {
32611
+ this.error = error;
32612
+ this.errorSet = true;
32613
+ }
32633
32614
  }
32634
32615
  async add(videoSample, shouldClose, encodeOptions) {
32635
32616
  const originalSample = videoSample;
@@ -32780,7 +32761,8 @@ ${cue.notes ?? ""}`;
32780
32761
  if (this.customEncoder) {
32781
32762
  this.customEncoderQueueSize++;
32782
32763
  const clonedSample = sampleToEncode.clone();
32783
- const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).then(() => this.customEncoderQueueSize--).catch((error) => this.error ??= error).finally(() => {
32764
+ const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).catch((error) => this.setError(error)).finally(() => {
32765
+ this.customEncoderQueueSize--;
32784
32766
  clonedSample.close();
32785
32767
  });
32786
32768
  if (this.customEncoderQueueSize >= 4) {
@@ -32824,12 +32806,10 @@ ${cue.notes ?? ""}`;
32824
32806
  this.encoder.encode(videoFrame, finalEncodeOptions);
32825
32807
  videoFrame.close();
32826
32808
  } else {
32827
- const width = videoFrame.displayWidth;
32828
- const height = videoFrame.displayHeight;
32829
32809
  if (!this.splitter) {
32830
- this.splitter = new ColorAlphaSplitter(width, height);
32810
+ this.splitter = new ColorAlphaSplitter();
32831
32811
  }
32832
- const { colorFrame, alphaFrame } = await this.splitter.update(videoFrame);
32812
+ const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame);
32833
32813
  this.alphaFrameQueue.push(alphaFrame);
32834
32814
  this.encoder.encode(colorFrame, finalEncodeOptions);
32835
32815
  colorFrame.close();
@@ -32893,9 +32873,12 @@ ${cue.notes ?? ""}`;
32893
32873
  maybeEnsureIsKeyPacket(this.source._connectedTrack, packet);
32894
32874
  this.encodingConfig.onEncodedPacket?.(packet, meta);
32895
32875
  this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
32896
- this.error ??= error;
32876
+ this.setError(error);
32897
32877
  });
32898
32878
  };
32879
+ this.customEncoder.onError = (error) => {
32880
+ this.setError(error);
32881
+ };
32899
32882
  await this.customEncoder.init();
32900
32883
  } else {
32901
32884
  if (typeof VideoEncoder === "undefined") {
@@ -32953,7 +32936,7 @@ ${cue.notes ?? ""}`;
32953
32936
  maybeEnsureIsKeyPacket(this.source._connectedTrack, packet);
32954
32937
  this.encodingConfig.onEncodedPacket?.(packet, meta);
32955
32938
  this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
32956
- this.error ??= error;
32939
+ this.setError(error);
32957
32940
  });
32958
32941
  this.emittedEncoderPackets++;
32959
32942
  };
@@ -32987,7 +32970,7 @@ ${cue.notes ?? ""}`;
32987
32970
  },
32988
32971
  error: (error) => {
32989
32972
  error.stack = stack;
32990
- this.error ??= error;
32973
+ this.setError(error);
32991
32974
  }
32992
32975
  });
32993
32976
  this.encoder.configure(encoderConfig);
@@ -33011,7 +32994,7 @@ ${cue.notes ?? ""}`;
33011
32994
  },
33012
32995
  error: (error) => {
33013
32996
  error.stack = stack2;
33014
- this.error ??= error;
32997
+ this.setError(error);
33015
32998
  }
33016
32999
  });
33017
33000
  this.alphaEncoder.configure(encoderConfig);
@@ -33043,6 +33026,7 @@ ${cue.notes ?? ""}`;
33043
33026
  if (!forceClose) {
33044
33027
  await this.encoder.flush();
33045
33028
  await this.alphaEncoder?.flush();
33029
+ await wait(25);
33046
33030
  }
33047
33031
  if (this.encoder.state !== "closed") {
33048
33032
  this.encoder.close();
@@ -33065,274 +33049,28 @@ ${cue.notes ?? ""}`;
33065
33049
  }
33066
33050
  }
33067
33051
  checkForEncoderError() {
33068
- if (this.error) {
33052
+ if (this.errorSet) {
33069
33053
  throw this.error;
33070
33054
  }
33071
33055
  }
33072
33056
  };
33073
- var splitterGpuUnavailable = false;
33074
- var _ColorAlphaSplitter = class _ColorAlphaSplitter {
33075
- constructor(initialWidth, initialHeight) {
33076
- this.canvas = null;
33077
- this.gl = null;
33078
- this.colorProgram = null;
33079
- this.alphaProgram = null;
33080
- this.vao = null;
33081
- this.sourceTexture = null;
33082
- this.alphaResolutionLocation = null;
33057
+ var splitterWorkerUrl = null;
33058
+ var ColorAlphaSplitter = class {
33059
+ constructor() {
33083
33060
  this.worker = null;
33084
33061
  this.pendingRequests = /* @__PURE__ */ new Map();
33085
33062
  this.nextRequestId = 0;
33086
- const canMakeCanvas = typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined" && typeof document.createElement === "function";
33087
- if (!_ColorAlphaSplitter.forceCpu && canMakeCanvas && !splitterGpuUnavailable) {
33088
- try {
33089
- if (typeof OffscreenCanvas !== "undefined") {
33090
- this.canvas = new OffscreenCanvas(initialWidth, initialHeight);
33091
- } else {
33092
- this.canvas = document.createElement("canvas");
33093
- this.canvas.width = initialWidth;
33094
- this.canvas.height = initialHeight;
33095
- }
33096
- const gl = this.canvas.getContext("webgl2", {
33097
- alpha: true
33098
- // Needed due to the YUV thing we do for alpha
33099
- });
33100
- if (!gl) {
33101
- throw new Error("Couldn't acquire WebGL 2 context.");
33102
- }
33103
- this.gl = gl;
33104
- this.colorProgram = this.createColorProgram();
33105
- this.alphaProgram = this.createAlphaProgram();
33106
- this.vao = this.createVAO();
33107
- this.sourceTexture = this.createTexture();
33108
- this.alphaResolutionLocation = this.gl.getUniformLocation(this.alphaProgram, "u_resolution");
33109
- this.gl.useProgram(this.colorProgram);
33110
- this.gl.uniform1i(this.gl.getUniformLocation(this.colorProgram, "u_sourceTexture"), 0);
33111
- this.gl.useProgram(this.alphaProgram);
33112
- this.gl.uniform1i(this.gl.getUniformLocation(this.alphaProgram, "u_sourceTexture"), 0);
33113
- } catch (error) {
33114
- this.gl = null;
33115
- this.canvas = null;
33116
- splitterGpuUnavailable = true;
33117
- Logging._warn("Falling back to CPU for color/alpha splitting.", error);
33118
- }
33119
- }
33120
33063
  }
33121
- async update(sourceFrame) {
33122
- if (this.gl) {
33123
- return this.updateGpu(sourceFrame);
33124
- } else {
33125
- return this.updateCpu(sourceFrame);
33126
- }
33127
- }
33128
- updateGpu(sourceFrame) {
33129
- assert(this.gl);
33130
- assert(this.canvas);
33131
- if (sourceFrame.displayWidth !== this.canvas.width || sourceFrame.displayHeight !== this.canvas.height) {
33132
- this.canvas.width = sourceFrame.displayWidth;
33133
- this.canvas.height = sourceFrame.displayHeight;
33134
- }
33135
- this.gl.activeTexture(this.gl.TEXTURE0);
33136
- this.gl.bindTexture(this.gl.TEXTURE_2D, this.sourceTexture);
33137
- this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceFrame);
33138
- const colorFrame = this.runColorProgram(sourceFrame);
33139
- const alphaFrame = this.runAlphaProgram(sourceFrame);
33140
- sourceFrame.close();
33141
- return { colorFrame, alphaFrame };
33142
- }
33143
- createVertexShader() {
33144
- assert(this.gl);
33145
- return this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
33146
- in vec2 a_position;
33147
- in vec2 a_texCoord;
33148
- out vec2 v_texCoord;
33149
-
33150
- void main() {
33151
- gl_Position = vec4(a_position, 0.0, 1.0);
33152
- v_texCoord = a_texCoord;
33153
- }
33154
- `);
33155
- }
33156
- createColorProgram() {
33157
- assert(this.gl);
33158
- const vertexShader = this.createVertexShader();
33159
- const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
33160
- precision highp float;
33161
-
33162
- uniform sampler2D u_sourceTexture;
33163
- in vec2 v_texCoord;
33164
- out vec4 fragColor;
33165
-
33166
- void main() {
33167
- vec4 source = texture(u_sourceTexture, v_texCoord);
33168
- fragColor = vec4(source.rgb, 1.0);
33169
- }
33170
- `);
33171
- const program = this.gl.createProgram();
33172
- this.gl.attachShader(program, vertexShader);
33173
- this.gl.attachShader(program, fragmentShader);
33174
- this.gl.linkProgram(program);
33175
- return program;
33176
- }
33177
- createAlphaProgram() {
33178
- assert(this.gl);
33179
- const vertexShader = this.createVertexShader();
33180
- const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
33181
- precision highp float;
33182
-
33183
- uniform sampler2D u_sourceTexture;
33184
- uniform vec2 u_resolution; // The width and height of the canvas
33185
- in vec2 v_texCoord;
33186
- out vec4 fragColor;
33187
-
33188
- // This function determines the value for a single byte in the YUV stream
33189
- float getByteValue(float byteOffset) {
33190
- float width = u_resolution.x;
33191
- float height = u_resolution.y;
33192
-
33193
- float yPlaneSize = width * height;
33194
-
33195
- if (byteOffset < yPlaneSize) {
33196
- // This byte is in the luma plane. Find the corresponding pixel coordinates to sample from
33197
- float y = floor(byteOffset / width);
33198
- float x = mod(byteOffset, width);
33199
-
33200
- // Add 0.5 to sample the center of the texel
33201
- vec2 sampleCoord = (vec2(x, y) + 0.5) / u_resolution;
33202
-
33203
- // The luma value is the alpha from the source texture
33204
- return texture(u_sourceTexture, sampleCoord).a;
33205
- } else {
33206
- // Write a fixed value for chroma and beyond
33207
- return 128.0 / 255.0;
33208
- }
33209
- }
33210
-
33211
- void main() {
33212
- // Each fragment writes 4 bytes (R, G, B, A)
33213
- float pixelIndex = floor(gl_FragCoord.y) * u_resolution.x + floor(gl_FragCoord.x);
33214
- float baseByteOffset = pixelIndex * 4.0;
33215
-
33216
- vec4 result;
33217
- for (int i = 0; i < 4; i++) {
33218
- float currentByteOffset = baseByteOffset + float(i);
33219
- result[i] = getByteValue(currentByteOffset);
33220
- }
33221
-
33222
- fragColor = result;
33223
- }
33224
- `);
33225
- const program = this.gl.createProgram();
33226
- this.gl.attachShader(program, vertexShader);
33227
- this.gl.attachShader(program, fragmentShader);
33228
- this.gl.linkProgram(program);
33229
- return program;
33230
- }
33231
- createShader(type, source) {
33232
- assert(this.gl);
33233
- const shader = this.gl.createShader(type);
33234
- this.gl.shaderSource(shader, source);
33235
- this.gl.compileShader(shader);
33236
- if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
33237
- Logging._error("Shader compile error:", this.gl.getShaderInfoLog(shader));
33238
- }
33239
- return shader;
33240
- }
33241
- createVAO() {
33242
- assert(this.gl);
33243
- assert(this.colorProgram);
33244
- const vao = this.gl.createVertexArray();
33245
- this.gl.bindVertexArray(vao);
33246
- const vertices = new Float32Array([
33247
- -1,
33248
- -1,
33249
- 0,
33250
- 1,
33251
- 1,
33252
- -1,
33253
- 1,
33254
- 1,
33255
- -1,
33256
- 1,
33257
- 0,
33258
- 0,
33259
- 1,
33260
- 1,
33261
- 1,
33262
- 0
33263
- ]);
33264
- const buffer = this.gl.createBuffer();
33265
- this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
33266
- this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
33267
- const positionLocation = this.gl.getAttribLocation(this.colorProgram, "a_position");
33268
- const texCoordLocation = this.gl.getAttribLocation(this.colorProgram, "a_texCoord");
33269
- this.gl.enableVertexAttribArray(positionLocation);
33270
- this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 16, 0);
33271
- this.gl.enableVertexAttribArray(texCoordLocation);
33272
- this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 16, 8);
33273
- return vao;
33274
- }
33275
- createTexture() {
33276
- assert(this.gl);
33277
- const texture = this.gl.createTexture();
33278
- this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
33279
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
33280
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
33281
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
33282
- this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
33283
- return texture;
33284
- }
33285
- runColorProgram(sourceFrame) {
33286
- assert(this.gl);
33287
- assert(this.canvas);
33288
- this.gl.useProgram(this.colorProgram);
33289
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
33290
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
33291
- this.gl.bindVertexArray(this.vao);
33292
- this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
33293
- return new VideoFrame(this.canvas, {
33294
- timestamp: sourceFrame.timestamp,
33295
- duration: sourceFrame.duration ?? void 0,
33296
- alpha: "discard"
33297
- });
33298
- }
33299
- runAlphaProgram(sourceFrame) {
33300
- assert(this.gl);
33301
- assert(this.canvas);
33302
- this.gl.useProgram(this.alphaProgram);
33303
- this.gl.uniform2f(this.alphaResolutionLocation, this.canvas.width, this.canvas.height);
33304
- this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
33305
- this.gl.clear(this.gl.COLOR_BUFFER_BIT);
33306
- this.gl.bindVertexArray(this.vao);
33307
- this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
33308
- const { width, height } = this.canvas;
33309
- const chromaSamples = Math.ceil(width / 2) * Math.ceil(height / 2);
33310
- const yuvSize = width * height + chromaSamples * 2;
33311
- const requiredHeight = Math.ceil(yuvSize / (width * 4));
33312
- let yuv = new Uint8Array(4 * width * requiredHeight);
33313
- this.gl.readPixels(0, 0, width, requiredHeight, this.gl.RGBA, this.gl.UNSIGNED_BYTE, yuv);
33314
- yuv = yuv.subarray(0, yuvSize);
33315
- assert(yuv[width * height] === 128);
33316
- assert(yuv[yuv.length - 1] === 128);
33317
- const init = {
33318
- format: "I420",
33319
- codedWidth: width,
33320
- codedHeight: height,
33321
- timestamp: sourceFrame.timestamp,
33322
- duration: sourceFrame.duration ?? void 0,
33323
- transfer: [yuv.buffer]
33324
- };
33325
- return new VideoFrame(yuv, init);
33326
- }
33327
- updateCpu(sourceFrame) {
33064
+ split(sourceFrame) {
33328
33065
  if (!this.worker) {
33329
- const blob = new Blob(
33330
- [`(${colorAlphaSplitterWorkerCode.toString()})()`],
33331
- { type: "application/javascript" }
33332
- );
33333
- const url2 = URL.createObjectURL(blob);
33334
- this.worker = new Worker(url2);
33335
- URL.revokeObjectURL(url2);
33066
+ if (!splitterWorkerUrl) {
33067
+ const blob = new Blob(
33068
+ [`(${colorAlphaSplitterWorkerCode.toString()})()`],
33069
+ { type: "application/javascript" }
33070
+ );
33071
+ splitterWorkerUrl = URL.createObjectURL(blob);
33072
+ }
33073
+ this.worker = new Worker(splitterWorkerUrl);
33336
33074
  this.worker.addEventListener("message", (event) => {
33337
33075
  const data = event.data;
33338
33076
  const pending2 = this.pendingRequests.get(data.id);
@@ -33361,9 +33099,6 @@ ${cue.notes ?? ""}`;
33361
33099
  return pending.promise;
33362
33100
  }
33363
33101
  close() {
33364
- this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
33365
- this.gl = null;
33366
- this.canvas = null;
33367
33102
  this.worker?.terminate();
33368
33103
  this.worker = null;
33369
33104
  const error = new Error("Color/alpha splitter closed.");
@@ -33373,8 +33108,6 @@ ${cue.notes ?? ""}`;
33373
33108
  this.pendingRequests.clear();
33374
33109
  }
33375
33110
  };
33376
- _ColorAlphaSplitter.forceCpu = true;
33377
- var ColorAlphaSplitter = _ColorAlphaSplitter;
33378
33111
  var colorAlphaSplitterWorkerCode = () => {
33379
33112
  let cpuSourceBuffer = null;
33380
33113
  let chain = Promise.resolve();
@@ -33396,21 +33129,21 @@ ${cue.notes ?? ""}`;
33396
33129
  if (!format) {
33397
33130
  throw new Error("CPU color/alpha splitting requires a known VideoFrame format.");
33398
33131
  }
33399
- const width = sourceFrame.codedWidth;
33400
- const height = sourceFrame.codedHeight;
33401
33132
  const sourceSize = sourceFrame.allocationSize();
33402
33133
  if (!cpuSourceBuffer || cpuSourceBuffer.byteLength !== sourceSize) {
33403
33134
  cpuSourceBuffer = new Uint8Array(sourceSize);
33404
33135
  }
33405
33136
  await sourceFrame.copyTo(cpuSourceBuffer);
33406
33137
  if (format === "RGBA" || format === "BGRA") {
33407
- return splitInterleavedRgba(cpuSourceBuffer, width, height, format, sourceFrame);
33138
+ return splitInterleavedRgba(cpuSourceBuffer, format, sourceFrame);
33408
33139
  } else if (format === "I420A" || format === "I420AP10" || format === "I420AP12" || format === "I422A" || format === "I422AP10" || format === "I422AP12" || format === "I444A" || format === "I444AP10" || format === "I444AP12") {
33409
- return splitPlanarYuvA(cpuSourceBuffer, width, height, format, sourceFrame);
33140
+ return splitPlanarYuvA(cpuSourceBuffer, format, sourceFrame);
33410
33141
  }
33411
33142
  throw new Error(`CPU color/alpha splitting does not support format '${format}'.`);
33412
33143
  };
33413
- const splitInterleavedRgba = (source, width, height, format, sourceFrame) => {
33144
+ const splitInterleavedRgba = (source, format, sourceFrame) => {
33145
+ const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
33146
+ const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
33414
33147
  const pixelCount = width * height;
33415
33148
  const chromaW = Math.ceil(width / 2);
33416
33149
  const chromaH = Math.ceil(height / 2);
@@ -33439,7 +33172,9 @@ ${cue.notes ?? ""}`;
33439
33172
  const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
33440
33173
  return { colorFrame, alphaFrame };
33441
33174
  };
33442
- const splitPlanarYuvA = (source, width, height, format, sourceFrame) => {
33175
+ const splitPlanarYuvA = (source, format, sourceFrame) => {
33176
+ const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
33177
+ const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
33443
33178
  const is10 = format.includes("P10");
33444
33179
  const is12 = format.includes("P12");
33445
33180
  const bytesPerSample = is10 || is12 ? 2 : 1;
@@ -33904,9 +33639,16 @@ ${cue.notes ?? ""}`;
33904
33639
  * So, we keep track of the encoder error and throw it as soon as we get the chance.
33905
33640
  */
33906
33641
  this.error = null;
33642
+ this.errorSet = false;
33907
33643
  this.lastMuxerPromise = Promise.resolve();
33908
33644
  this.closed = false;
33909
33645
  }
33646
+ setError(error) {
33647
+ if (!this.errorSet) {
33648
+ this.error = error;
33649
+ this.errorSet = true;
33650
+ }
33651
+ }
33910
33652
  async add(audioSample, shouldClose) {
33911
33653
  try {
33912
33654
  this.checkForEncoderError();
@@ -34028,7 +33770,8 @@ ${cue.notes ?? ""}`;
34028
33770
  if (this.customEncoder) {
34029
33771
  this.customEncoderQueueSize++;
34030
33772
  const clonedSample = audioSample.clone();
34031
- const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).then(() => this.customEncoderQueueSize--).catch((error) => this.error ??= error).finally(() => {
33773
+ const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).catch((error) => this.setError(error)).finally(() => {
33774
+ this.customEncoderQueueSize--;
34032
33775
  clonedSample.close();
34033
33776
  });
34034
33777
  if (this.customEncoderQueueSize >= 4) {
@@ -34134,9 +33877,12 @@ ${cue.notes ?? ""}`;
34134
33877
  }
34135
33878
  this.encodingConfig.onEncodedPacket?.(packet, meta);
34136
33879
  this.lastMuxerPromise = this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
34137
- this.error ??= error;
33880
+ this.setError(error);
34138
33881
  });
34139
33882
  };
33883
+ this.customEncoder.onError = (error) => {
33884
+ this.setError(error);
33885
+ };
34140
33886
  await this.customEncoder.init();
34141
33887
  } else if (PCM_AUDIO_CODECS.includes(this.encodingConfig.codec)) {
34142
33888
  this.initPcmEncoder();
@@ -34179,12 +33925,12 @@ ${cue.notes ?? ""}`;
34179
33925
  });
34180
33926
  this.encodingConfig.onEncodedPacket?.(packet, meta);
34181
33927
  this.lastMuxerPromise = this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
34182
- this.error ??= error;
33928
+ this.setError(error);
34183
33929
  });
34184
33930
  },
34185
33931
  error: (error) => {
34186
33932
  error.stack = stack;
34187
- this.error ??= error;
33933
+ this.setError(error);
34188
33934
  }
34189
33935
  });
34190
33936
  this.encoder.configure(encoderConfig);
@@ -34325,7 +34071,7 @@ ${cue.notes ?? ""}`;
34325
34071
  }
34326
34072
  }
34327
34073
  checkForEncoderError() {
34328
- if (this.error) {
34074
+ if (this.errorSet) {
34329
34075
  throw this.error;
34330
34076
  }
34331
34077
  }
@@ -34685,12 +34431,14 @@ ${cue.notes ?? ""}`;
34685
34431
  /** @internal */
34686
34432
  this._error = null;
34687
34433
  /** @internal */
34434
+ this._errorSet = false;
34435
+ /** @internal */
34688
34436
  this._lastMuxerPromise = Promise.resolve();
34689
34437
  this._parser = new SubtitleParser({
34690
34438
  codec,
34691
34439
  output: (cue, metadata) => {
34692
34440
  this._lastMuxerPromise = this._connectedTrack.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata).catch((error) => {
34693
- this._error ??= error;
34441
+ this._setError(error);
34694
34442
  });
34695
34443
  }
34696
34444
  });
@@ -34712,8 +34460,15 @@ ${cue.notes ?? ""}`;
34712
34460
  return this._lastMuxerPromise;
34713
34461
  }
34714
34462
  /** @internal */
34463
+ _setError(error) {
34464
+ if (!this._errorSet) {
34465
+ this._error = error;
34466
+ this._errorSet = true;
34467
+ }
34468
+ }
34469
+ /** @internal */
34715
34470
  _checkForError() {
34716
- if (this._error) {
34471
+ if (this._errorSet) {
34717
34472
  throw this._error;
34718
34473
  }
34719
34474
  }
@@ -37649,9 +37404,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37649
37404
  firstSample.close();
37650
37405
  await tempOutput.finalize();
37651
37406
  } catch (error) {
37652
- Logging._info("Error when probing encoder support. Falling back to rerender path.", error);
37653
- needsRerender = true;
37407
+ Logging._warn(
37408
+ "An error occurred when probing encoder support. Falling back to rerender path.",
37409
+ error
37410
+ );
37654
37411
  void tempOutput.cancel();
37412
+ needsRerender = true;
37413
+ encodingConfig.transform.force = true;
37655
37414
  }
37656
37415
  } else {
37657
37416
  await tempOutput.cancel();
@@ -37739,7 +37498,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37739
37498
  let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
37740
37499
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
37741
37500
  const needsTrimming = firstTimestamp < this._startTimestamp;
37742
- const needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
37501
+ let needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
37743
37502
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
37744
37503
  if (!trackOptions.forceTranscode && !trackOptions.bitrate && numberOfChannels === originalNumberOfChannels && sampleRate === originalSampleRate && !needsTrimming && !needsPadding && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
37745
37504
  const source = new EncodedAudioPacketSource(sourceCodec);
@@ -37835,24 +37594,32 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37835
37594
  audioSource = source;
37836
37595
  this._trackPromises.push((async () => {
37837
37596
  await this._started;
37838
- if (needsPadding) {
37839
- const paddingLength = firstTimestamp - this._startTimestamp;
37840
- const paddingLengthSamples = Math.round(paddingLength * originalSampleRate);
37841
- const silentSample = new AudioSample({
37842
- data: new Float32Array(paddingLengthSamples * originalNumberOfChannels),
37843
- format: "f32-planar",
37844
- numberOfChannels: originalNumberOfChannels,
37845
- sampleRate: originalSampleRate,
37846
- timestamp: 0
37847
- });
37848
- await this._registerAudioSample(silentSample, source, outputTrackId, () => lastSampleTimestamp);
37849
- }
37850
37597
  const sink = new AudioSampleSink(track);
37851
37598
  for await (let sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
37852
37599
  if (this._canceled) {
37853
37600
  sample.close();
37854
37601
  return;
37855
37602
  }
37603
+ if (needsPadding) {
37604
+ const paddingLength = firstTimestamp - this._startTimestamp;
37605
+ const paddingLengthSamples = Math.round(paddingLength * originalSampleRate);
37606
+ const bytesPerSample = getBytesPerSample(sample.format);
37607
+ const data = new Uint8Array(bytesPerSample * paddingLengthSamples * originalNumberOfChannels);
37608
+ if (sample.format === "u8" || sample.format === "u8-planar") {
37609
+ data.fill(2 ** 7);
37610
+ }
37611
+ const silentSample = new AudioSample({
37612
+ data,
37613
+ // Use the same format the decoder is spitting out. This avoids feeding changing sample
37614
+ // formats to the audio encoder.
37615
+ format: sample.format,
37616
+ numberOfChannels: originalNumberOfChannels,
37617
+ sampleRate: originalSampleRate,
37618
+ timestamp: 0
37619
+ });
37620
+ await this._registerAudioSample(silentSample, source, outputTrackId, () => lastSampleTimestamp);
37621
+ needsPadding = false;
37622
+ }
37856
37623
  let startFrame = 0;
37857
37624
  let endFrame = sample.numberOfFrames;
37858
37625
  if (sample.timestamp < this._startTimestamp) {