mediabunny 1.46.0 → 1.47.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.
@@ -6890,35 +6890,39 @@ var Mediabunny = (() => {
6890
6890
  this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
6891
6891
  if (this.currentTrack) {
6892
6892
  const trackData = this.currentFragment.trackData.get(this.currentTrack.id);
6893
- if (trackData) {
6894
- this.currentFragment.implicitBaseDataOffset = trackData.currentOffset;
6895
- trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
6896
- for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
6897
- const currentEntry = trackData.presentationTimestamps[i];
6898
- const currentSample = trackData.samples[currentEntry.sampleIndex];
6899
- if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) {
6900
- trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp;
6893
+ cond:
6894
+ if (trackData) {
6895
+ if (trackData.samples.length === 0) {
6896
+ this.currentFragment.trackData.delete(this.currentTrack.id);
6897
+ break cond;
6901
6898
  }
6902
- if (i < trackData.presentationTimestamps.length - 1) {
6903
- const nextEntry = trackData.presentationTimestamps[i + 1];
6904
- const duration = nextEntry.presentationTimestamp - currentEntry.presentationTimestamp;
6905
- currentSample.duration = duration;
6899
+ trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
6900
+ for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
6901
+ const currentEntry = trackData.presentationTimestamps[i];
6902
+ const currentSample = trackData.samples[currentEntry.sampleIndex];
6903
+ if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) {
6904
+ trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp;
6905
+ }
6906
+ if (i < trackData.presentationTimestamps.length - 1) {
6907
+ const nextEntry = trackData.presentationTimestamps[i + 1];
6908
+ const duration = nextEntry.presentationTimestamp - currentEntry.presentationTimestamp;
6909
+ currentSample.duration = duration;
6910
+ }
6911
+ }
6912
+ const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex];
6913
+ const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex];
6914
+ trackData.startTimestamp = firstSample.presentationTimestamp;
6915
+ trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration;
6916
+ const { currentFragmentState } = this.currentTrack;
6917
+ assert(currentFragmentState);
6918
+ if (currentFragmentState.startTimestamp !== null) {
6919
+ offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp);
6920
+ trackData.startTimestampIsFinal = true;
6921
+ }
6922
+ if (currentFragmentState.encryptionAuxInfo && !trackData.samples[0].encryption) {
6923
+ trackData.encryptionAuxInfo = currentFragmentState.encryptionAuxInfo;
6906
6924
  }
6907
6925
  }
6908
- const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex];
6909
- const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex];
6910
- trackData.startTimestamp = firstSample.presentationTimestamp;
6911
- trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration;
6912
- const { currentFragmentState } = this.currentTrack;
6913
- assert(currentFragmentState);
6914
- if (currentFragmentState.startTimestamp !== null) {
6915
- offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp);
6916
- trackData.startTimestampIsFinal = true;
6917
- }
6918
- if (currentFragmentState.encryptionAuxInfo && !trackData.samples[0].encryption) {
6919
- trackData.encryptionAuxInfo = currentFragmentState.encryptionAuxInfo;
6920
- }
6921
- }
6922
6926
  this.currentTrack.currentFragmentState = null;
6923
6927
  this.currentTrack = null;
6924
6928
  }
@@ -7051,10 +7055,6 @@ var Mediabunny = (() => {
7051
7055
  };
7052
7056
  this.currentFragment.trackData.set(track.id, trackData);
7053
7057
  }
7054
- if (sampleCount === 0) {
7055
- this.currentFragment.implicitBaseDataOffset = trackData.currentOffset;
7056
- break;
7057
- }
7058
7058
  for (let i = 0; i < sampleCount; i++) {
7059
7059
  let sampleDuration;
7060
7060
  if (sampleDurationPresent) {
@@ -7100,6 +7100,7 @@ var Mediabunny = (() => {
7100
7100
  trackData.currentOffset += sampleSize;
7101
7101
  trackData.currentTimestamp += sampleDuration;
7102
7102
  }
7103
+ this.currentFragment.implicitBaseDataOffset = trackData.currentOffset;
7103
7104
  }
7104
7105
  ;
7105
7106
  break;
@@ -20577,6 +20578,65 @@ var Mediabunny = (() => {
20577
20578
  });
20578
20579
  }
20579
20580
  }
