mediabunny 1.24.4 → 1.24.6

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 (45) hide show
  1. package/dist/bundles/mediabunny.cjs +120 -27
  2. package/dist/bundles/mediabunny.min.cjs +7 -7
  3. package/dist/bundles/mediabunny.min.mjs +7 -7
  4. package/dist/bundles/mediabunny.mjs +120 -27
  5. package/dist/mediabunny.d.ts +6 -2
  6. package/dist/modules/src/codec.d.ts +6 -0
  7. package/dist/modules/src/codec.d.ts.map +1 -1
  8. package/dist/modules/src/codec.js +38 -2
  9. package/dist/modules/src/conversion.d.ts +2 -1
  10. package/dist/modules/src/conversion.d.ts.map +1 -1
  11. package/dist/modules/src/conversion.js +4 -1
  12. package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
  13. package/dist/modules/src/flac/flac-demuxer.js +5 -4
  14. package/dist/modules/src/isobmff/isobmff-boxes.js +9 -7
  15. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +1 -0
  16. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  17. package/dist/modules/src/isobmff/isobmff-demuxer.js +3 -1
  18. package/dist/modules/src/matroska/matroska-demuxer.js +2 -1
  19. package/dist/modules/src/media-sink.js +4 -4
  20. package/dist/modules/src/media-source.d.ts.map +1 -1
  21. package/dist/modules/src/media-source.js +45 -2
  22. package/dist/modules/src/misc.d.ts +1 -1
  23. package/dist/modules/src/misc.d.ts.map +1 -1
  24. package/dist/modules/src/misc.js +8 -11
  25. package/dist/modules/src/packet.d.ts +4 -1
  26. package/dist/modules/src/packet.d.ts.map +1 -1
  27. package/dist/modules/src/packet.js +4 -1
  28. package/dist/modules/src/sample.d.ts.map +1 -1
  29. package/dist/modules/src/sample.js +2 -1
  30. package/dist/modules/src/source.d.ts.map +1 -1
  31. package/dist/modules/src/source.js +9 -2
  32. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +1 -1
  34. package/src/codec.ts +53 -2
  35. package/src/conversion.ts +6 -2
  36. package/src/flac/flac-demuxer.ts +6 -4
  37. package/src/isobmff/isobmff-boxes.ts +9 -7
  38. package/src/isobmff/isobmff-demuxer.ts +4 -1
  39. package/src/matroska/matroska-demuxer.ts +3 -2
  40. package/src/media-sink.ts +4 -4
  41. package/src/media-source.ts +61 -0
  42. package/src/misc.ts +9 -14
  43. package/src/packet.ts +4 -1
  44. package/src/sample.ts +4 -1
  45. package/src/source.ts +9 -1
@@ -624,13 +624,13 @@ var Mediabunny = (() => {
624
624
  return this.currentPromise = this.currentPromise.then(fn);
625
625
  }
626
626
  };
