mediabunny 1.45.5 → 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 +238 -206
- package/dist/bundles/mediabunny.min.cjs +11 -11
- package/dist/bundles/mediabunny.min.mjs +11 -11
- package/dist/bundles/mediabunny.mjs +238 -206
- package/dist/bundles/mediabunny.node.cjs +238 -206
- package/dist/mediabunny.d.ts +25 -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/index.d.ts +1 -1
- package/dist/modules/src/index.d.ts.map +1 -1
- 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 +23 -9
- package/dist/modules/src/misc.d.ts +8 -0
- package/dist/modules/src/misc.d.ts.map +1 -1
- 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 +13 -1
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/sample.js +89 -4
- 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/index.ts +1 -0
- package/src/input.ts +6 -2
- package/src/isobmff/isobmff-demuxer.ts +9 -7
- package/src/media-source.ts +30 -9
- package/src/misc.ts +9 -0
- package/src/resample.ts +10 -15
- package/src/sample.ts +111 -4
|
@@ -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;
|
|
@@ -19068,7 +19069,7 @@ var Mediabunny = (() => {
|
|
|
19068
19069
|
"init.displayWidth and init.displayHeight must be either both provided or both omitted."
|
|
19069
19070
|
);
|
|
19070
19071
|
}
|
|
19071
|
-
this._data = toUint8Array(data).slice();
|
|
19072
|
+
this._data = init._doNotCopy ? toUint8Array(data) : toUint8Array(data).slice();
|
|
19072
19073
|
this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
|
|
19073
19074
|
this.format = init.format;
|
|
19074
19075
|
this.rotation = init.rotation ?? 0;
|
|
@@ -19178,7 +19179,11 @@ var Mediabunny = (() => {
|
|
|
19178
19179
|
// Firefox has VideoFrame glitches with opaque canvases
|
|
19179
19180
|
willReadFrequently: true
|
|
19180
19181
|
});
|
|
19181
|
-
|
|
19182
|
+
if (!context) {
|
|
19183
|
+
throw new Error(
|
|
19184
|
+
"OffscreenCanvas must have support for the '2d' context in order to create a VideoSample from this data."
|
|
19185
|
+
);
|
|
19186
|
+
}
|
|
19182
19187
|
context.drawImage(data, 0, 0);
|
|
19183
19188
|
this._data = canvas;
|
|
19184
19189
|
this._layout = null;
|
|
@@ -19243,6 +19248,7 @@ var Mediabunny = (() => {
|
|
|
19243
19248
|
"Invalid data type: Must be a BufferSource, CanvasImageSource, or VideoSampleResource."
|
|
19244
19249
|
);
|
|
19245
19250
|
}
|
|
19251
|
+
this.encodeOptions = init?.encodeOptions ?? {};
|
|
19246
19252
|
this.pixelAspectRatio = simplifyRational({
|
|
19247
19253
|
num: this.squarePixelWidth * this.codedHeight,
|
|
19248
19254
|
den: this.squarePixelHeight * this.codedWidth
|
|
@@ -19290,13 +19296,15 @@ var Mediabunny = (() => {
|
|
|
19290
19296
|
return new _VideoSample(this._data, {
|
|
19291
19297
|
timestamp: this.timestamp,
|
|
19292
19298
|
duration: this.duration,
|
|
19293
|
-
rotation: this.rotation
|
|
19299
|
+
rotation: this.rotation,
|
|
19300
|
+
encodeOptions: this.encodeOptions
|
|
19294
19301
|
});
|
|
19295
19302
|
} else if (isVideoFrame(this._data)) {
|
|
19296
19303
|
return new _VideoSample(this._data.clone(), {
|
|
19297
19304
|
timestamp: this.timestamp,
|
|
19298
19305
|
duration: this.duration,
|
|
19299
|
-
rotation: this.rotation
|
|
19306
|
+
rotation: this.rotation,
|
|
19307
|
+
encodeOptions: this.encodeOptions
|
|
19300
19308
|
});
|
|
19301
19309
|
} else if (this._data instanceof Uint8Array) {
|
|
19302
19310
|
assert(this._layout);
|
|
@@ -19311,7 +19319,10 @@ var Mediabunny = (() => {
|
|
|
19311
19319
|
rotation: this.rotation,
|
|
19312
19320
|
visibleRect: this.visibleRect,
|
|
19313
19321
|
displayWidth: this.displayWidth,
|
|
19314
|
-
displayHeight: this.displayHeight
|
|
19322
|
+
displayHeight: this.displayHeight,
|
|
19323
|
+
encodeOptions: this.encodeOptions,
|
|
19324
|
+
// It's already been copied, if we copy it again we make the clone unnecessarily expensive
|
|
19325
|
+
_doNotCopy: true
|
|
19315
19326
|
});
|
|
19316
19327
|
} else {
|
|
19317
19328
|
return new _VideoSample(this._data, {
|
|
@@ -19324,7 +19335,8 @@ var Mediabunny = (() => {
|
|
|
19324
19335
|
rotation: this.rotation,
|
|
19325
19336
|
visibleRect: this.visibleRect,
|
|
19326
19337
|
displayWidth: this.displayWidth,
|
|
19327
|
-
displayHeight: this.displayHeight
|
|
19338
|
+
displayHeight: this.displayHeight,
|
|
19339
|
+
encodeOptions: this.encodeOptions
|
|
19328
19340
|
});
|
|
19329
19341
|
}
|
|
19330
19342
|
}
|
|
@@ -19886,7 +19898,11 @@ var Mediabunny = (() => {
|
|
|
19886
19898
|
const context = canvas.getContext("2d", {
|
|
19887
19899
|
alpha: true
|
|
19888
19900
|
});
|
|
19889
|
-
|
|
19901
|
+
if (!context) {
|
|
19902
|
+
throw new Error(
|
|
19903
|
+
"The '2d' canvas context is required to transform VideoSamples. Register a custom transformer using registerVideoSampleTransformer to work around this limitation."
|
|
19904
|
+
);
|
|
19905
|
+
}
|
|
19890
19906
|
if (description.alpha === "discard") {
|
|
19891
19907
|
context.fillStyle = "black";
|
|
19892
19908
|
context.fillRect(0, 0, description.width, description.height);
|
|
@@ -19926,6 +19942,13 @@ var Mediabunny = (() => {
|
|
|
19926
19942
|
}
|
|
19927
19943
|
this.duration = newDuration;
|
|
19928
19944
|
}
|
|
19945
|
+
/** Sets the encode options used when this sample is passed to an encoder. */
|
|
19946
|
+
setEncodeOptions(newEncodeOptions) {
|
|
19947
|
+
if (!newEncodeOptions || typeof newEncodeOptions !== "object") {
|
|
19948
|
+
throw new TypeError("newEncodeOptions must be an object.");
|
|
19949
|
+
}
|
|
19950
|
+
this.encodeOptions = newEncodeOptions;
|
|
19951
|
+
}
|
|
19929
19952
|
/** Calls `.close()`. */
|
|
19930
19953
|
[Symbol.dispose]() {
|
|
19931
19954
|
this.close();
|
|
@@ -20555,6 +20578,65 @@ var Mediabunny = (() => {
|
|
|
20555
20578
|
});
|
|
20556
20579
|
}
|
|
20557
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
|
+
}
|
|
20558
20640
|
/**
|
|
20559
20641
|
* Closes this audio sample, releasing held resources. Audio samples should be closed as soon as they are not
|
|
20560
20642
|
* needed anymore.
|
|
@@ -20973,6 +21055,9 @@ var Mediabunny = (() => {
|
|
|
20973
21055
|
if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") {
|
|
20974
21056
|
throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
|
|
20975
21057
|
}
|
|
21058
|
+
if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") {
|
|
21059
|
+
throw new TypeError("config.onEncodedSample, when provided, must be a function.");
|
|
21060
|
+
}
|
|
20976
21061
|
validateVideoEncodingAdditionalOptions(config.codec, config);
|
|
20977
21062
|
};
|
|
20978
21063
|
var validateVideoEncodingAdditionalOptions = (codec, options) => {
|
|
@@ -21068,6 +21153,9 @@ var Mediabunny = (() => {
|
|
|
21068
21153
|
if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") {
|
|
21069
21154
|
throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
|
|
21070
21155
|
}
|
|
21156
|
+
if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") {
|
|
21157
|
+
throw new TypeError("config.onEncodedSample, when provided, must be a function.");
|
|
21158
|
+
}
|
|
21071
21159
|
validateAudioEncodingAdditionalOptions(config.codec, config);
|
|
21072
21160
|
};
|
|
21073
21161
|
var validateAudioEncodingAdditionalOptions = (codec, options) => {
|
|
@@ -24515,7 +24603,10 @@ var Mediabunny = (() => {
|
|
|
24515
24603
|
ref.free();
|
|
24516
24604
|
}
|
|
24517
24605
|
this._sourceRefs.length = 0;
|
|
24518
|
-
|
|
24606
|
+
if (this._demuxerPromise) {
|
|
24607
|
+
void this._demuxerPromise.then((demuxer) => demuxer.dispose()).catch(() => {
|
|
24608
|
+
});
|
|
24609
|
+
}
|
|
24519
24610
|
}
|
|
24520
24611
|
/**
|
|
24521
24612
|
* Calls `.dispose()` on the input, implementing the `Disposable` interface for use with
|
|
@@ -31968,17 +32059,17 @@ ${cue.notes ?? ""}`;
|
|
|
31968
32059
|
constructor(options) {
|
|
31969
32060
|
this.sourceSampleRate = null;
|
|
31970
32061
|
this.sourceNumberOfChannels = null;
|
|
32062
|
+
this.startTime = null;
|
|
32063
|
+
/** Start frame of current buffer */
|
|
32064
|
+
this.bufferStartFrame = 0;
|
|
31971
32065
|
/** The highest index written to in the current buffer */
|
|
31972
32066
|
this.maxWrittenFrame = null;
|
|
31973
32067
|
this.targetSampleRate = options.targetSampleRate;
|
|
31974
32068
|
this.targetNumberOfChannels = options.targetNumberOfChannels;
|
|
31975
|
-
this.endTime = options.endTime;
|
|
31976
32069
|
this.onSample = options.onSample;
|
|
31977
32070
|
this.bufferSizeInFrames = Math.floor(this.targetSampleRate * 5);
|
|
31978
32071
|
this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels;
|
|
31979
32072
|
this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
|
|
31980
|
-
this.bufferStartFrame = Math.floor(options.startTime * this.targetSampleRate);
|
|
31981
|
-
this.timestampOffset = options.startTime - this.bufferStartFrame / this.targetSampleRate;
|
|
31982
32073
|
}
|
|
31983
32074
|
/**
|
|
31984
32075
|
* Sets up the channel mixer to handle up/downmixing in the case where input and output channel counts don't match.
|
|
@@ -32068,16 +32159,18 @@ ${cue.notes ?? ""}`;
|
|
|
32068
32159
|
if (this.sourceSampleRate === null) {
|
|
32069
32160
|
this.sourceSampleRate = audioSample.sampleRate;
|
|
32070
32161
|
this.sourceNumberOfChannels = audioSample.numberOfChannels;
|
|
32162
|
+
this.startTime = audioSample.timestamp;
|
|
32071
32163
|
this.tempSourceBuffer = new Float32Array(this.sourceSampleRate * this.sourceNumberOfChannels);
|
|
32072
32164
|
this.doChannelMixerSetup();
|
|
32073
32165
|
}
|
|
32166
|
+
assert(this.startTime !== null);
|
|
32074
32167
|
const requiredSamples = audioSample.numberOfFrames * audioSample.numberOfChannels;
|
|
32075
32168
|
this.ensureTempBufferSize(requiredSamples);
|
|
32076
32169
|
const sourceDataSize = audioSample.allocationSize({ planeIndex: 0, format: "f32" });
|
|
32077
32170
|
const sourceView = new Float32Array(this.tempSourceBuffer.buffer, 0, sourceDataSize / 4);
|
|
32078
32171
|
audioSample.copyTo(sourceView, { planeIndex: 0, format: "f32" });
|
|
32079
|
-
const inputStartTime = audioSample.timestamp;
|
|
32080
|
-
const inputEndTime =
|
|
32172
|
+
const inputStartTime = audioSample.timestamp - this.startTime;
|
|
32173
|
+
const inputEndTime = inputStartTime + audioSample.duration;
|
|
32081
32174
|
const outputStartFrame = Math.floor(inputStartTime * this.targetSampleRate);
|
|
32082
32175
|
const outputEndFrame = Math.ceil(inputEndTime * this.targetSampleRate);
|
|
32083
32176
|
for (let outputFrame = outputStartFrame; outputFrame < outputEndFrame; outputFrame++) {
|
|
@@ -32120,15 +32213,15 @@ ${cue.notes ?? ""}`;
|
|
|
32120
32213
|
if (this.maxWrittenFrame === null) {
|
|
32121
32214
|
return;
|
|
32122
32215
|
}
|
|
32216
|
+
assert(this.startTime !== null);
|
|
32123
32217
|
const samplesWritten = (this.maxWrittenFrame + 1) * this.targetNumberOfChannels;
|
|
32124
32218
|
const outputData = new Float32Array(samplesWritten);
|
|
32125
32219
|
outputData.set(this.outputBuffer.subarray(0, samplesWritten));
|
|
32126
|
-
const timestampSeconds = this.bufferStartFrame / this.targetSampleRate;
|
|
32127
32220
|
const audioSample = new AudioSample({
|
|
32128
32221
|
format: "f32",
|
|
32129
32222
|
sampleRate: this.targetSampleRate,
|
|
32130
32223
|
numberOfChannels: this.targetNumberOfChannels,
|
|
32131
|
-
timestamp:
|
|
32224
|
+
timestamp: this.startTime + this.bufferStartFrame / this.targetSampleRate,
|
|
32132
32225
|
data: outputData
|
|
32133
32226
|
});
|
|
32134
32227
|
await this.onSample(audioSample);
|
|
@@ -32292,6 +32385,7 @@ ${cue.notes ?? ""}`;
|
|
|
32292
32385
|
* So, we keep track of the encoder error and throw it as soon as we get the chance.
|
|
32293
32386
|
*/
|
|
32294
32387
|
this.error = null;
|
|
32388
|
+
this.closed = false;
|
|
32295
32389
|
this.lastMuxerPromise = Promise.resolve();
|
|
32296
32390
|
}
|
|
32297
32391
|
async add(videoSample, shouldClose, encodeOptions) {
|
|
@@ -32428,13 +32522,18 @@ ${cue.notes ?? ""}`;
|
|
|
32428
32522
|
}
|
|
32429
32523
|
}
|
|
32430
32524
|
assert(this.encoderInitialized);
|
|
32525
|
+
if (this.closed) {
|
|
32526
|
+
break;
|
|
32527
|
+
}
|
|
32431
32528
|
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
|
|
32432
32529
|
const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
|
|
32530
|
+
const mergedEncodeOptions = { ...sampleToEncode.encodeOptions, ...encodeOptions };
|
|
32433
32531
|
const finalEncodeOptions = {
|
|
32434
|
-
...
|
|
32435
|
-
keyFrame:
|
|
32532
|
+
...mergedEncodeOptions,
|
|
32533
|
+
keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
|
|
32436
32534
|
};
|
|
32437
32535
|
this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
|
|
32536
|
+
this.encodingConfig.onEncodedSample?.(sampleToEncode);
|
|
32438
32537
|
if (this.customEncoder) {
|
|
32439
32538
|
this.customEncoderQueueSize++;
|
|
32440
32539
|
const clonedSample = sampleToEncode.clone();
|
|
@@ -32689,6 +32788,7 @@ ${cue.notes ?? ""}`;
|
|
|
32689
32788
|
const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
|
|
32690
32789
|
await this.padFrameRate(alignedEnd);
|
|
32691
32790
|
}
|
|
32791
|
+
this.closed = true;
|
|
32692
32792
|
this.frameRateLastSample?.close();
|
|
32693
32793
|
this.frameRateLastSample = null;
|
|
32694
32794
|
if (this.customEncoder) {
|
|
@@ -33562,6 +33662,7 @@ ${cue.notes ?? ""}`;
|
|
|
33562
33662
|
*/
|
|
33563
33663
|
this.error = null;
|
|
33564
33664
|
this.lastMuxerPromise = Promise.resolve();
|
|
33665
|
+
this.closed = false;
|
|
33565
33666
|
}
|
|
33566
33667
|
async add(audioSample, shouldClose) {
|
|
33567
33668
|
try {
|
|
@@ -33584,8 +33685,6 @@ ${cue.notes ?? ""}`;
|
|
|
33584
33685
|
this.resampler = new AudioResampler({
|
|
33585
33686
|
targetNumberOfChannels: config.transform.numberOfChannels ?? audioSample.numberOfChannels,
|
|
33586
33687
|
targetSampleRate: config.transform.sampleRate ?? audioSample.sampleRate,
|
|
33587
|
-
startTime: audioSample.timestamp,
|
|
33588
|
-
endTime: Infinity,
|
|
33589
33688
|
onSample: async (sample) => {
|
|
33590
33689
|
await this.processAndEncode(sample, true);
|
|
33591
33690
|
}
|
|
@@ -33654,6 +33753,9 @@ ${cue.notes ?? ""}`;
|
|
|
33654
33753
|
}
|
|
33655
33754
|
}
|
|
33656
33755
|
assert(this.encoderInitialized);
|
|
33756
|
+
if (this.closed) {
|
|
33757
|
+
return;
|
|
33758
|
+
}
|
|
33657
33759
|
{
|
|
33658
33760
|
const startSampleIndex = Math.round(
|
|
33659
33761
|
audioSample.timestamp * audioSample.sampleRate
|
|
@@ -33679,6 +33781,7 @@ ${cue.notes ?? ""}`;
|
|
|
33679
33781
|
this.lastEndSampleIndex += audioSample.numberOfFrames;
|
|
33680
33782
|
}
|
|
33681
33783
|
}
|
|
33784
|
+
this.encodingConfig.onEncodedSample?.(audioSample);
|
|
33682
33785
|
if (this.customEncoder) {
|
|
33683
33786
|
this.customEncoderQueueSize++;
|
|
33684
33787
|
const clonedSample = audioSample.clone();
|
|
@@ -33951,6 +34054,7 @@ ${cue.notes ?? ""}`;
|
|
|
33951
34054
|
await this.resampler.finalize();
|
|
33952
34055
|
}
|
|
33953
34056
|
this.resampler = null;
|
|
34057
|
+
this.closed = true;
|
|
33954
34058
|
if (this.customEncoder) {
|
|
33955
34059
|
if (!forceClose) {
|
|
33956
34060
|
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
@@ -37313,6 +37417,9 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37313
37417
|
if (trackOptions.frameRate) {
|
|
37314
37418
|
encodingConfig.transform.frameRate = trackOptions.frameRate;
|
|
37315
37419
|
}
|
|
37420
|
+
if (trackOptions.process) {
|
|
37421
|
+
encodingConfig.transform.process = trackOptions.process;
|
|
37422
|
+
}
|
|
37316
37423
|
if (needsRerender) {
|
|
37317
37424
|
outputTrackRotation = 0;
|
|
37318
37425
|
encodingConfig.transform.width = width;
|
|
@@ -37322,6 +37429,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37322
37429
|
encodingConfig.transform.crop = crop;
|
|
37323
37430
|
encodingConfig.transform.alpha = alpha;
|
|
37324
37431
|
}
|
|
37432
|
+
let lastSampleTimestamp = null;
|
|
37433
|
+
encodingConfig.onEncodedSample = (sample) => {
|
|
37434
|
+
lastSampleTimestamp = sample.timestamp;
|
|
37435
|
+
};
|
|
37325
37436
|
const source = new VideoSampleSource(encodingConfig);
|
|
37326
37437
|
videoSource = source;
|
|
37327
37438
|
this._trackPromises.push((async () => {
|
|
@@ -37334,7 +37445,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37334
37445
|
}
|
|
37335
37446
|
const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
|
|
37336
37447
|
sample.setTimestamp(adjustedSampleTimestamp);
|
|
37337
|
-
|
|
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
|
+
}
|
|
37338
37455
|
sample.close();
|
|
37339
37456
|
}
|
|
37340
37457
|
source.close();
|
|
@@ -37362,52 +37479,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37362
37479
|
this._outputOwnTrackGroups.push(ownGroup);
|
|
37363
37480
|
}
|
|
37364
37481
|
/** @internal */
|
|
37365
|
-
async _registerVideoSample(trackOptions, outputTrackId, source, sample) {
|
|
37366
|
-
if (this._canceled) {
|
|
37367
|
-
return;
|
|
37368
|
-
}
|
|
37369
|
-
this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
|
|
37370
|
-
let finalSamples;
|
|
37371
|
-
if (!trackOptions.process) {
|
|
37372
|
-
finalSamples = [sample];
|
|
37373
|
-
} else {
|
|
37374
|
-
let processed = trackOptions.process(sample);
|
|
37375
|
-
if (processed instanceof Promise) processed = await processed;
|
|
37376
|
-
if (!Array.isArray(processed)) {
|
|
37377
|
-
processed = processed === null ? [] : [processed];
|
|
37378
|
-
}
|
|
37379
|
-
finalSamples = processed.map((x) => {
|
|
37380
|
-
if (x instanceof VideoSample) {
|
|
37381
|
-
return x;
|
|
37382
|
-
}
|
|
37383
|
-
if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
37384
|
-
return new VideoSample(x);
|
|
37385
|
-
}
|
|
37386
|
-
return new VideoSample(x, {
|
|
37387
|
-
timestamp: sample.timestamp,
|
|
37388
|
-
duration: sample.duration
|
|
37389
|
-
});
|
|
37390
|
-
});
|
|
37391
|
-
}
|
|
37392
|
-
try {
|
|
37393
|
-
for (const finalSample of finalSamples) {
|
|
37394
|
-
if (this._canceled) {
|
|
37395
|
-
break;
|
|
37396
|
-
}
|
|
37397
|
-
await source.add(finalSample);
|
|
37398
|
-
if (this._synchronizer.shouldWait(outputTrackId, finalSample.timestamp)) {
|
|
37399
|
-
await this._synchronizer.wait(finalSample.timestamp);
|
|
37400
|
-
}
|
|
37401
|
-
}
|
|
37402
|
-
} finally {
|
|
37403
|
-
for (const finalSample of finalSamples) {
|
|
37404
|
-
if (finalSample !== sample) {
|
|
37405
|
-
finalSample.close();
|
|
37406
|
-
}
|
|
37407
|
-
}
|
|
37408
|
-
}
|
|
37409
|
-
}
|
|
37410
|
-
/** @internal */
|
|
37411
37482
|
async _processAudioTrack(track, trackOptions, outputTrackId) {
|
|
37412
37483
|
const sourceCodec = await track.getCodec();
|
|
37413
37484
|
if (!sourceCodec) {
|
|
@@ -37424,9 +37495,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37424
37495
|
const firstTimestamp = await track.getFirstTimestamp();
|
|
37425
37496
|
let numberOfChannels = trackOptions.numberOfChannels ?? originalNumberOfChannels;
|
|
37426
37497
|
let sampleRate = trackOptions.sampleRate ?? originalSampleRate;
|
|
37427
|
-
|
|
37498
|
+
const needsTrimming = firstTimestamp < this._startTimestamp;
|
|
37499
|
+
const needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
|
|
37428
37500
|
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
|
37429
|
-
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) {
|
|
37430
37502
|
const source = new EncodedAudioPacketSource(sourceCodec);
|
|
37431
37503
|
audioSource = source;
|
|
37432
37504
|
this._trackPromises.push((async () => {
|
|
@@ -37482,7 +37554,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37482
37554
|
});
|
|
37483
37555
|
const nonPcmCodec = encodableCodecsWithDefaultParams.find((codec) => NON_PCM_AUDIO_CODECS.includes(codec));
|
|
37484
37556
|
if (nonPcmCodec) {
|
|
37485
|
-
needsResample = true;
|
|
37486
37557
|
codecOfChoice = nonPcmCodec;
|
|
37487
37558
|
numberOfChannels = FALLBACK_NUMBER_OF_CHANNELS;
|
|
37488
37559
|
sampleRate = FALLBACK_SAMPLE_RATE;
|
|
@@ -37498,38 +37569,70 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37498
37569
|
});
|
|
37499
37570
|
return;
|
|
37500
37571
|
}
|
|
37501
|
-
|
|
37502
|
-
|
|
37503
|
-
|
|
37504
|
-
|
|
37505
|
-
|
|
37506
|
-
|
|
37507
|
-
|
|
37508
|
-
|
|
37509
|
-
|
|
37510
|
-
|
|
37511
|
-
|
|
37512
|
-
|
|
37513
|
-
|
|
37514
|
-
|
|
37515
|
-
|
|
37516
|
-
|
|
37517
|
-
|
|
37518
|
-
|
|
37519
|
-
|
|
37520
|
-
|
|
37521
|
-
|
|
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) {
|
|
37522
37626
|
sample.close();
|
|
37523
|
-
|
|
37627
|
+
continue;
|
|
37524
37628
|
}
|
|
37525
|
-
sample.setTimestamp(sample.timestamp - this._startTimestamp);
|
|
37526
|
-
await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
|
|
37527
|
-
sample.close();
|
|
37528
37629
|
}
|
|
37529
|
-
|
|
37530
|
-
this.
|
|
37531
|
-
}
|
|
37532
|
-
|
|
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
|
+
})());
|
|
37533
37636
|
}
|
|
37534
37637
|
let ownGroup = null;
|
|
37535
37638
|
if (!trackOptions.group) {
|
|
@@ -37550,89 +37653,18 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37550
37653
|
this._outputOwnTrackGroups.push(ownGroup);
|
|
37551
37654
|
}
|
|
37552
37655
|
/** @internal */
|
|
37553
|
-
async _registerAudioSample(
|
|
37554
|
-
if (this._canceled) {
|
|
37555
|
-
return;
|
|
37556
|
-
}
|
|
37557
|
-
let sample = inputSample;
|
|
37558
|
-
if (trackOptions.sampleFormat !== void 0 && toInterleavedAudioFormat(sample.format) !== trackOptions.sampleFormat) {
|
|
37559
|
-
sample = audioSampleToInterleavedFormat(sample, trackOptions.sampleFormat);
|
|
37560
|
-
}
|
|
37656
|
+
async _registerAudioSample(sample, source, outputTrackId, getLastSampleTimestamp) {
|
|
37561
37657
|
this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
|
|
37562
|
-
|
|
37563
|
-
|
|
37564
|
-
|
|
37565
|
-
|
|
37566
|
-
|
|
37567
|
-
|
|
37568
|
-
if (!Array.isArray(processed)) {
|
|
37569
|
-
processed = processed === null ? [] : [processed];
|
|
37570
|
-
}
|
|
37571
|
-
if (!processed.every((x) => x instanceof AudioSample)) {
|
|
37572
|
-
throw new TypeError(
|
|
37573
|
-
"The audio process function must return an AudioSample, null, or an array of AudioSamples."
|
|
37574
|
-
);
|
|
37575
|
-
}
|
|
37576
|
-
finalSamples = processed;
|
|
37577
|
-
}
|
|
37578
|
-
try {
|
|
37579
|
-
for (const finalSample of finalSamples) {
|
|
37580
|
-
if (this._canceled) {
|
|
37581
|
-
break;
|
|
37582
|
-
}
|
|
37583
|
-
await source.add(finalSample);
|
|
37584
|
-
if (this._synchronizer.shouldWait(outputTrackId, finalSample.timestamp)) {
|
|
37585
|
-
await this._synchronizer.wait(finalSample.timestamp);
|
|
37586
|
-
}
|
|
37587
|
-
}
|
|
37588
|
-
} finally {
|
|
37589
|
-
if (sample !== inputSample) {
|
|
37590
|
-
sample.close();
|
|
37591
|
-
}
|
|
37592
|
-
for (const finalSample of finalSamples) {
|
|
37593
|
-
if (finalSample !== inputSample) {
|
|
37594
|
-
finalSample.close();
|
|
37595
|
-
}
|
|
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);
|
|
37596
37664
|
}
|
|
37597
37665
|
}
|
|
37598
37666
|
}
|
|
37599
37667
|
/** @internal */
|
|
37600
|
-
_resampleAudio(track, trackOptions, outputTrackId, codec, targetNumberOfChannels, targetSampleRate, bitrate) {
|
|
37601
|
-
const source = new AudioSampleSource({
|
|
37602
|
-
codec,
|
|
37603
|
-
bitrate
|
|
37604
|
-
});
|
|
37605
|
-
this._trackPromises.push((async () => {
|
|
37606
|
-
await this._started;
|
|
37607
|
-
const resampler = new AudioResampler({
|
|
37608
|
-
targetNumberOfChannels,
|
|
37609
|
-
targetSampleRate,
|
|
37610
|
-
startTime: this._startTimestamp,
|
|
37611
|
-
endTime: this._endTimestamp,
|
|
37612
|
-
onSample: async (sample) => {
|
|
37613
|
-
assert(sample.timestamp >= this._startTimestamp);
|
|
37614
|
-
sample.setTimestamp(sample.timestamp - this._startTimestamp);
|
|
37615
|
-
await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
|
|
37616
|
-
sample.close();
|
|
37617
|
-
}
|
|
37618
|
-
});
|
|
37619
|
-
const sink = new AudioSampleSink(track);
|
|
37620
|
-
const iterator = sink.samples(this._startTimestamp, this._endTimestamp);
|
|
37621
|
-
for await (const sample of iterator) {
|
|
37622
|
-
if (this._canceled) {
|
|
37623
|
-
sample.close();
|
|
37624
|
-
return;
|
|
37625
|
-
}
|
|
37626
|
-
await resampler.add(sample);
|
|
37627
|
-
sample.close();
|
|
37628
|
-
}
|
|
37629
|
-
await resampler.finalize();
|
|
37630
|
-
source.close();
|
|
37631
|
-
this._synchronizer.closeTrack(outputTrackId);
|
|
37632
|
-
})());
|
|
37633
|
-
return source;
|
|
37634
|
-
}
|
|
37635
|
-
/** @internal */
|
|
37636
37668
|
_reportProgress(trackId, endTimestamp) {
|
|
37637
37669
|
if (!this._computeProgress) {
|
|
37638
37670
|
return;
|