mediabunny 1.42.0 → 1.43.0

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 (41) hide show
  1. package/dist/bundles/mediabunny.cjs +242 -44
  2. package/dist/bundles/mediabunny.min.cjs +14 -14
  3. package/dist/bundles/mediabunny.min.mjs +14 -14
  4. package/dist/bundles/mediabunny.mjs +242 -44
  5. package/dist/bundles/mediabunny.node.cjs +242 -44
  6. package/dist/mediabunny.d.ts +12 -0
  7. package/dist/modules/src/codec-data.d.ts +2 -0
  8. package/dist/modules/src/codec-data.d.ts.map +1 -1
  9. package/dist/modules/src/codec-data.js +103 -0
  10. package/dist/modules/src/conversion.d.ts +7 -0
  11. package/dist/modules/src/conversion.d.ts.map +1 -1
  12. package/dist/modules/src/conversion.js +25 -11
  13. package/dist/modules/src/encode.d.ts +5 -0
  14. package/dist/modules/src/encode.d.ts.map +1 -1
  15. package/dist/modules/src/encode.js +5 -1
  16. package/dist/modules/src/index.d.ts +20 -20
  17. package/dist/modules/src/index.d.ts.map +1 -1
  18. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -0
  19. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  20. package/dist/modules/src/isobmff/isobmff-boxes.js +36 -4
  21. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +4 -2
  22. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  23. package/dist/modules/src/isobmff/isobmff-muxer.js +47 -21
  24. package/dist/modules/src/media-sink.d.ts.map +1 -1
  25. package/dist/modules/src/media-sink.js +20 -11
  26. package/dist/modules/src/media-source.d.ts.map +1 -1
  27. package/dist/modules/src/media-source.js +14 -1
  28. package/dist/modules/src/sample.d.ts +2 -0
  29. package/dist/modules/src/sample.d.ts.map +1 -1
  30. package/dist/modules/src/sample.js +27 -0
  31. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +1 -1
  33. package/src/codec-data.ts +112 -0
  34. package/src/conversion.ts +45 -11
  35. package/src/encode.ts +12 -1
  36. package/src/index.ts +82 -82
  37. package/src/isobmff/isobmff-boxes.ts +46 -8
  38. package/src/isobmff/isobmff-muxer.ts +71 -28
  39. package/src/media-sink.ts +20 -11
  40. package/src/media-source.ts +26 -1
  41. package/src/sample.ts +30 -0