20581
+ /**
20582
+ * Returns a new {@link AudioSample} containing only the frames in the range [startSample, endSample). Both bounds
20583
+ * must lie within this sample's range of frames. The returned sample's timestamp is shifted to match the start of
20584
+ * the trimmed section.
20585
+ */
20586
+ trim(startSample, endSample = this.numberOfFrames) {
20587
+ if (!Number.isInteger(startSample) || startSample < 0) {
20588
+ throw new TypeError("startSample must be a non-negative integer.");
20589
+ }
20590
+ if (!Number.isInteger(endSample) || endSample < 0) {
20591
+ throw new TypeError("endSample must be a non-negative integer.");
20592
+ }
20593
+ if (startSample > this.numberOfFrames) {
20594
+ throw new RangeError("startSample out of range.");
20595
+ }
20596
+ if (endSample > this.numberOfFrames) {
20597
+ throw new RangeError("endSample out of range.");
20598
+ }
20599
+ if (endSample < startSample) {
20600
+ throw new RangeError("endSample must not be less than startSample.");
20601
+ }
20602
+ if (this._closed) {
20603
+ throw new Error("AudioSample is closed.");
20604
+ }
20605
+ const frameCount = endSample - startSample;
20606
+ const bytesPerSample = getBytesPerSample(this.format);
20607
+ let data;
20608
+ if (formatIsPlanar(this.format)) {
20609
+ const planeSize = frameCount * bytesPerSample;
20610
+ data = new Uint8Array(planeSize * this.numberOfChannels);
20611
+ if (frameCount > 0) {
20612
+ for (let i = 0; i < this.numberOfChannels; i++) {
20613
+ this.copyTo(data.subarray(i * planeSize, (i + 1) * planeSize), {
20614
+ planeIndex: i,
20615
+ format: this.format,
20616
+ frameOffset: startSample,
20617
+ frameCount
20618
+ });
20619
+ }
20620
+ }
20621
+ } else {
20622
+ data = new Uint8Array(frameCount * this.numberOfChannels * bytesPerSample);
20623
+ if (frameCount > 0) {
20624
+ this.copyTo(data, {
20625
+ planeIndex: 0,
20626
+ format: this.format,
20627
+ frameOffset: startSample,
20628
+ frameCount
20629
+ });
20630
+ }
20631
+ }
20632
+ return new _AudioSample({
20633
+ data,
20634
+ format: this.format,
20635
+ sampleRate: this.sampleRate,
20636
+ numberOfChannels: this.numberOfChannels,
20637
+ timestamp: this.timestamp + startSample / this.sampleRate
20638
+ });
20639
+ }
20580
20640
  /**
20581
20641
  * Closes this audio sample, releasing held resources. Audio samples should be closed as soon as they are not
20582
20642
  * needed anymore.
@@ -20995,6 +21055,9 @@ var Mediabunny = (() => {
20995
21055
  if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") {
20996
21056
  throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
20997
21057
  }
21058
+ if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") {
21059
+ throw new TypeError("config.onEncodedSample, when provided, must be a function.");
21060
+ }
20998
21061
  validateVideoEncodingAdditionalOptions(config.codec, config);
20999
21062
  };
21000
21063
  var validateVideoEncodingAdditionalOptions = (codec, options) => {
@@ -21090,6 +21153,9 @@ var Mediabunny = (() => {
21090
21153
  if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") {
21091
21154
  throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
21092
21155
  }
21156
+ if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") {
21157
+ throw new TypeError("config.onEncodedSample, when provided, must be a function.");
21158
+ }
21093
21159
  validateAudioEncodingAdditionalOptions(config.codec, config);
21094
21160
  };
21095
21161
  var validateAudioEncodingAdditionalOptions = (codec, options) => {
@@ -24537,7 +24603,10 @@ var Mediabunny = (() => {
24537
24603
  ref.free();
24538
24604
  }
24539
24605
  this._sourceRefs.length = 0;
24540
- void this._demuxerPromise?.then((demuxer) => demuxer.dispose());
24606
+ if (this._demuxerPromise) {
24607
+ void this._demuxerPromise.then((demuxer) => demuxer.dispose()).catch(() => {
24608
+ });
24609
+ }
24541
24610
  }
24542
24611
  /**
24543
24612
  * Calls `.dispose()` on the input, implementing the `Disposable` interface for use with
@@ -31990,17 +32059,17 @@ ${cue.notes ?? ""}`;
31990
32059
  constructor(options) {
31991
32060
  this.sourceSampleRate = null;
31992
32061
  this.sourceNumberOfChannels = null;
32062
+ this.startTime = null;
32063
+ /** Start frame of current buffer */
32064
+ this.bufferStartFrame = 0;
31993
32065
  /** The highest index written to in the current buffer */
