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.
- package/dist/bundles/mediabunny.cjs +206 -197
- package/dist/bundles/mediabunny.min.cjs +11 -11
- package/dist/bundles/mediabunny.min.mjs +11 -11
- package/dist/bundles/mediabunny.mjs +206 -197
- package/dist/bundles/mediabunny.node.cjs +206 -197
- package/dist/mediabunny.d.ts +10 -0
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/conversion.js +91 -163
- package/dist/modules/src/encode.d.ts +4 -0
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/encode.js +6 -0
- package/dist/modules/src/input.d.ts.map +1 -1
- package/dist/modules/src/input.js +6 -2
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-demuxer.js +8 -7
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/media-source.js +14 -2
- package/dist/modules/src/resample.d.ts +1 -4
- package/dist/modules/src/resample.d.ts.map +1 -1
- package/dist/modules/src/resample.js +9 -9
- package/dist/modules/src/sample.d.ts +6 -0
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/sample.js +62 -0
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/conversion.ts +108 -215
- package/src/encode.ts +10 -0
- package/src/input.ts +6 -2
- package/src/isobmff/isobmff-demuxer.ts +9 -7
- package/src/media-source.ts +20 -2
- package/src/resample.ts +10 -15
- package/src/sample.ts +68 -0
|
@@ -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
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
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
|
-
|
|
6903
|
-
|
|
6904
|
-
const
|
|
6905
|
-
currentSample
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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 && !
|
|
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
|
-
|
|
37525
|
-
|
|
37526
|
-
|
|
37527
|
-
|
|
37528
|
-
|
|
37529
|
-
|
|
37530
|
-
|
|
37531
|
-
|
|
37532
|
-
|
|
37533
|
-
|
|
37534
|
-
|
|
37535
|
-
|
|
37536
|
-
|
|
37537
|
-
|
|
37538
|
-
|
|
37539
|
-
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
|
|
37543
|
-
|
|
37544
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37553
|
-
this.
|
|
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(
|
|
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
|
-
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
|
|
37589
|
-
|
|
37590
|
-
|
|
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;
|