627
- var isSafariCache = null;
628
- var isSafari = () => {
629
- if (isSafariCache !== null) {
630
- return isSafariCache;
627
+ var isWebKitCache = null;
628
+ var isWebKit = () => {
629
+ if (isWebKitCache !== null) {
630
+ return isWebKitCache;
631
631
  }
632
- const result = !!(typeof navigator !== "undefined" && navigator.vendor?.match(/apple/i) && !navigator.userAgent?.match(/crios/i) && !navigator.userAgent?.match(/fxios/i) && !navigator.userAgent?.match(/Opera|OPT\//));
633
- isSafariCache = result;
632
+ const result = !!(typeof navigator !== "undefined" && navigator.vendor?.match(/apple/i));
633
+ isWebKitCache = result;
634
634
  return result;
635
635
  };
636
636
  var isFirefoxCache = null;
@@ -1123,18 +1123,19 @@ var Mediabunny = (() => {
1123
1123
  var extractVideoCodecString = (trackInfo) => {
1124
1124
  const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
1125
1125
  if (codec === "avc") {
1126
+ assert(trackInfo.avcType !== null);
1126
1127
  if (avcCodecInfo) {
1127
1128
  const bytes2 = new Uint8Array([
1128
1129
  avcCodecInfo.avcProfileIndication,
1129
1130
  avcCodecInfo.profileCompatibility,
1130
1131
  avcCodecInfo.avcLevelIndication
1131
1132
  ]);
1132
- return `avc1.${bytesToHexString(bytes2)}`;
1133
+ return `avc${trackInfo.avcType}.${bytesToHexString(bytes2)}`;
1133
1134
  }
1134
1135
  if (!codecDescription || codecDescription.byteLength < 4) {
1135
1136
  throw new TypeError("AVC decoder description is not provided or is not at least 4 bytes long.");
1136
1137
  }
1137
- return `avc1.${bytesToHexString(codecDescription.subarray(1, 4))}`;
1138
+ return `avc${trackInfo.avcType}.${bytesToHexString(codecDescription.subarray(1, 4))}`;
1138
1139
  } else if (codec === "hevc") {
1139
1140
  let generalProfileSpace;
1140
1141
  let generalProfileIdc;
@@ -1337,6 +1338,40 @@ var Mediabunny = (() => {
1337
1338
  numberOfChannels
1338
1339
  };
1339
1340
  };
1341
+ var buildAacAudioSpecificConfig = (config) => {
1342
+ let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
1343
+ let customSampleRate = null;
1344
+ if (frequencyIndex === -1) {
1345
+ frequencyIndex = 15;
1346
+ customSampleRate = config.sampleRate;
1347
+ }
1348
+ const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
1349
+ if (channelConfiguration === -1) {
1350
+ throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
1351
+ }
1352
+ let bitCount = 5 + 4 + 4;
1353
+ if (config.objectType >= 32) {
1354
+ bitCount += 6;
1355
+ }
1356
+ if (frequencyIndex === 15) {
1357
+ bitCount += 24;
1358
+ }
1359
+ const byteCount = Math.ceil(bitCount / 8);
1360
+ const bytes2 = new Uint8Array(byteCount);
1361
+ const bitstream = new Bitstream(bytes2);
1362
+ if (config.objectType < 32) {
1363
+ bitstream.writeBits(5, config.objectType);
1364
+ } else {
1365
+ bitstream.writeBits(5, 31);
1366
+ bitstream.writeBits(6, config.objectType - 32);
1367
+ }
1368
+ bitstream.writeBits(4, frequencyIndex);
1369
+ if (frequencyIndex === 15) {
1370
+ bitstream.writeBits(24, customSampleRate);
1371
+ }
1372
+ bitstream.writeBits(4, channelConfiguration);
1373
+ return bytes2;
1374
+ };
1340
1375
  var OPUS_SAMPLE_RATE = 48e3;
1341
1376
  var PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
1342
1377
  var parsePcmCodec = (codec) => {
@@ -3312,7 +3347,10 @@ var Mediabunny = (() => {
3312
3347
  this.sideData.alphaByteLength = this.sideData.alpha.byteLength;
3313
3348
  }
3314
3349
  }
3315
- /** If this packet is a metadata-only packet. Metadata-only packets don't contain their packet data. */
3350
+ /**
3351
+ * If this packet is a metadata-only packet. Metadata-only packets don't contain their packet data. They are the
3352
+ * result of retrieving packets with {@link PacketRetrievalOptions.metadataOnly} set to `true`.
3353
+ */
3316
3354
  get isMetadataOnly() {
3317
3355
  return this.data === PLACEHOLDER_DATA;
3318
3356
  }
@@ -3890,12 +3928,14 @@ var Mediabunny = (() => {
3890
3928
  dx = (canvasWidth - newWidth) / 2;
3891
3929
  dy = (canvasHeight - newHeight) / 2;
3892
3930
  }
3931
+ context.save();
3893
3932
  const aspectRatioChange = rotation % 180 === 0 ? 1 : newWidth / newHeight;
3894
3933
  context.translate(canvasWidth / 2, canvasHeight / 2);
3895
3934
  context.rotate(rotation * Math.PI / 180);
3896
3935
  context.scale(1 / aspectRatioChange, aspectRatioChange);
3897
3936
  context.translate(-canvasWidth / 2, -canvasHeight / 2);
3898
3937
  context.drawImage(this.toCanvasImageSource(), sx, sy, sWidth, sHeight, dx, dy, newWidth, newHeight);
3938
+ context.restore();
3899
3939
  }
3900
3940
  /** @internal */
3901
3941
  _rotateSourceRegion(sx, sy, sWidth, sHeight, rotation) {
@@ -5123,7 +5163,7 @@ var Mediabunny = (() => {
5123
5163
  void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).then(() => this.customDecoderQueueSize--);
5124
5164
  } else {
5125
5165
  assert(this.decoder);
5126
- if (!isSafari()) {
5166
+ if (!isWebKit()) {
5127
5167
  insertSorted(this.inputTimestamps, packet.timestamp, (x) => x);
5128
5168
  }
5129
5169
  this.decoder.decode(packet.toEncodedVideoChunk());
@@ -5220,7 +5260,7 @@ var Mediabunny = (() => {
5220
5260
  }
5221
5261
  /** Handler for the WebCodecs VideoDecoder for ironing out browser differences. */
5222
5262
  sampleHandler(sample) {
5223
- if (isSafari()) {
5263
+ if (isWebKit()) {
5224
5264
  if (this.sampleQueue.length > 0 && sample.timestamp >= last(this.sampleQueue).timestamp) {
5225
5265
  for (const sample2 of this.sampleQueue) {
5226
5266
  this.finalizeAndEmitSample(sample2);
@@ -5278,7 +5318,7 @@ var Mediabunny = (() => {
5278
5318
  this.currentAlphaPacketIndex = 0;
5279
5319
  this.alphaRaslSkipped = false;
5280
5320
  }
5281
- if (isSafari()) {
5321
+ if (isWebKit()) {
5282
5322
  for (const sample of this.sampleQueue) {
5283
5323
  this.finalizeAndEmitSample(sample);
5284
5324
  }
@@ -6841,6 +6881,7 @@ var Mediabunny = (() => {
6841
6881
  codec: null,
6842
6882
  codecDescription: null,
6843
6883
  colorSpace: null,
6884
+ avcType: null,
6844
6885
  avcCodecInfo: null,
6845
6886
  hevcCodecInfo: null,
6846
6887
  vp9CodecInfo: null,
@@ -6891,8 +6932,9 @@ var Mediabunny = (() => {
6891
6932
  track.internalCodecId = sampleBoxInfo.name;
6892
6933
  const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
6893
6934
  if (track.info.type === "video") {
6894
- if (lowercaseBoxName === "avc1") {
6935
+ if (lowercaseBoxName === "avc1" || lowercaseBoxName === "avc3") {
6895
6936
  track.info.codec = "avc";
6937
+ track.info.avcType = lowercaseBoxName === "avc1" ? 1 : 3;
6896
6938
  } else if (lowercaseBoxName === "hvc1" || lowercaseBoxName === "hev1") {
6897
6939
  track.info.codec = "hevc";
6898
6940
  } else if (lowercaseBoxName === "vp08") {
@@ -9645,10 +9687,10 @@ var Mediabunny = (() => {
9645
9687
  }
9646
9688
  assert(frameSizes.length === frameCount);
9647
9689
  blocks.splice(blockIndex, 1);
9690
+ const blockDuration = originalBlock.duration || frameCount * (track.defaultDuration ?? 0);
9648
9691
  for (let i = 0; i < frameCount; i++) {
9649
9692
  const frameSize = frameSizes[i];
9650
9693
  const frameData = readBytes(slice, frameSize);
9651
- const blockDuration = originalBlock.duration || frameCount * (track.defaultDuration ?? 0);
9652
9694
  const frameTimestamp = originalBlock.timestamp + blockDuration * i / frameCount;
9653
9695
  const frameDuration = blockDuration / frameCount;
9654
9696
  blocks.splice(blockIndex + i, 0, {
@@ -10996,6 +11038,8 @@ var Mediabunny = (() => {
10996
11038
  codec: this.internalTrack.info.codec,
10997
11039
  codecDescription: this.internalTrack.info.codecDescription,
10998
11040
  colorSpace: this.internalTrack.info.colorSpace,
11041
+ avcType: 1,
11042
+ // We don't know better (or do we?) so just assume 'avc1'
10999
11043
  avcCodecInfo: this.internalTrack.info.codec === "avc" && firstPacket ? extractAvcDecoderConfigurationRecord(firstPacket.data) : null,
11000
11044
  hevcCodecInfo: this.internalTrack.info.codec === "hevc" && firstPacket ? extractHevcDecoderConfigurationRecord(firstPacket.data) : null,
11001
11045
  vp9CodecInfo: this.internalTrack.info.codec === "vp9" && firstPacket ? extractVp9CodecInfoFromPacket(firstPacket.data) : null,
@@ -14379,10 +14423,11 @@ var Mediabunny = (() => {
14379
14423
  }
14380
14424
  const nextByte = readU8(slice);
14381
14425
  if (nextByte === 255) {
14426
+ const positionBeforeReading = slice.filePos;
14382
14427
  const byteAfterNextByte = readU8(slice);
14383
14428
  const expected = this.blockingBit === 1 ? 249 : 248;
14384
14429
  if (byteAfterNextByte !== expected) {
14385
- slice.skip(-1);
14430
+ slice.filePos = positionBeforeReading;
14386
14431
  continue;
14387
14432
  }
14388
14433
  slice.skip(-2);
@@ -14392,17 +14437,17 @@ var Mediabunny = (() => {
14392
14437
  isFirstPacket: false
14393
14438
  });
14394
14439
  if (!nextFrameHeader) {
14395
- slice.skip(-1);
14440
+ slice.filePos = positionBeforeReading;
14396
14441
  continue;
14397
14442
  }
14398
14443
  if (this.blockingBit === 0) {
14399
14444
  if (nextFrameHeader.num - frameHeader.num !== 1) {
14400
- slice.skip(-1);
14445
+ slice.filePos = positionBeforeReading;
14401
14446
  continue;
14402
14447
  }
14403
14448
  } else {
14404
14449
  if (nextFrameHeader.num - frameHeader.num !== frameHeader.blockSize) {
14405
- slice.skip(-1);
14450
+ slice.filePos = positionBeforeReading;
14406
14451
  continue;
14407
14452
  }
14408
14453
  }
@@ -15068,7 +15113,7 @@ var Mediabunny = (() => {
15068
15113
  async _runWorker(worker) {
15069
15114
  let reader = this._readers.get(worker);
15070
15115
  if (reader === void 0) {
15071
- if ("stream" in this._blob) {
15116
+ if ("stream" in this._blob && !isWebKit()) {
15072
15117
  const slice = this._blob.slice(worker.currentPos);
15073
15118
  reader = slice.stream().getReader();
15074
15119
  } else {
@@ -17092,7 +17137,7 @@ var Mediabunny = (() => {
17092
17137
  let sampleDescription;
17093
17138
  if (trackData.type === "video") {
17094
17139
  sampleDescription = videoSampleDescription(
17095
- VIDEO_CODEC_TO_BOX_NAME[trackData.track.source._codec],
17140
+ videoCodecToBoxName(trackData.track.source._codec, trackData.info.decoderConfig.codec),
17096
17141
  trackData
17097
17142
  );
17098
17143
  } else if (trackData.type === "audio") {
@@ -18048,12 +18093,19 @@ var Mediabunny = (() => {
18048
18093
  ...textEncoder.encode(value)
18049
18094
  ]);
18050
18095
  };
18051
- var VIDEO_CODEC_TO_BOX_NAME = {
18052
- avc: "avc1",
18053
- hevc: "hvc1",
18054
- vp8: "vp08",
18055
- vp9: "vp09",
18056
- av1: "av01"
18096
+ var videoCodecToBoxName = (codec, fullCodecString) => {
18097
+ switch (codec) {
18098
+ case "avc":
18099
+ return fullCodecString.startsWith("avc3") ? "avc3" : "avc1";
18100
+ case "hevc":
18101
+ return "hvc1";
18102
+ case "vp8":
18103
+ return "vp08";
18104
+ case "vp9":
18105
+ return "vp09";
18106
+ case "av1":
18107
+ return "av01";
18108
+ }
18057
18109
  };
18058
18110
  var VIDEO_CODEC_TO_CONFIGURATION_BOX = {
18059
18111
  avc: avcC,
@@ -22972,6 +23024,7 @@ ${cue.notes ?? ""}`;
22972
23024
  this.customEncoder = null;
22973
23025
  this.customEncoderCallSerializer = new CallSerializer();
22974
23026
  this.customEncoderQueueSize = 0;
23027
+ this.lastEndSampleIndex = null;
22975
23028
  /**
22976
23029
  * Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context.
22977
23030
  * However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught.
@@ -23003,6 +23056,27 @@ ${cue.notes ?? ""}`;
23003
23056
  }
23004
23057
  }
23005
23058
  assert(this.encoderInitialized);
23059
+ {
23060
+ const startSampleIndex = Math.round(
23061
+ audioSample.timestamp * audioSample.sampleRate
23062
+ );
23063
+ const endSampleIndex = Math.round(
23064
+ (audioSample.timestamp + audioSample.duration) * audioSample.sampleRate
23065
+ );
23066
+ if (this.lastEndSampleIndex !== null && startSampleIndex > this.lastEndSampleIndex) {
23067
+ const sampleCount = startSampleIndex - this.lastEndSampleIndex;
23068
+ const fillSample = new AudioSample({
23069
+ data: new Float32Array(sampleCount * audioSample.numberOfChannels),
23070
+ format: "f32-planar",
23071
+ sampleRate: audioSample.sampleRate,
23072
+ numberOfChannels: audioSample.numberOfChannels,
23073
+ numberOfFrames: sampleCount,
23074
+ timestamp: this.lastEndSampleIndex / audioSample.sampleRate
23075
+ });
23076
+ await this.add(fillSample, true);
23077
+ }
23078
+ this.lastEndSampleIndex = endSampleIndex;
23079
+ }
23006
23080
  if (this.customEncoder) {
23007
23081
  this.customEncoderQueueSize++;
23008
23082
  const clonedSample = audioSample.clone();
@@ -23132,6 +23206,25 @@ ${cue.notes ?? ""}`;
23132
23206
  }
23133
23207
  this.encoder = new AudioEncoder({
23134
23208
  output: (chunk, meta) => {
23209
+ if (this.encodingConfig.codec === "aac" && meta?.decoderConfig) {
23210
+ let needsDescriptionOverwrite = false;
23211
+ if (!meta.decoderConfig.description || meta.decoderConfig.description.byteLength < 2) {
23212
+ needsDescriptionOverwrite = true;
23213
+ } else {
23214
+ const audioSpecificConfig = parseAacAudioSpecificConfig(
23215
+ toUint8Array(meta.decoderConfig.description)
23216
+ );
23217
+ needsDescriptionOverwrite = audioSpecificConfig.objectType === 0;
23218
+ }
23219
+ if (needsDescriptionOverwrite) {
23220
+ const objectType = Number(last(encoderConfig.codec.split(".")));
23221
+ meta.decoderConfig.description = buildAacAudioSpecificConfig({
23222
+ objectType,
23223
+ numberOfChannels: meta.decoderConfig.numberOfChannels,
23224
+ sampleRate: meta.decoderConfig.sampleRate
23225
+ });
23226
+ }
23227
+ }
23135
23228
  const packet = EncodedPacket.fromEncodedChunk(chunk);
23136
23229
  this.encodingConfig.onEncodedPacket?.(packet, meta);
23137
23230
  void this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
@@ -24330,7 +24423,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
24330
24423
  }
24331
24424
  const firstTimestamp = await track.getFirstTimestamp();
24332
24425
  const needsTranscode = !!trackOptions.forceTranscode || this._startTimestamp > 0 || firstTimestamp < 0 || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0;
24333
- let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !outputSupportsRotation || !!crop;
24426
+ let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && (!outputSupportsRotation || trackOptions.process !== void 0) || !!crop;
24334
24427
  const alpha = trackOptions.alpha ?? "discard";
24335
24428
  let videoCodecs = this.output.format.getSupportedVideoCodecs();
24336
24429
  if (!needsTranscode && !trackOptions.bitrate && !needsRerender && videoCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec)) {