31994
32066
  this.maxWrittenFrame = null;
31995
32067
  this.targetSampleRate = options.targetSampleRate;
31996
32068
  this.targetNumberOfChannels = options.targetNumberOfChannels;
31997
- this.endTime = options.endTime;
31998
32069
  this.onSample = options.onSample;
31999
32070
  this.bufferSizeInFrames = Math.floor(this.targetSampleRate * 5);
32000
32071
  this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels;
32001
32072
  this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
32002
- this.bufferStartFrame = Math.floor(options.startTime * this.targetSampleRate);
32003
- this.timestampOffset = options.startTime - this.bufferStartFrame / this.targetSampleRate;
32004
32073
  }
32005
32074
  /**
32006
32075
  * Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
@@ -32090,16 +32159,18 @@ ${cue.notes ?? ""}`;
32090
32159
  if (this.sourceSampleRate === null) {
32091
32160
  this.sourceSampleRate = audioSample.sampleRate;
32092
32161
  this.sourceNumberOfChannels = audioSample.numberOfChannels;
32162
+ this.startTime = audioSample.timestamp;
32093
32163
  this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
32094
32164
  this.doChannelMixerSetup();
32095
32165
  }
32166
+ assert(this.startTime !== null);
32096
32167
  const requiredSamples = audioSample.numberOfFrames * audioSample.numberOfChannels;
32097
32168
  this.ensureTempBufferSize(requiredSamples);
32098
32169
  const sourceDataSize = audioSample.allocationSize({ planeIndex: 0, format: "f32" });
32099
32170
  const sourceView = new Float32Array(this.tempSourceBuffer.buffer, 0, sourceDataSize / 4);
32100
32171
  audioSample.copyTo(sourceView, { planeIndex: 0, format: "f32" });
32101
- const inputStartTime = audioSample.timestamp;
32102
- const inputEndTime = Math.min(audioSample.timestamp + audioSample.duration, this.endTime);
32172
+ const inputStartTime = audioSample.timestamp - this.startTime;
32173
+ const inputEndTime = inputStartTime + audioSample.duration;
32103
32174
  const outputStartFrame = Math.floor(inputStartTime * this.targetSampleRate);
32104
32175
  const outputEndFrame = Math.ceil(inputEndTime * this.targetSampleRate);
32105
32176
  for (let outputFrame = outputStartFrame; outputFrame < outputEndFrame; outputFrame++) {
@@ -32142,15 +32213,15 @@ ${cue.notes ?? ""}`;
32142
32213
  if (this.maxWrittenFrame === null) {
32143
32214
  return;
32144
32215
  }
32216
+ assert(this.startTime !== null);
32145
32217
  const samplesWritten = (this.maxWrittenFrame + 1) * this.targetNumberOfChannels;
32146
32218
  const outputData = new Float32Array(samplesWritten);
32147
32219
  outputData.set(this.outputBuffer.subarray(0, samplesWritten));
32148
- const timestampSeconds = this.bufferStartFrame / this.targetSampleRate;
32149
32220
  const audioSample = new AudioSample({
32150
32221
  format: "f32",
32151
32222
  sampleRate: this.targetSampleRate,
32152
32223
  numberOfChannels: this.targetNumberOfChannels,
32153
- timestamp: timestampSeconds + this.timestampOffset,
32224
+ timestamp: this.startTime + this.bufferStartFrame / this.targetSampleRate,
32154
32225
  data: outputData
32155
32226
  });
32156
32227
  await this.onSample(audioSample);
@@ -32314,6 +32385,7 @@ ${cue.notes ?? ""}`;
32314
32385
  * So, we keep track of the encoder error and throw it as soon as we get the chance.
32315
32386
  */