@@ -3064,6 +3064,16 @@ var Mediabunny = (() => {
3064
3064
  bitstream.skipBits(5);
3065
3065
  bitstream.skipBits(5);
3066
3066
  };
3067
+ var concatHevcNalUnits = (nalUnits, decoderConfig) => {
3068
+ if (decoderConfig.description) {
3069
+ const bytes2 = toUint8Array(decoderConfig.description);
3070
+ const lengthSizeMinusOne = bytes2[21] & 3;
3071
+ const lengthSize = lengthSizeMinusOne + 1;
3072
+ return concatNalUnitsInLengthPrefixed(nalUnits, lengthSize);
3073
+ } else {
3074
+ return concatNalUnitsInAnnexB(nalUnits);
3075
+ }
3076
+ };
3067
3077
  var iterateHevcNalUnits = (packetData, decoderConfig) => {
3068
3078
  if (decoderConfig.description) {
3069
3079
  const bytes2 = toUint8Array(decoderConfig.description);
@@ -3663,6 +3673,70 @@ var Mediabunny = (() => {
3663
3673
  return null;
3664
3674
  }
3665
3675
  };
3676
+ var sanitizeHevcPacketForChromium = (packetData, decoderConfig) => {
3677
+ const removedNalUnits = /* @__PURE__ */ new Set();
3678
+ let orderState = 0 /* audAllowed */;
3679
+ for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) {
3680
+ if (orderState === 4 /* noMoreDataAllowed */) {
3681
+ removedNalUnits.add(loc.offset);
3682
+ continue;
3683
+ }
3684
+ const type = extractNalUnitTypeForHevc(packetData[loc.offset]);
3685
+ if (orderState === 3 /* eoBitstreamAllowed */ && type !== 37) {
3686
+ removedNalUnits.add(loc.offset);
3687
+ continue;
3688
+ }
3689
+ let remove = false;
3690
+ if (type === 35) {
3691
+ if (orderState > 0 /* audAllowed */) {
3692
+ remove = true;
3693
+ } else {
3694
+ orderState = 1 /* beforeFirstVcl */;
3695
+ }
3696
+ } else if (type <= 31) {
3697
+ if (orderState > 2 /* afterFirstVcl */) {
3698
+ remove = true;
3699
+ } else {
3700
+ orderState = 2 /* afterFirstVcl */;
3701
+ }
3702
+ } else if (type === 36) {
3703
+ if (orderState !== 2 /* afterFirstVcl */) {
3704
+ remove = true;
3705
+ } else {
3706
+ orderState = 3 /* eoBitstreamAllowed */;
3707
+ }
3708
+ } else if (type === 37) {
3709
+ if (orderState < 2 /* afterFirstVcl */) {
3710
+ remove = true;
3711
+ } else {
3712
+ orderState = 4 /* noMoreDataAllowed */;
3713
+ }
3714
+ } else if (type === 32 || type === 33 || type === 34 || type === 39 || type >= 41 && type <= 44 || type >= 48 && type <= 55) {
3715
+ if (orderState > 1 /* beforeFirstVcl */) {
3716
+ remove = true;
3717
+ } else {
3718
+ orderState = 1 /* beforeFirstVcl */;
3719
+ }
3720
+ } else if (type === 38 || type === 40 || type >= 45 && type <= 47 || type >= 56 && type <= 63) {
3721
+ if (orderState < 2 /* afterFirstVcl */) {
3722
+ remove = true;
3723
+ }
3724
+ }
3725
+ if (remove) {
3726
+ removedNalUnits.add(loc.offset);
3727
+ }
3728
+ }
3729
+ if (removedNalUnits.size === 0) {
3730
+ return null;
3731
+ }
3732
+ const filteredNalUnits = [];
3733
+ for (const loc of iterateHevcNalUnits(packetData, decoderConfig)) {
3734
+ if (!removedNalUnits.has(loc.offset)) {
3735
+ filteredNalUnits.push(packetData.subarray(loc.offset, loc.offset + loc.length));
3736
+ }
3737
+ }
3738
+ return concatHevcNalUnits(filteredNalUnits, decoderConfig);
3739
+ };
3666
3740
  var extractVp9CodecInfoFromPacket = (packet) => {
3667
3741
  const bitstream = new Bitstream(packet);
3668
3742
  const frameMarker = bitstream.readBits(2);
@@ -20021,6 +20095,20 @@ var Mediabunny = (() => {
20021
20095
  var isAudioData = (x) => {
20022
20096
  return typeof AudioData !== "undefined" && x instanceof AudioData;
20023
20097
  };
20098
+ var toInterleavedAudioFormat = (format) => {
20099
+ switch (format) {
20100
+ case "u8-planar":
20101
+ return "u8";
20102
+ case "s16-planar":
20103
+ return "s16";
20104
+ case "s32-planar":
20105
+ return "s32";
20106
+ case "f32-planar":
20107
+ return "f32";
20108
+ default:
20109
+ return format;
20110
+ }
20111
+ };
20024
20112
  var doAudioDataCopyToWebKitWorkaround = (audioData, destView, srcFormat, destFormat, numChannels, planeIndex, frameOffset, copyFrameCount) => {
20025
20113
  const readFn = getReadFunction(srcFormat);
20026
20114
  const writeFn = getWriteFunction(destFormat);
@@ -20100,6 +20188,19 @@ var Mediabunny = (() => {
20100
20188
  }
20101
20189
  }
20102
20190
  };
20191
+ var audioSampleToInterleavedFormat = (sample, format) => {
20192
+ const size = sample.allocationSize({ format, planeIndex: 0 });
20193
+ const buffer = new ArrayBuffer(size);
20194
+ sample.copyTo(buffer, { format, planeIndex: 0 });
20195
+ return new AudioSample({
20196
+ data: buffer,
20197
+ format,
20198
+ numberOfChannels: sample.numberOfChannels,
20199
+ sampleRate: sample.sampleRate,
20200
+ timestamp: sample.timestamp,
20201
+ duration: sample.duration
20202
+ });
20203
+ };
20103
20204
 
20104
20205
  // src/encode.ts
20105
20206
  var canEncodeVideoMemo = /* @__PURE__ */ new Map();
@@ -20233,7 +20334,7 @@ var Mediabunny = (() => {
20233
20334
  if (!AUDIO_CODECS.includes(config.codec)) {
20234
20335
  throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
20235
20336
  }
20236
- if (config.bitrate === void 0 && (!PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
20337
+ if (config.bitrate === void 0 && !(PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
20237
20338
  throw new TypeError("config.bitrate must be provided for compressed audio codecs.");
20238
20339
  }
20239
20340
  if (config.bitrate !== void 0 && !(config.bitrate instanceof Quality) && (!Number.isInteger(config.bitrate) || config.bitrate <= 0)) {
@@ -20249,6 +20350,9 @@ var Mediabunny = (() => {
20249
20350
  if (config.transform.sampleRate !== void 0 && (!Number.isInteger(config.transform.sampleRate) || config.transform.sampleRate <= 0)) {
20250
20351
  throw new TypeError("config.transform.sampleRate, when provided, must be a positive integer.");
20251
20352
  }
20353
+ if (config.transform.sampleFormat !== void 0 && !["u8", "s16", "s32", "f32"].includes(config.transform.sampleFormat)) {
20354
+ throw new TypeError("config.transform.sampleFormat, when provided, must be one of: u8, s16, s32, f32.");
20355
+ }
20252
20356
  if (config.transform.process !== void 0 && typeof config.transform.process !== "function") {
20253
20357
  throw new TypeError("config.transform.process, when provided, must be a function.");
20254
20358
  }
@@ -21389,16 +21493,23 @@ var Mediabunny = (() => {
21389
21493
  if (!isWebKit()) {
21390
21494
  insertSorted(this.inputTimestamps, packet.timestamp, (x) => x);
21391
21495
  }
21392
- if (isChromium() && this.currentPacketIndex === 0 && this.codec === "avc") {
21393
- const filteredNalUnits = [];
21394
- for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
21395
- const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
21396
- if (!(type >= 20 && type <= 31)) {
21397
- filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
21496
+ if (isChromium() && this.currentPacketIndex === 0) {
21497
+ if (this.codec === "avc") {
21498
+ const filteredNalUnits = [];
21499
+ for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
21500
+ const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
21501
+ if (!(type >= 20 && type <= 31)) {
21502
+ filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
21503
+ }
21504
+ }
21505
+ const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
21506
+ packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
21507
+ } else if (this.codec === "hevc") {
21508
+ const sanitizedData = sanitizeHevcPacketForChromium(packet.data, this.decoderConfig);
21509
+ if (sanitizedData) {
21510
+ packet = new EncodedPacket(sanitizedData, packet.type, packet.timestamp, packet.duration);
21398
21511
  }
21399
21512
  }
21400
- const newData = concatAvcNalUnits(filteredNalUnits, this.decoderConfig);
21401
- packet = new EncodedPacket(newData, packet.type, packet.timestamp, packet.duration);
21402
21513
  }
21403
21514
  this.decoder.decode(packet.toEncodedVideoChunk());
21404
21515
  this.decodeAlphaData(packet);
@@ -25266,6 +25377,11 @@ var Mediabunny = (() => {
25266
25377
  view.setUint32(4, value, false);
25267
25378
  return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
25268
25379
  };
25380
+ var i64 = (value) => {
25381
+ view.setInt32(0, Math.floor(value / 2 ** 32), false);
25382
+ view.setUint32(4, value, false);
25383
+ return [bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
25384
+ };
25269
25385
  var fixed_8_8 = (value) => {
25270
25386
  view.setInt16(0, 2 ** 8 * value, false);
25271
25387
  return [bytes[0], bytes[1]];
@@ -25436,10 +25552,10 @@ var Mediabunny = (() => {
25436
25552
  udta(muxer)
25437
25553
  ]);
25438
25554
  var mvhd = (creationTime, trackDatas) => {
25439
- const duration = intoTimescale(Math.max(
25555
+ const duration = Math.max(
25440
25556
  0,
25441
- ...trackDatas.map((x) => presentationSpan(x))
25442
- ), GLOBAL_TIMESCALE);
25557
+ ...trackDatas.map((trackData) => intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE))
25558
+ );
25443
25559
  const nextTrackId = Math.max(0, ...trackDatas.map((x) => x.track.id)) + 1;
25444
25560
  const needsU64 = !isU32(creationTime) || !isU32(duration);
25445
25561
  const u32OrU64 = needsU64 ? u64 : u32;
@@ -25472,7 +25588,8 @@ var Mediabunny = (() => {
25472
25588
  }
25473
25589
  let minTimestamp = Infinity;
25474
25590
  let maxEndTimestamp = -Infinity;
25475
- for (const sample of trackData.samples) {
25591
+ for (let i = 0; i < trackData.samples.length; i++) {
25592
+ const sample = trackData.samples[i];
25476
25593
  if (sample.timestamp < minTimestamp) {
25477
25594
  minTimestamp = sample.timestamp;
25478
25595
  }
@@ -25487,8 +25604,10 @@ var Mediabunny = (() => {
25487
25604
  };
25488
25605
  var trak = (trackData, creationTime) => {
25489
25606
  const trackMetadata = getTrackMetadata(trackData);
25607
+ const needsEditList = trackData.startTimestampOffset !== null && trackData.startTimestampOffset > 0;
25490
25608
  return box("trak", void 0, [
25491
25609
  tkhd(trackData, creationTime),
25610
+ needsEditList ? edts(trackData, trackData.startTimestampOffset) : null,
25492
25611
  mdia(trackData, creationTime),
25493
25612
  trackMetadata.name !== void 0 ? box("udta", void 0, [
25494
25613
  box("name", [
@@ -25499,10 +25618,7 @@ var Mediabunny = (() => {
25499
25618
  ]);
25500
25619
  };
25501
25620
  var tkhd = (trackData, creationTime) => {
25502
- const durationInGlobalTimescale = intoTimescale(
25503
- presentationSpan(trackData),
25504
- GLOBAL_TIMESCALE
25505
- );
25621
+ const durationInGlobalTimescale = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE) + intoTimescale(trackData.startTimestampOffset ?? 0, GLOBAL_TIMESCALE);
25506
25622
  const needsU64 = !isU32(creationTime) || !isU32(durationInGlobalTimescale);
25507
25623
  const u32OrU64 = needsU64 ? u64 : u32;
25508
25624
  let matrix;
@@ -25545,6 +25661,33 @@ var Mediabunny = (() => {
25545
25661
  // Track height
25546
25662
  ]);
25547
25663
  };
25664
+ var edts = (trackData, offset) => {
25665
+ const startOffset = intoTimescale(offset, GLOBAL_TIMESCALE);
25666
+ const mediaDuration = intoTimescale(presentationSpan(trackData), GLOBAL_TIMESCALE);
25667
+ const needs64Bits = !isU32(startOffset) || !isU32(mediaDuration);
25668
+ const u32OrU64 = needs64Bits ? u64 : u32;
25669
+ const i32OrI64 = needs64Bits ? i64 : i32;
25670
+ return box("edts", void 0, [
25671
+ fullBox("elst", needs64Bits ? 1 : 0, 0, [
25672
+ u32(2),
25673
+ // Entry count
25674
+ // #1
25675
+ u32OrU64(startOffset),
25676
+ // Segment duration
25677
+ i32OrI64(-1),
25678
+ // Media time
25679
+ fixed_16_16(1),
25680
+ // Media rate
25681
+ // #2
25682
+ u32OrU64(mediaDuration),
25683
+ // Segment duration
25684
+ i32OrI64(0),
25685
+ // Media time
25686
+ fixed_16_16(1)
25687
+ // Media rate
25688
+ ])
25689
+ ]);
25690
+ };
25548
25691
  var mdia = (trackData, creationTime) => box("mdia", void 0, [
25549
25692
  mdhd(trackData, creationTime),
25550
25693
  hdlr(true, TRACK_TYPE_TO_COMPONENT_SUBTYPE[trackData.type], TRACK_TYPE_TO_HANDLER_NAME[trackData.type]),
@@ -27473,7 +27616,7 @@ var Mediabunny = (() => {
27473
27616
  };
27474
27617
 
27475
27618
  // src/isobmff/isobmff-muxer.ts
27476
- var GLOBAL_TIMESCALE = 1e3;
27619
+ var GLOBAL_TIMESCALE = 57600;
27477
27620
  var TIMESTAMP_OFFSET = 2082844800;
27478
27621
  var getTrackMetadata = (trackData) => {
27479
27622
  const metadata = {};
@@ -27641,7 +27784,10 @@ var Mediabunny = (() => {
27641
27784
  decoderConfig.description = serializeHevcDecoderConfigurationRecord(decoderConfigurationRecord);
27642
27785
  requiresAnnexBTransformation = true;
27643
27786
  }
27644
- const timescale = computeRationalApproximation(1 / (track.metadata.frameRate ?? 57600), 1e6).denominator;
27787
+ const timescale = computeRationalApproximation(
27788
+ 1 / (track.metadata.frameRate ?? GLOBAL_TIMESCALE),
27789
+ 1e6
27790
+ ).denominator;
27645
27791
  const displayAspectWidth = decoderConfig.displayAspectWidth;
27646
27792
  const displayAspectHeight = decoderConfig.displayAspectHeight;
27647
27793
  const pixelAspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? { num: 1, den: 1 } : simplifyRational({
@@ -27667,6 +27813,7 @@ var Mediabunny = (() => {
27667
27813
  compositionTimeOffsetTable: [],
27668
27814
  lastTimescaleUnits: null,
27669
27815
  lastSample: null,
27816
+ startTimestampOffset: null,
27670
27817
  finalizedChunks: [],
27671
27818
  currentChunk: null,
27672
27819
  compactlyCodedChunkTable: [],
@@ -27717,6 +27864,7 @@ var Mediabunny = (() => {
27717
27864
  sampleRate: meta.decoderConfig.sampleRate,
27718
27865
  decoderConfig,
27719
27866
  requiresPcmTransformation: !this.isFragmented && PCM_AUDIO_CODECS.includes(track.source._codec),
27867
+ expectedNextPcmPacketTimestamp: null,
27720
27868
  requiresAdtsStripping,
27721
27869
  firstPacket: packet
27722
27870
  },
@@ -27728,6 +27876,7 @@ var Mediabunny = (() => {
27728
27876
  compositionTimeOffsetTable: [],
27729
27877
  lastTimescaleUnits: null,
27730
27878
  lastSample: null,
27879
+ startTimestampOffset: null,
27731
27880
  finalizedChunks: [],
27732
27881
  currentChunk: null,
27733
27882
  compactlyCodedChunkTable: [],
@@ -27764,6 +27913,7 @@ var Mediabunny = (() => {
27764
27913
  compositionTimeOffsetTable: [],
27765
27914
  lastTimescaleUnits: null,
27766
27915
  lastSample: null,
27916
+ startTimestampOffset: null,
27767
27917
  finalizedChunks: [],
27768
27918
  currentChunk: null,
27769
27919
  compactlyCodedChunkTable: [],
@@ -27824,31 +27974,48 @@ var Mediabunny = (() => {
27824
27974
  const headerLength = adtsFrame.crcCheck === null ? MIN_ADTS_FRAME_HEADER_SIZE : MAX_ADTS_FRAME_HEADER_SIZE;
27825
27975
  packetData = packetData.subarray(headerLength);
27826
27976
  }
27827
- const timestamp = this.validateAndNormalizeTimestamp(
27977
+ let timestamp = this.validateAndNormalizeTimestamp(
27828
27978
  trackData.track,
27829
27979
  packet.timestamp,
27830
27980
  packet.type === "key"
27831
27981
  );
27982
+ let duration = packet.duration;
27983
+ if (trackData.info.requiresPcmTransformation) {
27984
+ const pcmInfo = parsePcmCodec(
27985
+ trackData.info.decoderConfig.codec
27986
+ );
27987
+ const frameSize = pcmInfo.sampleSize * trackData.info.numberOfChannels;
27988
+ duration = packetData.byteLength / frameSize / trackData.info.sampleRate;
27989
+ if (trackData.info.expectedNextPcmPacketTimestamp !== null) {
27990
+ const diff = timestamp - trackData.info.expectedNextPcmPacketTimestamp;
27991
+ if (diff < 0.01) {
27992
+ timestamp = trackData.info.expectedNextPcmPacketTimestamp;
27993
+ } else {
27994
+ const paddedDuration = await this.padWithSilence(
27995
+ trackData,
27996
+ trackData.info.expectedNextPcmPacketTimestamp,
27997
+ diff
27998
+ );
27999
+ timestamp = trackData.info.expectedNextPcmPacketTimestamp + paddedDuration;
28000
+ }
28001
+ }
28002
+ trackData.info.expectedNextPcmPacketTimestamp = timestamp + duration;
28003
+ }
27832
28004
  const internalSample = this.createSampleForTrack(
27833
28005
  trackData,
27834
28006
  packetData,
27835
28007
  timestamp,
27836
- packet.duration,
28008
+ duration,
27837
28009
  packet.type
27838
28010
  );
27839
- if (trackData.info.requiresPcmTransformation) {
27840
- await this.maybePadWithSilence(trackData, timestamp);
27841
- }
27842
28011
  await this.registerSample(trackData, internalSample);
27843
28012
  } finally {
27844
28013
  release();
27845
28014
  }
27846
28015
  }
27847
- async maybePadWithSilence(trackData, untilTimestamp) {
27848
- const lastSample = last(trackData.samples);
27849
- const lastEndTimestamp = lastSample ? lastSample.timestamp + lastSample.duration : 0;
27850
- const delta = untilTimestamp - lastEndTimestamp;
27851
- const deltaInTimescale = intoTimescale(delta, trackData.timescale);
28016
+ async padWithSilence(trackData, timestamp, duration) {
28017
+ const deltaInTimescale = intoTimescale(duration, trackData.timescale);
28018
+ duration = deltaInTimescale / trackData.timescale;
27852
28019
  if (deltaInTimescale > 0) {
27853
28020
  const { sampleSize, silentValue } = parsePcmCodec(
27854
28021
  trackData.info.decoderConfig.codec
@@ -27858,12 +28025,13 @@ var Mediabunny = (() => {
27858
28025
  const paddingSample = this.createSampleForTrack(
27859
28026
  trackData,
27860
28027
  new Uint8Array(data.buffer),
27861
- lastEndTimestamp,
27862
- delta,
28028
+ timestamp,
28029
+ duration,
27863
28030
  "key"
27864
28031
  );
27865
28032
  await this.registerSample(trackData, paddingSample);
27866
28033
  }
28034
+ return duration;
27867
28035
  }
27868
28036
  async addSubtitleCue(track, cue, meta) {
27869
28037
  const release = await this.mutex.acquire();
@@ -27964,6 +28132,9 @@ var Mediabunny = (() => {
27964
28132
  return;
27965
28133
  }
27966
28134
  if (trackData.type === "audio" && trackData.info.requiresPcmTransformation) {
28135
+ if (!this.isFragmented) {
28136
+ trackData.startTimestampOffset ??= trackData.timestampProcessingQueue[0].timestamp;
28137
+ }
27967
28138
  let totalDuration = 0;
27968
28139
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
27969
28140
  const sample = trackData.timestampProcessingQueue[i];
@@ -27983,12 +28154,12 @@ var Mediabunny = (() => {
27983
28154
  return;
27984
28155
  }
27985
28156
  const sortedTimestamps = trackData.timestampProcessingQueue.map((x) => x.timestamp).sort((a, b) => a - b);
28157
+ if (!this.isFragmented) {
28158
+ trackData.startTimestampOffset ??= sortedTimestamps[0];
28159
+ }
27986
28160
  for (let i = 0; i < trackData.timestampProcessingQueue.length; i++) {
27987
28161
  const sample = trackData.timestampProcessingQueue[i];
27988
28162
  sample.decodeTimestamp = sortedTimestamps[i];
27989
- if (!this.isFragmented && trackData.lastTimescaleUnits === null) {
27990
- sample.decodeTimestamp = 0;
27991
- }
27992
28163
  const sampleCompositionTimeOffset = intoTimescale(sample.timestamp - sample.decodeTimestamp, trackData.timescale);
27993
28164
  const durationInTimescale = intoTimescale(sample.duration, trackData.timescale);
27994
28165
  if (trackData.lastTimescaleUnits !== null) {
@@ -28352,6 +28523,12 @@ var Mediabunny = (() => {
28352
28523
  } else {
28353
28524
  for (const trackData of this.trackDatas) {
28354
28525
  await this.finalizeCurrentChunk(trackData);
28526
+ assert(trackData.startTimestampOffset !== null);
28527
+ for (let i = 0; i < trackData.samples.length; i++) {
28528
+ const sample = trackData.samples[i];
28529
+ sample.timestamp -= trackData.startTimestampOffset;
28530
+ sample.decodeTimestamp -= trackData.startTimestampOffset;
28531
+ }
28355
28532
  }
28356
28533
  }
28357
28534
  assert(this.writer);
@@ -32320,6 +32497,14 @@ ${cue.notes ?? ""}`;
32320
32497
  */
32321
32498
  async processAndEncode(audioSample, shouldClose) {
32322
32499
  const config = this.encodingConfig;
32500
+ if (config.transform?.sampleFormat !== void 0 && toInterleavedAudioFormat(audioSample.format) !== config.transform.sampleFormat) {
32501
+ const newSample = audioSampleToInterleavedFormat(audioSample, config.transform.sampleFormat);
32502
+ if (shouldClose) {
32503
+ audioSample.close();
32504
+ }
32505
+ audioSample = newSample;
32506
+ shouldClose = true;
32507
+ }
32323
32508
  if (config.transform?.process) {
32324
32509
  let processed = config.transform.process(audioSample);
32325
32510
  if (processed instanceof Promise) {
@@ -32339,6 +32524,9 @@ ${cue.notes ?? ""}`;
32339
32524
  }
32340
32525
  await this.encodeSample(sample, true);
32341
32526
  }
32527
+ if (shouldClose) {
32528
+ audioSample.close();
32529
+ }
32342
32530
  } else {
32343
32531
  await this.encodeSample(audioSample, shouldClose);
32344
32532
  }
@@ -35418,6 +35606,9 @@ ${cue.notes ?? ""}`;
35418
35606
  if (audioOptions?.sampleRate !== void 0 && (!Number.isInteger(audioOptions.sampleRate) || audioOptions.sampleRate <= 0)) {
35419
35607
  throw new TypeError("options.audio.sampleRate, when provided, must be a positive integer.");
35420
35608
  }
35609
+ if (audioOptions?.sampleFormat !== void 0 && !["u8", "s16", "s32", "f32"].includes(audioOptions.sampleFormat)) {
35610
+ throw new TypeError("options.audio.sampleFormat, when provided, must be one of: u8, s16, s32, f32.");
35611
+ }
35421
35612
  if (audioOptions?.process !== void 0 && typeof audioOptions.process !== "function") {
35422
35613
  throw new TypeError("options.audio.process, when provided, must be a function.");
35423
35614
  }
@@ -36036,7 +36227,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36036
36227
  timestamp: lastCanvasTimestamp + i / frameRate,
36037
36228
  duration: 1 / frameRate
36038
36229
  });
36039
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36230
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36040
36231
  sample.close();
36041
36232
  }
36042
36233
  };
@@ -36063,7 +36254,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36063
36254
  timestamp: adjustedSampleTimestamp,
36064
36255
  duration: frameRate !== void 0 ? 1 / frameRate : duration
36065
36256
  });
36066
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36257
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36067
36258
  sample.close();
36068
36259
  if (frameRate !== void 0) {
36069
36260
  lastCanvas = canvas;
@@ -36093,7 +36284,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36093
36284
  for (let i = 1; i < frameDifference; i++) {
36094
36285
  lastSample.setTimestamp(lastSampleTimestamp + i / frameRate);
36095
36286
  lastSample.setDuration(1 / frameRate);
36096
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, lastSample);
36287
+ await this._registerVideoSample(trackOptions, outputTrackId, source, lastSample);
36097
36288
  }
36098
36289
  lastSample.close();
36099
36290
  };
@@ -36121,7 +36312,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36121
36312
  sample.setDuration(1 / frameRate);
36122
36313
  }
36123
36314
  sample.setTimestamp(adjustedSampleTimestamp);
36124
- await this._registerVideoSample(track, trackOptions, outputTrackId, source, sample);
36315
+ await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
36125
36316
  if (frameRate !== void 0) {
36126
36317
  lastSample = sample;
36127
36318
  lastSampleTimestamp = adjustedSampleTimestamp;
@@ -36160,7 +36351,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36160
36351
  this._outputOwnTrackGroups.push(ownGroup);
36161
36352
  }
36162
36353
  /** @internal */
36163
- async _registerVideoSample(track, trackOptions, outputTrackId, source, sample) {
36354
+ async _registerVideoSample(trackOptions, outputTrackId, source, sample) {
36164
36355
  if (this._canceled) {
36165
36356
  return;
36166
36357
  }
@@ -36224,7 +36415,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36224
36415
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
36225
36416
  let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || firstTimestamp < this._startTimestamp || firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
36226
36417
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
36227
- if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process) {
36418
+ if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
36228
36419
  const source = new EncodedAudioPacketSource(sourceCodec);
36229
36420
  audioSource = source;
36230
36421
  this._trackPromises.push((async () => {
@@ -36321,7 +36512,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36321
36512
  return;
36322
36513
  }
36323
36514
  sample.setTimestamp(sample.timestamp - this._startTimestamp);
36324
- await this._registerAudioSample(track, trackOptions, outputTrackId, source, sample);
36515
+ await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
36325
36516
  sample.close();
36326
36517
  }
36327
36518
  source.close();
@@ -36348,10 +36539,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36348
36539
  this._outputOwnTrackGroups.push(ownGroup);
36349
36540
  }
36350
36541
  /** @internal */
36351
- async _registerAudioSample(track, trackOptions, outputTrackId, source, sample) {
36542
+ async _registerAudioSample(trackOptions, outputTrackId, source, inputSample) {
36352
36543
  if (this._canceled) {
36353
36544
  return;
36354
36545
  }
36546
+ let sample = inputSample;
36547
+ if (trackOptions.sampleFormat !== void 0 && toInterleavedAudioFormat(sample.format) !== trackOptions.sampleFormat) {
36548
+ sample = audioSampleToInterleavedFormat(sample, trackOptions.sampleFormat);
36549
+ }
36355
36550
  this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
36356
36551
  let finalSamples;
36357
36552
  if (!trackOptions.process) {
@@ -36380,8 +36575,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36380
36575
  }
36381
36576
  }
36382
36577
  } finally {
36578
+ if (sample !== inputSample) {
36579
+ sample.close();
36580
+ }
36383
36581
  for (const finalSample of finalSamples) {
36384
- if (finalSample !== sample) {
36582
+ if (finalSample !== inputSample) {
36385
36583
  finalSample.close();
36386
36584
  }
36387
36585
  }
@@ -36402,7 +36600,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
36402
36600
  endTime: this._endTimestamp,
36403
36601
  onSample: async (sample) => {
36404
36602
  sample.setTimestamp(sample.timestamp - this._startTimestamp);
36405
- await this._registerAudioSample(track, trackOptions, outputTrackId, source, sample);
36603
+ await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
36406
36604
  sample.close();
36407
36605
  }
36408
36606
  });