32316
32387
  this.error = null;
32388
+ this.closed = false;
32317
32389
  this.lastMuxerPromise = Promise.resolve();
32318
32390
  }
32319
32391
  async add(videoSample, shouldClose, encodeOptions) {
@@ -32450,6 +32522,9 @@ ${cue.notes ?? ""}`;
32450
32522
  }
32451
32523
  }
32452
32524
  assert(this.encoderInitialized);
32525
+ if (this.closed) {
32526
+ break;
32527
+ }
32453
32528
  const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
32454
32529
  const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
32455
32530
  const mergedEncodeOptions = { ...sampleToEncode.encodeOptions, ...encodeOptions };
@@ -32458,6 +32533,7 @@ ${cue.notes ?? ""}`;
32458
32533
  keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
32459
32534
  };
32460
32535
  this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
32536
+ this.encodingConfig.onEncodedSample?.(sampleToEncode);
32461
32537
  if (this.customEncoder) {
32462
32538
  this.customEncoderQueueSize++;
32463
32539
  const clonedSample = sampleToEncode.clone();
@@ -32712,6 +32788,7 @@ ${cue.notes ?? ""}`;
32712
32788
  const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
32713
32789
  await this.padFrameRate(alignedEnd);
32714
32790
  }
32791
+ this.closed = true;
32715
32792
  this.frameRateLastSample?.close();
32716
32793
  this.frameRateLastSample = null;
32717
32794
  if (this.customEncoder) {
@@ -33585,6 +33662,7 @@ ${cue.notes ?? ""}`;
33585
33662
  */
33586
33663
  this.error = null;
33587
33664
  this.lastMuxerPromise = Promise.resolve();
33665
+ this.closed = false;
33588
33666
  }
33589
33667
  async add(audioSample, shouldClose) {
33590
33668
  try {
@@ -33607,8 +33685,6 @@ ${cue.notes ?? ""}`;
33607
33685
  this.resampler = new AudioResampler({
33608
33686
  targetNumberOfChannels: config.transform.numberOfChannels ?? audioSample.numberOfChannels,
33609
33687
  targetSampleRate: config.transform.sampleRate ?? audioSample.sampleRate,
33610
- startTime: audioSample.timestamp,
33611
- endTime: Infinity,
33612
33688
  onSample: async (sample) => {
33613
33689
  await this.processAndEncode(sample, true);
33614
33690
  }
@@ -33677,6 +33753,9 @@ ${cue.notes ?? ""}`;
33677
33753
  }
33678
33754
  }
33679
33755
  assert(this.encoderInitialized);
33756
+ if (this.closed) {
33757
+ return;
33758
+ }
33680
33759
  {
33681
33760
  const startSampleIndex = Math.round(
33682
33761
  audioSample.timestamp * audioSample.sampleRate
@@ -33702,6 +33781,7 @@ ${cue.notes ?? ""}`;
33702
33781
  this.lastEndSampleIndex += audioSample.numberOfFrames;
33703
33782
  }
33704
33783
  }
33784
+ this.encodingConfig.onEncodedSample?.(audioSample);
33705
33785
  if (this.customEncoder) {
33706
33786
  this.customEncoderQueueSize++;
33707
33787
  const clonedSample = audioSample.clone();
@@ -33974,6 +34054,7 @@ ${cue.notes ?? ""}`;
33974
34054
  await this.resampler.finalize();
33975
34055
  }
33976
34056
  this.resampler = null;
34057
+ this.closed = true;
33977
34058
  if (this.customEncoder) {
33978
34059
  if (!forceClose) {
33979
34060
  void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
@@ -37336,6 +37417,9 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37336
37417
  if (trackOptions.frameRate) {
37337
37418
  encodingConfig.transform.frameRate = trackOptions.frameRate;
37338
37419
  }
37420
+ if (trackOptions.process) {
37421
+ encodingConfig.transform.process = trackOptions.process;
37422
+ }
37339
37423
  if (needsRerender) {
37340
37424
  outputTrackRotation = 0;
37341
37425
  encodingConfig.transform.width = width;
@@ -37345,6 +37429,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37345
37429
  encodingConfig.transform.crop = crop;
37346
37430
  encodingConfig.transform.alpha = alpha;
37347
37431
  }
37432
+ let lastSampleTimestamp = null;
37433
+ encodingConfig.onEncodedSample = (sample) => {
37434
+ lastSampleTimestamp = sample.timestamp;
37435
+ };
37348
37436
  const source = new VideoSampleSource(encodingConfig);
37349
37437
  videoSource = source;
37350
37438
  this._trackPromises.push((async () => {
@@ -37357,7 +37445,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37357
37445
  }
37358
37446
  const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
37359
37447
  sample.setTimestamp(adjustedSampleTimestamp);
37360
- await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
37448
+ this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
37449
+ await source.add(sample);
37450
+ if (lastSampleTimestamp !== null) {
37451
+ if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) {
37452
+ await this._synchronizer.wait(lastSampleTimestamp);
37453
+ }
37454
+ }
37361
37455
  sample.close();
37362
37456
  }
37363
37457
  source.close();
@@ -37385,52 +37479,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37385
37479
  this._outputOwnTrackGroups.push(ownGroup);
37386
37480
  }
37387
37481
  /** @internal */
37388
- async _registerVideoSample(trackOptions, outputTrackId, source, sample) {
37389
- if (this._canceled) {
37390
- return;
37391
- }
37392
- this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
37393
- let finalSamples;
37394
- if (!trackOptions.process) {
37395
- finalSamples = [sample];
37396
- } else {
37397
- let processed = trackOptions.process(sample);
37398
- if (processed instanceof Promise) processed = await processed;
37399
- if (!Array.isArray(processed)) {
37400
- processed = processed === null ? [] : [processed];
37401
- }
37402
- finalSamples = processed.map((x) => {
37403
- if (x instanceof VideoSample) {
37404
- return x;
37405
- }
37406
- if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
37407
- return new VideoSample(x);
37408
- }
37409
- return new VideoSample(x, {
37410
- timestamp: sample.timestamp,
37411
- duration: sample.duration
37412
- });
37413
- });
37414
- }
37415
- try {
37416
- for (const finalSample of finalSamples) {
37417
- if (this._canceled) {
37418
- break;
37419
- }
37420
- await source.add(finalSample);
37421
- if (this._synchronizer.shouldWait(outputTrackId, finalSample.timestamp)) {
37422
- await this._synchronizer.wait(finalSample.timestamp);
37423
- }
37424
- }
37425
- } finally {
37426
- for (const finalSample of finalSamples) {
37427
- if (finalSample !== sample) {
37428
- finalSample.close();
37429
- }
37430
- }
37431
- }
37432
- }
37433
- /** @internal */
37434
37482
  async _processAudioTrack(track, trackOptions, outputTrackId) {
37435
37483
  const sourceCodec = await track.getCodec();
37436
37484
  if (!sourceCodec) {
@@ -37447,9 +37495,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37447
37495
  const firstTimestamp = await track.getFirstTimestamp();
37448
37496
  let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
37449
37497
  let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
37450
- let needsResample = numberOfChannels !== originalNumberOfChannels || sampleRate !== originalSampleRate || firstTimestamp < this._startTimestamp || firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
37498
+ const needsTrimming = firstTimestamp < this._startTimestamp;
37499
+ const needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
37451
37500
  let audioCodecs = this.output.format.getSupportedAudioCodecs();
37452
- if (!trackOptions.forceTranscode && !trackOptions.bitrate && !needsResample && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
37501
+ 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) {
37453
37502
  const source = new EncodedAudioPacketSource(sourceCodec);
37454
37503
  audioSource = source;
37455
37504
  this._trackPromises.push((async () => {
@@ -37505,7 +37554,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37505
37554
  });
37506
37555
  const nonPcmCodec = encodableCodecsWithDefaultParams.find((codec) => NON_PCM_AUDIO_CODECS.includes(codec));
37507
37556
  if (nonPcmCodec) {
37508
- needsResample = true;
37509
37557
  codecOfChoice = nonPcmCodec;
37510
37558
  numberOfChannels = FALLBACK_NUMBER_OF_CHANNELS;
37511
37559
  sampleRate = FALLBACK_SAMPLE_RATE;
@@ -37521,38 +37569,70 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37521
37569
  });
37522
37570
  return;
37523
37571
  }
37524
- if (needsResample) {
37525
- audioSource = this._resampleAudio(
37526
- track,
37527
- trackOptions,
37528
- outputTrackId,
37529
- codecOfChoice,
37530
- numberOfChannels,
37531
- sampleRate,
37532
- bitrate
37533
- );
37534
- } else {
37535
- const source = new AudioSampleSource({
37536
- codec: codecOfChoice,
37537
- bitrate
37538
- });
37539
- audioSource = source;
37540
- this._trackPromises.push((async () => {
37541
- await this._started;
37542
- const sink = new AudioSampleSink(track);
37543
- for await (const sample of sink.samples(void 0, this._endTimestamp)) {
37544
- if (this._canceled) {
37572
+ const encodingConfig = {
37573
+ codec: codecOfChoice,
37574
+ bitrate,
37575
+ transform: {
37576
+ sampleFormat: trackOptions.sampleFormat,
37577
+ process: trackOptions.process
37578
+ }
37579
+ };
37580
+ assert(encodingConfig.transform);
37581
+ if (numberOfChannels !== originalNumberOfChannels) {
37582
+ encodingConfig.transform.numberOfChannels = numberOfChannels;
37583
+ }
37584
+ if (sampleRate !== originalSampleRate) {
37585
+ encodingConfig.transform.sampleRate = sampleRate;
37586
+ }
37587
+ let lastSampleTimestamp = null;
37588
+ encodingConfig.onEncodedSample = (sample) => {
37589
+ lastSampleTimestamp = sample.timestamp;
37590
+ };
37591
+ const source = new AudioSampleSource(encodingConfig);
37592
+ audioSource = source;
37593
+ this._trackPromises.push((async () => {
37594
+ await this._started;
37595
+ if (needsPadding) {
37596
+ const paddingLength = firstTimestamp - this._startTimestamp;
37597
+ const paddingLengthSamples = Math.round(paddingLength * originalSampleRate);
37598
+ const silentSample = new AudioSample({
37599
+ data: new Float32Array(paddingLengthSamples * originalNumberOfChannels),
37600
+ format: "f32-planar",
37601
+ numberOfChannels: originalNumberOfChannels,
37602
+ sampleRate: originalSampleRate,
37603
+ timestamp: 0
37604
+ });
37605
+ await this._registerAudioSample(silentSample, source, outputTrackId, () => lastSampleTimestamp);
37606
+ }
37607
+ const sink = new AudioSampleSink(track);
37608
+ for await (let sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
37609
+ if (this._canceled) {
37610
+ sample.close();
37611
+ return;
37612
+ }
37613
+ let startFrame = 0;
37614
+ let endFrame = sample.numberOfFrames;
37615
+ if (sample.timestamp < this._startTimestamp) {
37616
+ startFrame = Math.round((this._startTimestamp - sample.timestamp) * sample.sampleRate);
37617
+ }
37618
+ if (sample.timestamp + sample.duration > this._endTimestamp) {
37619
+ endFrame = Math.round((this._endTimestamp - sample.timestamp) * sample.sampleRate);
37620
+ }
37621
+ if (startFrame > 0 || endFrame < sample.numberOfFrames) {
37622
+ const trimmedSample = sample.trim(startFrame, endFrame);
37623
+ sample.close();
37624
+ sample = trimmedSample;
37625
+ if (sample.numberOfFrames === 0) {
37545
37626
  sample.close();
37546
- return;
37627
+ continue;
37547
37628
  }
37548
- sample.setTimestamp(sample.timestamp - this._startTimestamp);
37549
- await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
37550
- sample.close();
37551
37629
  }
37552
- source.close();
37553
- this._synchronizer.closeTrack(outputTrackId);
37554
- })());
37555
- }
37630
+ sample.setTimestamp(sample.timestamp - this._startTimestamp);
37631
+ await this._registerAudioSample(sample, source, outputTrackId, () => lastSampleTimestamp);
37632
+ }
37633
+ source.close();
37634
+ this._synchronizer.closeTrack(outputTrackId);
37635
+ })());
37556
37636
  }
37557
37637
  let ownGroup = null;
37558
37638
  if (!trackOptions.group) {
@@ -37573,89 +37653,18 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
37573
37653
  this._outputOwnTrackGroups.push(ownGroup);
37574
37654
  }
37575
37655
  /** @internal */
37576
- async _registerAudioSample(trackOptions, outputTrackId, source, inputSample) {
37577
- if (this._canceled) {
37578
- return;
37579
- }
37580
- let sample = inputSample;
37581
- if (trackOptions.sampleFormat !== void 0 && toInterleavedAudioFormat(sample.format) !== trackOptions.sampleFormat) {
37582
- sample = audioSampleToInterleavedFormat(sample, trackOptions.sampleFormat);
37583
- }
37656
+ async _registerAudioSample(sample, source, outputTrackId, getLastSampleTimestamp) {
37584
37657
  this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
37585
- let finalSamples;
37586
- if (!trackOptions.process) {
37587
- finalSamples = [sample];
37588
- } else {
37589
- let processed = trackOptions.process(sample);
37590
- if (processed instanceof Promise) processed = await processed;
37591
- if (!Array.isArray(processed)) {
37592
- processed = processed === null ? [] : [processed];
37593
- }
37594
- if (!processed.every((x) => x instanceof AudioSample)) {
37595
- throw new TypeError(
37596
- "The audio process function must return an AudioSample, null, or an array of AudioSamples."
37597
- );
37598
- }
37599
- finalSamples = processed;
37600
- }
37601
- try {
37602
- for (const finalSample of finalSamples) {
37603
- if (this._canceled) {
37604
- break;
37605
- }
37606
- await source.add(finalSample);
37607
- if (this._synchronizer.shouldWait(outputTrackId, finalSample.timestamp)) {
37608
- await this._synchronizer.wait(finalSample.timestamp);
37609
- }
37610
- }
37611
- } finally {
37612
- if (sample !== inputSample) {
37613
- sample.close();
37614
- }
37615
- for (const finalSample of finalSamples) {
37616
- if (finalSample !== inputSample) {
37617
- finalSample.close();
37618
- }
37658
+ await source.add(sample);
37659
+ sample.close();
37660
+ const lastSampleTimestamp = getLastSampleTimestamp();
37661
+ if (lastSampleTimestamp !== null) {
37662
+ if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) {
37663
+ await this._synchronizer.wait(lastSampleTimestamp);
37619
37664
  }
37620
37665
  }
37621
37666
  }
37622
37667
  /** @internal */
37623
- _resampleAudio(track, trackOptions, outputTrackId, codec, targetNumberOfChannels, targetSampleRate, bitrate) {
37624
- const source = new AudioSampleSource({
37625
- codec,
37626
- bitrate
37627
- });
37628
- this._trackPromises.push((async () => {
37629
- await this._started;
37630
- const resampler = new AudioResampler({
37631
- targetNumberOfChannels,
37632
- targetSampleRate,
37633
- startTime: this._startTimestamp,
37634
- endTime: this._endTimestamp,
37635
- onSample: async (sample) => {
37636
- assert(sample.timestamp >= this._startTimestamp);
37637
- sample.setTimestamp(sample.timestamp - this._startTimestamp);
37638
- await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
37639
- sample.close();
37640
- }
37641
- });
37642
- const sink = new AudioSampleSink(track);
37643
- const iterator = sink.samples(this._startTimestamp, this._endTimestamp);
37644
- for await (const sample of iterator) {
37645
- if (this._canceled) {
37646
- sample.close();
37647
- return;
37648
- }
37649
- await resampler.add(sample);
37650
- sample.close();
37651
- }
37652
- await resampler.finalize();
37653
- source.close();
37654
- this._synchronizer.closeTrack(outputTrackId);
37655
- })());
37656
- return source;
37657
- }
37658
- /** @internal */
37659
37668
  _reportProgress(trackId, endTimestamp) {
37660
37669
  if (!this._computeProgress) {
37661
37670
  return;