mediabunny 1.51.0 → 1.52.1
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 +566 -205
- package/dist/bundles/mediabunny.min.cjs +15 -15
- package/dist/bundles/mediabunny.min.mjs +15 -15
- package/dist/bundles/mediabunny.mjs +566 -205
- package/dist/bundles/mediabunny.node.cjs +566 -205
- package/dist/mediabunny.d.ts +117 -11
- package/dist/modules/src/conversion.d.ts +12 -2
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/conversion.js +44 -20
- package/dist/modules/src/encode.d.ts +140 -12
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/encode.js +318 -77
- package/dist/modules/src/index.d.ts +1 -1
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/index.js +11 -1
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-boxes.js +5 -2
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts +1 -0
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-muxer.js +3 -0
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-sink.js +11 -5
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/media-source.js +287 -113
- package/dist/modules/src/misc.d.ts +1 -0
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/misc.js +3 -0
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output.d.ts +1 -1
- package/dist/modules/src/output.d.ts.map +1 -1
- package/dist/modules/src/output.js +2 -0
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/source.js +20 -5
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/conversion.ts +58 -26
- package/src/encode.ts +524 -93
- package/src/index.ts +9 -0
- package/src/isobmff/isobmff-boxes.ts +7 -2
- package/src/isobmff/isobmff-muxer.ts +5 -0
- package/src/media-sink.ts +11 -5
- package/src/media-source.ts +251 -136
- package/src/misc.ts +4 -0
- package/src/output-format.ts +3 -3
- package/src/output.ts +4 -1
- package/src/source.ts +23 -5
|
@@ -558,6 +558,9 @@ var Mediabunny = (() => {
|
|
|
558
558
|
var clamp = (value, min, max) => {
|
|
559
559
|
return Math.max(min, Math.min(max, value));
|
|
560
560
|
};
|
|
561
|
+
var lerp = (from, to, t) => {
|
|
562
|
+
return from + (to - from) * t;
|
|
563
|
+
};
|
|
561
564
|
var UNDETERMINED_LANGUAGE = "und";
|
|
562
565
|
var roundIfAlmostInteger = (value) => {
|
|
563
566
|
const rounded = Math.round(value);
|
|
@@ -16837,6 +16840,9 @@ var Mediabunny = (() => {
|
|
|
16837
16840
|
}
|
|
16838
16841
|
/** @internal */
|
|
16839
16842
|
_dispose() {
|
|
16843
|
+
for (const pendingSlice of this._pendingSlices) {
|
|
16844
|
+
pendingSlice.reject(new InputDisposedError());
|
|
16845
|
+
}
|
|
16840
16846
|
this._pendingSlices.length = 0;
|
|
16841
16847
|
this._cache.length = 0;
|
|
16842
16848
|
void this._reader?.cancel();
|
|
@@ -17288,7 +17294,9 @@ var Mediabunny = (() => {
|
|
|
17288
17294
|
}
|
|
17289
17295
|
signalWorkerStoppedRunning(worker) {
|
|
17290
17296
|
worker.running = false;
|
|
17291
|
-
worker.
|
|
17297
|
+
if (!worker.aborted) {
|
|
17298
|
+
worker.pendingSlices.length = 0;
|
|
17299
|
+
}
|
|
17292
17300
|
}
|
|
17293
17301
|
/** Called when a worker reaches the end of the underlying data and must be cleaned up. */
|
|
17294
17302
|
onWorkerFinished(worker) {
|
|
@@ -17371,10 +17379,20 @@ var Mediabunny = (() => {
|
|
|
17371
17379
|
}
|
|
17372
17380
|
dispose() {
|
|
17373
17381
|
for (const worker of this.workers) {
|
|
17382
|
+
for (const slice of worker.pendingSlices) {
|
|
17383
|
+
slice.reject(new InputDisposedError());
|
|
17384
|
+
}
|
|
17385
|
+
worker.pendingSlices.length = 0;
|
|
17374
17386
|
worker.aborted = true;
|
|
17375
17387
|
}
|
|
17388
|
+
for (const queuedRead of this.queuedReads) {
|
|
17389
|
+
for (const slice of queuedRead.pendingSlices) {
|
|
17390
|
+
slice.reject(new InputDisposedError());
|
|
17391
|
+
}
|
|
17392
|
+
}
|
|
17376
17393
|
this.workers.length = 0;
|
|
17377
17394
|
this.cache.length = 0;
|
|
17395
|
+
this.queuedReads.length = 0;
|
|
17378
17396
|
this.disposed = true;
|
|
17379
17397
|
}
|
|
17380
17398
|
};
|
|
@@ -21343,8 +21361,18 @@ var Mediabunny = (() => {
|
|
|
21343
21361
|
if (!VIDEO_CODECS.includes(config.codec)) {
|
|
21344
21362
|
throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
|
|
21345
21363
|
}
|
|
21346
|
-
|
|
21347
|
-
|
|
21364
|
+
const bitrate = config.bitrate;
|
|
21365
|
+
if (config.quality === void 0 && bitrate === void 0) {
|
|
21366
|
+
throw new TypeError("config.quality must be provided.");
|
|
21367
|
+
}
|
|
21368
|
+
if (config.quality !== void 0 && bitrate !== void 0) {
|
|
21369
|
+
throw new TypeError("config.quality and config.bitrate cannot both be provided.");
|
|
21370
|
+
}
|
|
21371
|
+
if (config.quality !== void 0 && !(config.quality instanceof Quality)) {
|
|
21372
|
+
throw new TypeError("config.quality, when provided, must be a Quality.");
|
|
21373
|
+
}
|
|
21374
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
21375
|
+
throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality.");
|
|
21348
21376
|
}
|
|
21349
21377
|
if (config.keyFrameInterval !== void 0 && (!Number.isFinite(config.keyFrameInterval) || config.keyFrameInterval < 0)) {
|
|
21350
21378
|
throw new TypeError("config.keyFrameInterval, when provided, must be a non-negative number.");
|
|
@@ -21411,7 +21439,8 @@ var Mediabunny = (() => {
|
|
|
21411
21439
|
if (options.alpha !== void 0 && !["discard", "keep"].includes(options.alpha)) {
|
|
21412
21440
|
throw new TypeError("options.alpha, when provided, must be 'discard' or 'keep'.");
|
|
21413
21441
|
}
|
|
21414
|
-
|
|
21442
|
+
const bitrateMode = options.bitrateMode;
|
|
21443
|
+
if (bitrateMode !== void 0 && !["constant", "variable"].includes(bitrateMode)) {
|
|
21415
21444
|
throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'.");
|
|
21416
21445
|
}
|
|
21417
21446
|
if (options.latencyMode !== void 0 && !["quality", "realtime"].includes(options.latencyMode)) {
|
|
@@ -21437,22 +21466,28 @@ var Mediabunny = (() => {
|
|
|
21437
21466
|
throw new TypeError("contentHint, when provided, must be a string.");
|
|
21438
21467
|
}
|
|
21439
21468
|
};
|
|
21440
|
-
var
|
|
21441
|
-
const
|
|
21442
|
-
|
|
21469
|
+
var buildVideoEncoderConfigs = (options) => {
|
|
21470
|
+
const fallbackBitrateMode = options.bitrateMode;
|
|
21471
|
+
const rateControl = options.quality._toVideoRateControl(
|
|
21472
|
+
options.codec,
|
|
21473
|
+
options.width,
|
|
21474
|
+
options.height,
|
|
21475
|
+
fallbackBitrateMode
|
|
21476
|
+
);
|
|
21477
|
+
const buildConfig = (bitrate, bitrateMode, bitrateEstimate) => ({
|
|
21443
21478
|
codec: options.fullCodecString ?? buildVideoCodecString(
|
|
21444
21479
|
options.codec,
|
|
21445
21480
|
options.width,
|
|
21446
21481
|
options.height,
|
|
21447
|
-
|
|
21482
|
+
bitrateEstimate,
|
|
21448
21483
|
options.alpha === "keep"
|
|
21449
21484
|
),
|
|
21450
21485
|
width: options.width,
|
|
21451
21486
|
height: options.height,
|
|
21452
21487
|
displayWidth: options.squarePixelWidth,
|
|
21453
21488
|
displayHeight: options.squarePixelHeight,
|
|
21454
|
-
bitrate
|
|
21455
|
-
bitrateMode
|
|
21489
|
+
bitrate,
|
|
21490
|
+
bitrateMode,
|
|
21456
21491
|
alpha: options.alpha ?? "discard",
|
|
21457
21492
|
framerate: options.framerate,
|
|
21458
21493
|
latencyMode: options.latencyMode,
|
|
@@ -21460,7 +21495,22 @@ var Mediabunny = (() => {
|
|
|
21460
21495
|
scalabilityMode: options.scalabilityMode,
|
|
21461
21496
|
contentHint: options.contentHint,
|
|
21462
21497
|
...getVideoEncoderConfigExtension(options.codec)
|
|
21463
|
-
};
|
|
21498
|
+
});
|
|
21499
|
+
const candidates = [];
|
|
21500
|
+
if (rateControl.quantizer !== null) {
|
|
21501
|
+
candidates.push({
|
|
21502
|
+
config: buildConfig(void 0, "quantizer", rateControl.bitrate),
|
|
21503
|
+
quantizer: rateControl.quantizer
|
|
21504
|
+
});
|
|
21505
|
+
}
|
|
21506
|
+
if (rateControl.bitrateMode !== "quantizer") {
|
|
21507
|
+
candidates.push({
|
|
21508
|
+
config: buildConfig(rateControl.bitrate, rateControl.bitrateMode, rateControl.bitrate),
|
|
21509
|
+
quantizer: null
|
|
21510
|
+
});
|
|
21511
|
+
}
|
|
21512
|
+
assert(candidates.length > 0);
|
|
21513
|
+
return candidates;
|
|
21464
21514
|
};
|
|
21465
21515
|
var validateAudioEncodingConfig = (config) => {
|
|
21466
21516
|
if (!config || typeof config !== "object") {
|
|
@@ -21469,10 +21519,17 @@ var Mediabunny = (() => {
|
|
|
21469
21519
|
if (!AUDIO_CODECS.includes(config.codec)) {
|
|
21470
21520
|
throw new TypeError(`Invalid audio codec '${config.codec}'. Must be one of: ${AUDIO_CODECS.join(", ")}.`);
|
|
21471
21521
|
}
|
|
21472
|
-
|
|
21473
|
-
|
|
21522
|
+
const bitrate = config.bitrate;
|
|
21523
|
+
if (config.quality === void 0 && bitrate === void 0 && !(PCM_AUDIO_CODECS.includes(config.codec) || config.codec === "flac")) {
|
|
21524
|
+
throw new TypeError("config.quality must be provided for compressed audio codecs.");
|
|
21525
|
+
}
|
|
21526
|
+
if (config.quality !== void 0 && bitrate !== void 0) {
|
|
21527
|
+
throw new TypeError("config.quality and config.bitrate cannot both be provided.");
|
|
21474
21528
|
}
|
|
21475
|
-
if (config.
|
|
21529
|
+
if (config.quality !== void 0 && !(config.quality instanceof Quality)) {
|
|
21530
|
+
throw new TypeError("config.quality, when provided, must be a Quality.");
|
|
21531
|
+
}
|
|
21532
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
21476
21533
|
throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality.");
|
|
21477
21534
|
}
|
|
21478
21535
|
if (config.transform !== void 0) {
|
|
@@ -21507,7 +21564,8 @@ var Mediabunny = (() => {
|
|
|
21507
21564
|
if (!options || typeof options !== "object") {
|
|
21508
21565
|
throw new TypeError("Encoding options must be an object.");
|
|
21509
21566
|
}
|
|
21510
|
-
|
|
21567
|
+
const bitrateMode = options.bitrateMode;
|
|
21568
|
+
if (bitrateMode !== void 0 && !["constant", "variable"].includes(bitrateMode)) {
|
|
21511
21569
|
throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'.");
|
|
21512
21570
|
}
|
|
21513
21571
|
if (options.fullCodecString !== void 0 && typeof options.fullCodecString !== "string") {
|
|
@@ -21520,7 +21578,7 @@ var Mediabunny = (() => {
|
|
|
21520
21578
|
}
|
|
21521
21579
|
};
|
|
21522
21580
|
var buildAudioEncoderConfig = (options) => {
|
|
21523
|
-
const
|
|
21581
|
+
const fallbackBitrateMode = options.bitrateMode;
|
|
21524
21582
|
return {
|
|
21525
21583
|
codec: options.fullCodecString ?? buildAudioCodecString(
|
|
21526
21584
|
options.codec,
|
|
@@ -21529,46 +21587,132 @@ var Mediabunny = (() => {
|
|
|
21529
21587
|
),
|
|
21530
21588
|
numberOfChannels: options.numberOfChannels,
|
|
21531
21589
|
sampleRate: options.sampleRate,
|
|
21532
|
-
bitrate:
|
|
21533
|
-
bitrateMode: options.
|
|
21590
|
+
bitrate: options.quality?._toAudioBitrate(options.codec),
|
|
21591
|
+
bitrateMode: options.quality?._bitrateMode ?? fallbackBitrateMode,
|
|
21534
21592
|
...getAudioEncoderConfigExtension(options.codec)
|
|
21535
21593
|
};
|
|
21536
21594
|
};
|
|
21537
21595
|
var Quality = class {
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21596
|
+
constructor(options) {
|
|
21597
|
+
if (typeof options === "number" || typeof options === "string") {
|
|
21598
|
+
options = { quality: options };
|
|
21599
|
+
}
|
|
21600
|
+
if (!options || typeof options !== "object") {
|
|
21601
|
+
throw new TypeError("options must be an object.");
|
|
21602
|
+
}
|
|
21603
|
+
if (options.bitrateMode !== void 0 && !["constant", "variable"].includes(options.bitrateMode)) {
|
|
21604
|
+
throw new TypeError("options.bitrateMode, when provided, must be 'constant' or 'variable'.");
|
|
21605
|
+
}
|
|
21606
|
+
if ("quality" in options) {
|
|
21607
|
+
if (typeof options.quality === "string" ? !(options.quality in QUALITY_LEVELS) : typeof options.quality !== "number" || Number.isNaN(options.quality)) {
|
|
21608
|
+
throw new TypeError(
|
|
21609
|
+
"options.quality must be a number, or one of 'very-low', 'low', 'medium', 'high' or 'very-high'."
|
|
21610
|
+
);
|
|
21611
|
+
}
|
|
21612
|
+
if (options.preferBitrate !== void 0 && typeof options.preferBitrate !== "boolean") {
|
|
21613
|
+
throw new TypeError("options.preferBitrate, when provided, must be a boolean.");
|
|
21614
|
+
}
|
|
21615
|
+
if ("bitrate" in options || "quantizer" in options) {
|
|
21616
|
+
throw new TypeError("options.quality cannot be combined with options.bitrate or options.quantizer.");
|
|
21617
|
+
}
|
|
21618
|
+
this._quality = typeof options.quality === "string" ? QUALITY_LEVELS[options.quality] : options.quality;
|
|
21619
|
+
this._preferBitrate = options.preferBitrate ?? false;
|
|
21620
|
+
this._bitrate = void 0;
|
|
21621
|
+
this._quantizer = void 0;
|
|
21622
|
+
} else {
|
|
21623
|
+
if (options.bitrate !== void 0 && (!Number.isInteger(options.bitrate) || options.bitrate <= 0)) {
|
|
21624
|
+
throw new TypeError("options.bitrate, when provided, must be a positive integer.");
|
|
21625
|
+
}
|
|
21626
|
+
if (options.quantizer !== void 0 && (!Number.isInteger(options.quantizer) || options.quantizer < 0)) {
|
|
21627
|
+
throw new TypeError("options.quantizer, when provided, must be a non-negative integer.");
|
|
21628
|
+
}
|
|
21629
|
+
if (options.bitrate === void 0 && options.quantizer === void 0) {
|
|
21630
|
+
throw new TypeError("At least one of options.bitrate or options.quantizer must be set.");
|
|
21631
|
+
}
|
|
21632
|
+
if ("preferBitrate" in options) {
|
|
21633
|
+
throw new TypeError("options.preferBitrate can only be combined with options.quality.");
|
|
21634
|
+
}
|
|
21635
|
+
this._quality = void 0;
|
|
21636
|
+
this._preferBitrate = false;
|
|
21637
|
+
this._bitrate = options.bitrate;
|
|
21638
|
+
this._quantizer = options.quantizer;
|
|
21639
|
+
}
|
|
21640
|
+
this._bitrateMode = options.bitrateMode;
|
|
21641
|
+
}
|
|
21642
|
+
/**
|
|
21643
|
+
* Determines the rate control methods usable for the given codec.
|
|
21644
|
+
* @internal
|
|
21645
|
+
*/
|
|
21646
|
+
_toVideoRateControl(codec, width, height, fallbackBitrateMode) {
|
|
21647
|
+
const quantizerSupport = VIDEO_CODEC_QUANTIZER_SUPPORT[codec];
|
|
21648
|
+
let quantizer = null;
|
|
21649
|
+
let bitrateMode = this._bitrateMode ?? fallbackBitrateMode ?? "variable";
|
|
21650
|
+
if (this._quantizer !== void 0) {
|
|
21651
|
+
if (!quantizerSupport) {
|
|
21652
|
+
if (this._bitrate === void 0) {
|
|
21653
|
+
throw new Error(
|
|
21654
|
+
`Codec '${codec}' does not support quantizer-based encoding. Provide a bitrate in the Quality to define a fallback.`
|
|
21655
|
+
);
|
|
21656
|
+
}
|
|
21657
|
+
} else if (this._quantizer < quantizerSupport.min || this._quantizer > quantizerSupport.max) {
|
|
21658
|
+
if (this._bitrate === void 0) {
|
|
21659
|
+
throw new Error(
|
|
21660
|
+
`Quantizer ${this._quantizer} is out of range for codec '${codec}'; must be between ${quantizerSupport.min} and ${quantizerSupport.max}.`
|
|
21661
|
+
);
|
|
21662
|
+
}
|
|
21663
|
+
} else {
|
|
21664
|
+
quantizer = this._quantizer;
|
|
21665
|
+
if (this._bitrate === void 0) {
|
|
21666
|
+
bitrateMode = "quantizer";
|
|
21667
|
+
}
|
|
21668
|
+
}
|
|
21669
|
+
} else if (this._bitrate === void 0 && quantizerSupport && !this._preferBitrate) {
|
|
21670
|
+
assert(this._quality !== void 0);
|
|
21671
|
+
quantizer = clamp(
|
|
21672
|
+
Math.round(lerp(quantizerSupport.worst, quantizerSupport.best, this._quality)),
|
|
21673
|
+
quantizerSupport.min,
|
|
21674
|
+
quantizerSupport.max
|
|
21675
|
+
);
|
|
21676
|
+
}
|
|
21677
|
+
let bitrate;
|
|
21678
|
+
if (this._bitrate !== void 0) {
|
|
21679
|
+
bitrate = this._bitrate;
|
|
21680
|
+
} else {
|
|
21681
|
+
let quality = this._quality;
|
|
21682
|
+
if (quality === void 0) {
|
|
21683
|
+
assert(quantizer !== null && quantizerSupport);
|
|
21684
|
+
quality = clamp(
|
|
21685
|
+
(quantizer - quantizerSupport.worst) / (quantizerSupport.best - quantizerSupport.worst),
|
|
21686
|
+
0,
|
|
21687
|
+
1
|
|
21688
|
+
);
|
|
21689
|
+
}
|
|
21690
|
+
bitrate = computeVideoBitrate(codec, width, height, qualityToBitrateFactor(quality));
|
|
21691
|
+
}
|
|
21692
|
+
return { quantizer, bitrate, bitrateMode };
|
|
21541
21693
|
}
|
|
21542
21694
|
/** @internal */
|
|
21543
21695
|
_toVideoBitrate(codec, width, height) {
|
|
21544
|
-
|
|
21545
|
-
|
|
21546
|
-
|
|
21547
|
-
|
|
21548
|
-
|
|
21549
|
-
const codecEfficiencyFactors = {
|
|
21550
|
-
avc: 1,
|
|
21551
|
-
// H.264/AVC (baseline)
|
|
21552
|
-
hevc: 0.6,
|
|
21553
|
-
// H.265/HEVC (~40% more efficient than AVC)
|
|
21554
|
-
vp9: 0.6,
|
|
21555
|
-
// Similar to HEVC
|
|
21556
|
-
av1: 0.4,
|
|
21557
|
-
// ~60% more efficient than AVC
|
|
21558
|
-
vp8: 1.2,
|
|
21559
|
-
// Slightly less efficient than AVC
|
|
21560
|
-
prores: 22e7 / referenceBitrate
|
|
21561
|
-
// Apple ProRes white paper claims 220 Mbps for 1080p 422 HQ @30Hz
|
|
21562
|
-
};
|
|
21563
|
-
const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec];
|
|
21564
|
-
const finalBitrate = codecAdjustedBitrate * this._factor;
|
|
21565
|
-
return Math.ceil(finalBitrate / 1e3) * 1e3;
|
|
21696
|
+
if (this._bitrate !== void 0) {
|
|
21697
|
+
return this._bitrate;
|
|
21698
|
+
}
|
|
21699
|
+
assert(this._quality !== void 0);
|
|
21700
|
+
return computeVideoBitrate(codec, width, height, qualityToBitrateFactor(this._quality));
|
|
21566
21701
|
}
|
|
21567
21702
|
/** @internal */
|
|
21568
21703
|
_toAudioBitrate(codec) {
|
|
21569
21704
|
if (PCM_AUDIO_CODECS.includes(codec) || codec === "flac") {
|
|
21570
21705
|
return void 0;
|
|
21571
21706
|
}
|
|
21707
|
+
if (this._bitrate !== void 0) {
|
|
21708
|
+
return this._bitrate;
|
|
21709
|
+
}
|
|
21710
|
+
if (this._quality === void 0) {
|
|
21711
|
+
throw new Error(
|
|
21712
|
+
"This Quality defines neither a quality level nor a bitrate and therefore cannot be used for audio encoding."
|
|
21713
|
+
);
|
|
21714
|
+
}
|
|
21715
|
+
const factor = qualityToBitrateFactor(this._quality);
|
|
21572
21716
|
const baseRates = {
|
|
21573
21717
|
aac: 128e3,
|
|
21574
21718
|
// 128kbps base for AAC
|
|
@@ -21587,7 +21731,7 @@ var Mediabunny = (() => {
|
|
|
21587
21731
|
if (!baseBitrate) {
|
|
21588
21732
|
throw new Error(`Unhandled codec: ${codec}`);
|
|
21589
21733
|
}
|
|
21590
|
-
let finalBitrate = baseBitrate *
|
|
21734
|
+
let finalBitrate = baseBitrate * factor;
|
|
21591
21735
|
if (codec === "aac") {
|
|
21592
21736
|
const validRates = [96e3, 128e3, 16e4, 192e3];
|
|
21593
21737
|
finalBitrate = validRates.reduce(
|
|
@@ -21621,11 +21765,61 @@ var Mediabunny = (() => {
|
|
|
21621
21765
|
return Math.round(finalBitrate / 1e3) * 1e3;
|
|
21622
21766
|
}
|
|
21623
21767
|
};
|
|
21624
|
-
var
|
|
21625
|
-
|
|
21626
|
-
|
|
21627
|
-
|
|
21628
|
-
|
|
21768
|
+
var QUALITY_LEVELS = {
|
|
21769
|
+
"very-low": 0,
|
|
21770
|
+
"low": 0.25,
|
|
21771
|
+
"medium": 0.5,
|
|
21772
|
+
"high": 0.75,
|
|
21773
|
+
"very-high": 1
|
|
21774
|
+
};
|
|
21775
|
+
var VIDEO_CODEC_QUANTIZER_SUPPORT = {
|
|
21776
|
+
avc: { min: 0, max: 51, worst: 41, best: 16 },
|
|
21777
|
+
hevc: { min: 0, max: 51, worst: 41, best: 16 },
|
|
21778
|
+
vp9: { min: 0, max: 63, worst: 52, best: 20 },
|
|
21779
|
+
av1: { min: 0, max: 255, worst: 208, best: 80 }
|
|
21780
|
+
};
|
|
21781
|
+
var qualityToBitrateFactor = (quality) => 0.3 * Math.exp(2.5538 * quality);
|
|
21782
|
+
var computeVideoBitrate = (codec, width, height, factor) => {
|
|
21783
|
+
const pixels = width * height;
|
|
21784
|
+
const referencePixels = 1920 * 1080;
|
|
21785
|
+
const referenceBitrate = 3e6;
|
|
21786
|
+
const scaleFactor = Math.pow(pixels / referencePixels, 0.95);
|
|
21787
|
+
const baseBitrate = referenceBitrate * scaleFactor;
|
|
21788
|
+
const codecEfficiencyFactors = {
|
|
21789
|
+
avc: 1,
|
|
21790
|
+
// H.264/AVC (baseline)
|
|
21791
|
+
hevc: 0.6,
|
|
21792
|
+
// H.265/HEVC (~40% more efficient than AVC)
|
|
21793
|
+
vp9: 0.6,
|
|
21794
|
+
// Similar to HEVC
|
|
21795
|
+
av1: 0.4,
|
|
21796
|
+
// ~60% more efficient than AVC
|
|
21797
|
+
vp8: 1.2,
|
|
21798
|
+
// Slightly less efficient than AVC
|
|
21799
|
+
prores: 22e7 / referenceBitrate
|
|
21800
|
+
// Apple ProRes white paper claims 220 Mbps for 1080p 422 HQ @30Hz
|
|
21801
|
+
};
|
|
21802
|
+
const codecAdjustedBitrate = baseBitrate * codecEfficiencyFactors[codec];
|
|
21803
|
+
const finalBitrate = codecAdjustedBitrate * factor;
|
|
21804
|
+
return Math.ceil(finalBitrate / 1e3) * 1e3;
|
|
21805
|
+
};
|
|
21806
|
+
var buildQuantizerEncodeOptions = (codec, quantizer) => {
|
|
21807
|
+
if (codec === "avc") {
|
|
21808
|
+
return { avc: { quantizer } };
|
|
21809
|
+
} else if (codec === "hevc") {
|
|
21810
|
+
return { hevc: { quantizer } };
|
|
21811
|
+
} else if (codec === "vp9") {
|
|
21812
|
+
return { vp9: { quantizer } };
|
|
21813
|
+
} else if (codec === "av1") {
|
|
21814
|
+
return { av1: { quantizer } };
|
|
21815
|
+
}
|
|
21816
|
+
assert(false);
|
|
21817
|
+
};
|
|
21818
|
+
var QUALITY_VERY_LOW = /* @__PURE__ */ new Quality("very-low");
|
|
21819
|
+
var QUALITY_LOW = /* @__PURE__ */ new Quality("low");
|
|
21820
|
+
var QUALITY_MEDIUM = /* @__PURE__ */ new Quality("medium");
|
|
21821
|
+
var QUALITY_HIGH = /* @__PURE__ */ new Quality("high");
|
|
21822
|
+
var QUALITY_VERY_HIGH = /* @__PURE__ */ new Quality("very-high");
|
|
21629
21823
|
var canEncode = (codec) => {
|
|
21630
21824
|
if (VIDEO_CODECS.includes(codec)) {
|
|
21631
21825
|
return canEncodeVideo(codec);
|
|
@@ -21640,7 +21834,9 @@ var Mediabunny = (() => {
|
|
|
21640
21834
|
const {
|
|
21641
21835
|
width = 1280,
|
|
21642
21836
|
height = 720,
|
|
21643
|
-
|
|
21837
|
+
quality,
|
|
21838
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
21839
|
+
bitrate,
|
|
21644
21840
|
...restOptions
|
|
21645
21841
|
} = options;
|
|
21646
21842
|
if (!VIDEO_CODECS.includes(codec)) {
|
|
@@ -21652,28 +21848,42 @@ var Mediabunny = (() => {
|
|
|
21652
21848
|
if (!Number.isInteger(height) || height <= 0) {
|
|
21653
21849
|
throw new TypeError("height must be a positive integer.");
|
|
21654
21850
|
}
|
|
21655
|
-
if (
|
|
21851
|
+
if (quality !== void 0 && !(quality instanceof Quality)) {
|
|
21852
|
+
throw new TypeError("quality, when provided, must be a Quality.");
|
|
21853
|
+
}
|
|
21854
|
+
if (quality !== void 0 && bitrate !== void 0) {
|
|
21855
|
+
throw new TypeError("quality and bitrate cannot both be provided.");
|
|
21856
|
+
}
|
|
21857
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
21656
21858
|
throw new TypeError("bitrate must be a positive integer or a quality.");
|
|
21657
21859
|
}
|
|
21658
21860
|
validateVideoEncodingAdditionalOptions(codec, restOptions);
|
|
21659
|
-
const
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21861
|
+
const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality({ bitrate: 1e6 });
|
|
21862
|
+
let candidates;
|
|
21863
|
+
try {
|
|
21864
|
+
candidates = buildVideoEncoderConfigs({
|
|
21865
|
+
codec,
|
|
21866
|
+
width,
|
|
21867
|
+
height,
|
|
21868
|
+
quality: resolvedQuality,
|
|
21869
|
+
framerate: void 0,
|
|
21870
|
+
...restOptions,
|
|
21871
|
+
alpha: "discard"
|
|
21872
|
+
// Since we handle alpha ourselves
|
|
21873
|
+
});
|
|
21874
|
+
} catch {
|
|
21875
|
+
return false;
|
|
21876
|
+
}
|
|
21877
|
+
const key = JSON.stringify(candidates);
|
|
21670
21878
|
const memoized = canEncodeVideoMemo.get(key);
|
|
21671
21879
|
if (memoized) {
|
|
21672
21880
|
return memoized;
|
|
21673
21881
|
}
|
|
21674
21882
|
const promise = (async () => {
|
|
21675
|
-
|
|
21676
|
-
|
|
21883
|
+
for (const { config } of candidates) {
|
|
21884
|
+
if (customVideoEncoders.some((x) => x.supports(codec, config))) {
|
|
21885
|
+
return true;
|
|
21886
|
+
}
|
|
21677
21887
|
}
|
|
21678
21888
|
if (typeof VideoEncoder === "undefined") {
|
|
21679
21889
|
return false;
|
|
@@ -21682,19 +21892,22 @@ var Mediabunny = (() => {
|
|
|
21682
21892
|
if (hasOddDimension && (codec === "avc" || codec === "hevc")) {
|
|
21683
21893
|
return false;
|
|
21684
21894
|
}
|
|
21685
|
-
const
|
|
21686
|
-
|
|
21687
|
-
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21895
|
+
for (const { config, quantizer } of candidates) {
|
|
21896
|
+
const support = await VideoEncoder.isConfigSupported(config);
|
|
21897
|
+
if (!support.supported) {
|
|
21898
|
+
continue;
|
|
21899
|
+
}
|
|
21900
|
+
if (!isFirefox()) {
|
|
21901
|
+
return true;
|
|
21902
|
+
}
|
|
21903
|
+
const success = await new Promise(async (resolve) => {
|
|
21691
21904
|
try {
|
|
21692
21905
|
const encoder = new VideoEncoder({
|
|
21693
21906
|
output: () => {
|
|
21694
21907
|
},
|
|
21695
21908
|
error: () => resolve(false)
|
|
21696
21909
|
});
|
|
21697
|
-
encoder.configure(
|
|
21910
|
+
encoder.configure(config);
|
|
21698
21911
|
const frameData = new Uint8Array(width * height * 4);
|
|
21699
21912
|
const frame = new VideoFrame(frameData, {
|
|
21700
21913
|
format: "RGBA",
|
|
@@ -21702,7 +21915,10 @@ var Mediabunny = (() => {
|
|
|
21702
21915
|
codedHeight: height,
|
|
21703
21916
|
timestamp: 0
|
|
21704
21917
|
});
|
|
21705
|
-
encoder.encode(
|
|
21918
|
+
encoder.encode(
|
|
21919
|
+
frame,
|
|
21920
|
+
quantizer !== null ? buildQuantizerEncodeOptions(codec, quantizer) : void 0
|
|
21921
|
+
);
|
|
21706
21922
|
frame.close();
|
|
21707
21923
|
await encoder.flush();
|
|
21708
21924
|
resolve(true);
|
|
@@ -21710,8 +21926,11 @@ var Mediabunny = (() => {
|
|
|
21710
21926
|
resolve(false);
|
|
21711
21927
|
}
|
|
21712
21928
|
});
|
|
21929
|
+
if (success) {
|
|
21930
|
+
return true;
|
|
21931
|
+
}
|
|
21713
21932
|
}
|
|
21714
|
-
return
|
|
21933
|
+
return false;
|
|
21715
21934
|
})();
|
|
21716
21935
|
canEncodeVideoMemo.set(key, promise);
|
|
21717
21936
|
return promise;
|
|
@@ -21720,7 +21939,9 @@ var Mediabunny = (() => {
|
|
|
21720
21939
|
const {
|
|
21721
21940
|
numberOfChannels = 2,
|
|
21722
21941
|
sampleRate = 48e3,
|
|
21723
|
-
|
|
21942
|
+
quality,
|
|
21943
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
21944
|
+
bitrate,
|
|
21724
21945
|
...restOptions
|
|
21725
21946
|
} = options;
|
|
21726
21947
|
if (!AUDIO_CODECS.includes(codec)) {
|
|
@@ -21732,15 +21953,22 @@ var Mediabunny = (() => {
|
|
|
21732
21953
|
if (!Number.isInteger(sampleRate) || sampleRate <= 0) {
|
|
21733
21954
|
throw new TypeError("sampleRate must be a positive integer.");
|
|
21734
21955
|
}
|
|
21735
|
-
if (
|
|
21956
|
+
if (quality !== void 0 && !(quality instanceof Quality)) {
|
|
21957
|
+
throw new TypeError("quality, when provided, must be a Quality.");
|
|
21958
|
+
}
|
|
21959
|
+
if (quality !== void 0 && bitrate !== void 0) {
|
|
21960
|
+
throw new TypeError("quality and bitrate cannot both be provided.");
|
|
21961
|
+
}
|
|
21962
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
21736
21963
|
throw new TypeError("bitrate must be a positive integer.");
|
|
21737
21964
|
}
|
|
21738
21965
|
validateAudioEncodingAdditionalOptions(codec, restOptions);
|
|
21966
|
+
const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality({ bitrate: 128e3 });
|
|
21739
21967
|
const encoderConfig = buildAudioEncoderConfig({
|
|
21740
21968
|
codec,
|
|
21741
21969
|
numberOfChannels,
|
|
21742
21970
|
sampleRate,
|
|
21743
|
-
|
|
21971
|
+
quality: resolvedQuality,
|
|
21744
21972
|
...restOptions
|
|
21745
21973
|
});
|
|
21746
21974
|
const key = JSON.stringify(encoderConfig);
|
|
@@ -21764,6 +21992,15 @@ var Mediabunny = (() => {
|
|
|
21764
21992
|
canEncodeAudioMemo.set(key, promise);
|
|
21765
21993
|
return promise;
|
|
21766
21994
|
};
|
|
21995
|
+
var resolveQuality = (quality, bitrate) => {
|
|
21996
|
+
if (quality !== void 0) {
|
|
21997
|
+
return quality;
|
|
21998
|
+
}
|
|
21999
|
+
if (bitrate === void 0) {
|
|
22000
|
+
return void 0;
|
|
22001
|
+
}
|
|
22002
|
+
return bitrate instanceof Quality ? bitrate : new Quality({ bitrate });
|
|
22003
|
+
};
|
|
21767
22004
|
var canEncodeSubtitles = async (codec) => {
|
|
21768
22005
|
if (!SUBTITLE_CODECS.includes(codec)) {
|
|
21769
22006
|
return false;
|
|
@@ -22241,6 +22478,7 @@ var Mediabunny = (() => {
|
|
|
22241
22478
|
let decoderIsFlushed = false;
|
|
22242
22479
|
let ended = false;
|
|
22243
22480
|
let terminated = false;
|
|
22481
|
+
let decoder = null;
|
|
22244
22482
|
let outOfBandError = null;
|
|
22245
22483
|
let hasOutOfBandError = false;
|
|
22246
22484
|
const packetRetrievalOptions = {
|
|
@@ -22249,7 +22487,7 @@ var Mediabunny = (() => {
|
|
|
22249
22487
|
metadataOnly: false
|
|
22250
22488
|
};
|
|
22251
22489
|
(async () => {
|
|
22252
|
-
|
|
22490
|
+
decoder = await this._createDecoder((sample) => {
|
|
22253
22491
|
onQueueDequeue();
|
|
22254
22492
|
if (sample.timestamp >= endTimestamp) {
|
|
22255
22493
|
ended = true;
|
|
@@ -22306,7 +22544,6 @@ var Mediabunny = (() => {
|
|
|
22306
22544
|
if (!terminated && !this._track.input._disposed) {
|
|
22307
22545
|
await decoder.flush();
|
|
22308
22546
|
}
|
|
22309
|
-
decoder.close();
|
|
22310
22547
|
if (!firstSampleQueued && lastSample) {
|
|
22311
22548
|
sampleQueue.push(lastSample);
|
|
22312
22549
|
}
|
|
@@ -22318,6 +22555,8 @@ var Mediabunny = (() => {
|
|
|
22318
22555
|
hasOutOfBandError = true;
|
|
22319
22556
|
onQueueNotEmpty();
|
|
22320
22557
|
}
|
|
22558
|
+
}).finally(() => {
|
|
22559
|
+
decoder?.close();
|
|
22321
22560
|
});
|
|
22322
22561
|
const track = this._track;
|
|
22323
22562
|
const closeSamples = () => {
|
|
@@ -22374,6 +22613,7 @@ var Mediabunny = (() => {
|
|
|
22374
22613
|
let { promise: queueDequeue, resolve: onQueueDequeue } = promiseWithResolvers();
|
|
22375
22614
|
let decoderIsFlushed = false;
|
|
22376
22615
|
let terminated = false;
|
|
22616
|
+
let decoder = null;
|
|
22377
22617
|
let outOfBandError = null;
|
|
22378
22618
|
let hasOutOfBandError = false;
|
|
22379
22619
|
const pushToQueue = (sample) => {
|
|
@@ -22387,7 +22627,7 @@ var Mediabunny = (() => {
|
|
|
22387
22627
|
metadataOnly: false
|
|
22388
22628
|
};
|
|
22389
22629
|
(async () => {
|
|
22390
|
-
|
|
22630
|
+
decoder = await this._createDecoder((sample) => {
|
|
22391
22631
|
onQueueDequeue();
|
|
22392
22632
|
if (terminated) {
|
|
22393
22633
|
sample.close();
|
|
@@ -22418,6 +22658,7 @@ var Mediabunny = (() => {
|
|
|
22418
22658
|
let maxSequenceNumber = -1;
|
|
22419
22659
|
const decodePackets = async () => {
|
|
22420
22660
|
assert(lastKeyPacket);
|
|
22661
|
+
assert(decoder);
|
|
22421
22662
|
let currentPacket = lastKeyPacket;
|
|
22422
22663
|
decoder.decode(currentPacket);
|
|
22423
22664
|
while (currentPacket.sequenceNumber < maxSequenceNumber) {
|
|
@@ -22437,6 +22678,7 @@ var Mediabunny = (() => {
|
|
|
22437
22678
|
maxSequenceNumber = -1;
|
|
22438
22679
|
};
|
|
22439
22680
|
const flushDecoder = async () => {
|
|
22681
|
+
assert(decoder);
|
|
22440
22682
|
await decoder.flush();
|
|
22441
22683
|
for (let i = 0; i < timestampsOfInterest.length; i++) {
|
|
22442
22684
|
pushToQueue(null);
|
|
@@ -22474,7 +22716,6 @@ var Mediabunny = (() => {
|
|
|
22474
22716
|
}
|
|
22475
22717
|
await flushDecoder();
|
|
22476
22718
|
}
|
|
22477
|
-
decoder.close();
|
|
22478
22719
|
decoderIsFlushed = true;
|
|
22479
22720
|
onQueueNotEmpty();
|
|
22480
22721
|
})().catch((error) => {
|
|
@@ -22483,6 +22724,8 @@ var Mediabunny = (() => {
|
|
|
22483
22724
|
hasOutOfBandError = true;
|
|
22484
22725
|
onQueueNotEmpty();
|
|
22485
22726
|
}
|
|
22727
|
+
}).finally(() => {
|
|
22728
|
+
decoder?.close();
|
|
22486
22729
|
});
|
|
22487
22730
|
const track = this._track;
|
|
22488
22731
|
const closeSamples = () => {
|
|
@@ -27156,9 +27399,12 @@ var Mediabunny = (() => {
|
|
|
27156
27399
|
// Reserved
|
|
27157
27400
|
u16(1),
|
|
27158
27401
|
// Frame count
|
|
27159
|
-
Array(32).fill(0),
|
|
27160
27402
|
// Compressor name
|
|
27161
|
-
|
|
27403
|
+
u8("Mediabunny".length),
|
|
27404
|
+
// Weird Pascal-style string
|
|
27405
|
+
ascii("Mediabunny"),
|
|
27406
|
+
Array(31 - "Mediabunny".length).fill(0),
|
|
27407
|
+
u16(trackData.info.hasAlphaChannel ? 32 : 24),
|
|
27162
27408
|
// Depth
|
|
27163
27409
|
i16(65535)
|
|
27164
27410
|
// Pre-defined
|
|
@@ -29114,6 +29360,7 @@ var Mediabunny = (() => {
|
|
|
29114
29360
|
num: displayAspectWidth * decoderConfig.codedHeight,
|
|
29115
29361
|
den: displayAspectHeight * decoderConfig.codedWidth
|
|
29116
29362
|
});
|
|
29363
|
+
const hasAlphaChannel = decoderConfig.codec === "ap4h" || decoderConfig.codec === "ap4x";
|
|
29117
29364
|
const newTrackData = {
|
|
29118
29365
|
muxer: this,
|
|
29119
29366
|
track,
|
|
@@ -29123,7 +29370,8 @@ var Mediabunny = (() => {
|
|
|
29123
29370
|
height: decoderConfig.codedHeight,
|
|
29124
29371
|
pixelAspectRatio,
|
|
29125
29372
|
decoderConfig,
|
|
29126
|
-
requiresAnnexBTransformation
|
|
29373
|
+
requiresAnnexBTransformation,
|
|
29374
|
+
hasAlphaChannel
|
|
29127
29375
|
},
|
|
29128
29376
|
timescale,
|
|
29129
29377
|
samples: [],
|
|
@@ -32654,6 +32902,8 @@ ${cue.notes ?? ""}`;
|
|
|
32654
32902
|
this.customEncoder = null;
|
|
32655
32903
|
this.customEncoderCallSerializer = new CallSerializer();
|
|
32656
32904
|
this.customEncoderQueueSize = 0;
|
|
32905
|
+
// Set when the encoder uses quantizer-based rate control; carries the quantizer value applied to each frame
|
|
32906
|
+
this.defaultEncodeOptions = {};
|
|
32657
32907
|
// Alpha stuff
|
|
32658
32908
|
this.alphaEncoder = null;
|
|
32659
32909
|
this.splitter = null;
|
|
@@ -32783,18 +33033,36 @@ ${cue.notes ?? ""}`;
|
|
|
32783
33033
|
if (!Array.isArray(processed)) {
|
|
32784
33034
|
processed = [processed];
|
|
32785
33035
|
}
|
|
32786
|
-
|
|
32787
|
-
|
|
32788
|
-
|
|
33036
|
+
const mappedSamples = [];
|
|
33037
|
+
try {
|
|
33038
|
+
for (const x of processed) {
|
|
33039
|
+
if (x instanceof VideoSample) {
|
|
33040
|
+
mappedSamples.push(x);
|
|
33041
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
33042
|
+
mappedSamples.push(new VideoSample(x));
|
|
33043
|
+
} else {
|
|
33044
|
+
mappedSamples.push(new VideoSample(x, {
|
|
33045
|
+
timestamp: videoSample.timestamp,
|
|
33046
|
+
duration: videoSample.duration
|
|
33047
|
+
}));
|
|
33048
|
+
}
|
|
32789
33049
|
}
|
|
32790
|
-
|
|
32791
|
-
|
|
33050
|
+
} catch (error) {
|
|
33051
|
+
for (const sample of mappedSamples) {
|
|
33052
|
+
if (sample !== videoSample) {
|
|
33053
|
+
sample.close();
|
|
33054
|
+
}
|
|
32792
33055
|
}
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
33056
|
+
for (const x of processed) {
|
|
33057
|
+
if (x instanceof VideoSample && x !== videoSample) {
|
|
33058
|
+
x.close();
|
|
33059
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
33060
|
+
x.close();
|
|
33061
|
+
}
|
|
33062
|
+
}
|
|
33063
|
+
throw error;
|
|
33064
|
+
}
|
|
33065
|
+
samplesToEncode = mappedSamples;
|
|
32798
33066
|
} else {
|
|
32799
33067
|
samplesToEncode = [videoSample];
|
|
32800
33068
|
}
|
|
@@ -32814,7 +33082,11 @@ ${cue.notes ?? ""}`;
|
|
|
32814
33082
|
}
|
|
32815
33083
|
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
|
|
32816
33084
|
const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
|
|
32817
|
-
const mergedEncodeOptions = {
|
|
33085
|
+
const mergedEncodeOptions = {
|
|
33086
|
+
...this.defaultEncodeOptions,
|
|
33087
|
+
...sampleToEncode.encodeOptions,
|
|
33088
|
+
...encodeOptions
|
|
33089
|
+
};
|
|
32818
33090
|
const finalEncodeOptions = {
|
|
32819
33091
|
...mergedEncodeOptions,
|
|
32820
33092
|
keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
|
|
@@ -32860,22 +33132,31 @@ ${cue.notes ?? ""}`;
|
|
|
32860
33132
|
}
|
|
32861
33133
|
}
|
|
32862
33134
|
if (!this.alphaEncoder) {
|
|
32863
|
-
|
|
32864
|
-
|
|
33135
|
+
try {
|
|
33136
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
33137
|
+
} finally {
|
|
33138
|
+
videoFrame.close();
|
|
33139
|
+
}
|
|
32865
33140
|
} else {
|
|
32866
33141
|
const frameDefinitelyHasNoAlpha = !!videoFrame.format && !videoFrame.format.includes("A");
|
|
32867
33142
|
if (frameDefinitelyHasNoAlpha || this.splitterCreationFailed) {
|
|
32868
33143
|
this.alphaFrameQueue.push(null);
|
|
32869
|
-
|
|
32870
|
-
|
|
33144
|
+
try {
|
|
33145
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
33146
|
+
} finally {
|
|
33147
|
+
videoFrame.close();
|
|
33148
|
+
}
|
|
32871
33149
|
} else {
|
|
32872
33150
|
if (!this.splitter) {
|
|
32873
33151
|
this.splitter = new ColorAlphaSplitter();
|
|
32874
33152
|
}
|
|
32875
33153
|
const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame);
|
|
32876
33154
|
this.alphaFrameQueue.push(alphaFrame);
|
|
32877
|
-
|
|
32878
|
-
|
|
33155
|
+
try {
|
|
33156
|
+
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
33157
|
+
} finally {
|
|
33158
|
+
colorFrame.close();
|
|
33159
|
+
}
|
|
32879
33160
|
}
|
|
32880
33161
|
}
|
|
32881
33162
|
if (this.encoder.encodeQueueSize >= 4) {
|
|
@@ -32900,28 +33181,83 @@ ${cue.notes ?? ""}`;
|
|
|
32900
33181
|
assert(this.frameRateLastSample);
|
|
32901
33182
|
const frameDifference = Math.round((until - this.frameRateLastTimestamp) * frameRate);
|
|
32902
33183
|
for (let i = 1; i < frameDifference; i++) {
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
|
|
32906
|
-
|
|
32907
|
-
|
|
33184
|
+
var _stack = [];
|
|
33185
|
+
try {
|
|
33186
|
+
const sample = __using(_stack, this.frameRateLastSample.clone());
|
|
33187
|
+
sample.setTimestamp(this.frameRateLastTimestamp + i / frameRate);
|
|
33188
|
+
sample.setDuration(1 / frameRate);
|
|
33189
|
+
await this.processAndEncode(sample, encodeOptions);
|
|
33190
|
+
} catch (_) {
|
|
33191
|
+
var _error = _, _hasError = true;
|
|
33192
|
+
} finally {
|
|
33193
|
+
__callDispose(_stack, _error, _hasError);
|
|
33194
|
+
}
|
|
32908
33195
|
}
|
|
32909
33196
|
}
|
|
32910
33197
|
ensureEncoder(videoSample) {
|
|
32911
33198
|
this.ensureEncoderPromise = (async () => {
|
|
32912
|
-
const
|
|
33199
|
+
const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
|
|
33200
|
+
assert(quality !== void 0);
|
|
33201
|
+
const candidates = buildVideoEncoderConfigs({
|
|
32913
33202
|
...this.encodingConfig,
|
|
33203
|
+
quality,
|
|
32914
33204
|
width: videoSample.codedWidth,
|
|
32915
33205
|
height: videoSample.codedHeight,
|
|
32916
33206
|
squarePixelWidth: videoSample.squarePixelWidth,
|
|
32917
33207
|
squarePixelHeight: videoSample.squarePixelHeight,
|
|
32918
33208
|
framerate: this.source._connectedTrack?.metadata.frameRate
|
|
32919
33209
|
});
|
|
32920
|
-
|
|
32921
|
-
|
|
32922
|
-
|
|
32923
|
-
|
|
32924
|
-
|
|
33210
|
+
let selected = null;
|
|
33211
|
+
let MatchingCustomEncoder;
|
|
33212
|
+
for (const candidate of candidates) {
|
|
33213
|
+
const candidateConfig = candidate.config;
|
|
33214
|
+
this.encodingConfig.onEncoderConfig?.(candidateConfig);
|
|
33215
|
+
MatchingCustomEncoder = customVideoEncoders.find((x) => x.supports(
|
|
33216
|
+
this.encodingConfig.codec,
|
|
33217
|
+
candidateConfig
|
|
33218
|
+
));
|
|
33219
|
+
if (MatchingCustomEncoder) {
|
|
33220
|
+
selected = candidate;
|
|
33221
|
+
break;
|
|
33222
|
+
}
|
|
33223
|
+
if (typeof VideoEncoder === "undefined") {
|
|
33224
|
+
continue;
|
|
33225
|
+
}
|
|
33226
|
+
candidateConfig.alpha = "discard";
|
|
33227
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
33228
|
+
candidateConfig.latencyMode = "quality";
|
|
33229
|
+
}
|
|
33230
|
+
const hasOddDimension = candidateConfig.width % 2 === 1 || candidateConfig.height % 2 === 1;
|
|
33231
|
+
if (hasOddDimension && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) {
|
|
33232
|
+
throw new Error(
|
|
33233
|
+
`The dimensions ${candidateConfig.width}x${candidateConfig.height} are not supported for codec '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to round your dimensions to the nearest even number.`
|
|
33234
|
+
);
|
|
33235
|
+
}
|
|
33236
|
+
const support = await VideoEncoder.isConfigSupported(candidateConfig);
|
|
33237
|
+
if (support.supported) {
|
|
33238
|
+
selected = candidate;
|
|
33239
|
+
break;
|
|
33240
|
+
}
|
|
33241
|
+
}
|
|
33242
|
+
if (!selected) {
|
|
33243
|
+
if (typeof VideoEncoder === "undefined") {
|
|
33244
|
+
throw new Error("VideoEncoder is not supported by this browser.");
|
|
33245
|
+
}
|
|
33246
|
+
const firstConfig = candidates[0].config;
|
|
33247
|
+
const rateControls = candidates.map(
|
|
33248
|
+
({ config, quantizer }) => quantizer !== null ? `quantizer ${quantizer}` : `${config.bitrate} bps`
|
|
33249
|
+
);
|
|
33250
|
+
throw new Error(
|
|
33251
|
+
`This specific encoder configuration (${firstConfig.codec}, ${rateControls.join(" / ")}, ${firstConfig.width}x${firstConfig.height}, hardware acceleration: ${firstConfig.hardwareAcceleration ?? "no-preference"}) is not supported by this browser. Consider using another codec or changing your video parameters.`
|
|
33252
|
+
);
|
|
33253
|
+
}
|
|
33254
|
+
const encoderConfig = selected.config;
|
|
33255
|
+
if (selected.quantizer !== null) {
|
|
33256
|
+
this.defaultEncodeOptions = buildQuantizerEncodeOptions(
|
|
33257
|
+
this.encodingConfig.codec,
|
|
33258
|
+
selected.quantizer
|
|
33259
|
+
);
|
|
33260
|
+
}
|
|
32925
33261
|
if (MatchingCustomEncoder) {
|
|
32926
33262
|
this.customEncoder = new MatchingCustomEncoder();
|
|
32927
33263
|
this.customEncoder.codec = this.encodingConfig.codec;
|
|
@@ -32944,25 +33280,6 @@ ${cue.notes ?? ""}`;
|
|
|
32944
33280
|
};
|
|
32945
33281
|
await this.customEncoder.init();
|
|
32946
33282
|
} else {
|
|
32947
|
-
if (typeof VideoEncoder === "undefined") {
|
|
32948
|
-
throw new Error("VideoEncoder is not supported by this browser.");
|
|
32949
|
-
}
|
|
32950
|
-
encoderConfig.alpha = "discard";
|
|
32951
|
-
if (this.encodingConfig.alpha === "keep") {
|
|
32952
|
-
encoderConfig.latencyMode = "quality";
|
|
32953
|
-
}
|
|
32954
|
-
const hasOddDimension = encoderConfig.width % 2 === 1 || encoderConfig.height % 2 === 1;
|
|
32955
|
-
if (hasOddDimension && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) {
|
|
32956
|
-
throw new Error(
|
|
32957
|
-
`The dimensions ${encoderConfig.width}x${encoderConfig.height} are not supported for codec '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to round your dimensions to the nearest even number.`
|
|
32958
|
-
);
|
|
32959
|
-
}
|
|
32960
|
-
const support = await VideoEncoder.isConfigSupported(encoderConfig);
|
|
32961
|
-
if (!support.supported) {
|
|
32962
|
-
throw new Error(
|
|
32963
|
-
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps, ${encoderConfig.width}x${encoderConfig.height}, hardware acceleration: ${encoderConfig.hardwareAcceleration ?? "no-preference"}) is not supported by this browser. Consider using another codec or changing your video parameters.`
|
|
32964
|
-
);
|
|
32965
|
-
}
|
|
32966
33283
|
const colorChunkQueue = [];
|
|
32967
33284
|
const nullAlphaChunkQueue = [];
|
|
32968
33285
|
let encodedAlphaChunkCount = 0;
|
|
@@ -33014,6 +33331,7 @@ ${cue.notes ?? ""}`;
|
|
|
33014
33331
|
assert(alphaFrame !== void 0);
|
|
33015
33332
|
if (alphaFrame) {
|
|
33016
33333
|
this.alphaEncoder.encode(alphaFrame, {
|
|
33334
|
+
...this.defaultEncodeOptions,
|
|
33017
33335
|
// Crucial: The alpha frame is forced to be a key frame whenever the color frame
|
|
33018
33336
|
// also is. Without this, playback can glitch and even crash in some browsers.
|
|
33019
33337
|
// This is the reason why the two encoders are wired in series and not in parallel.
|
|
@@ -33069,36 +33387,42 @@ ${cue.notes ?? ""}`;
|
|
|
33069
33387
|
})();
|
|
33070
33388
|
}
|
|
33071
33389
|
async flushAndClose(forceClose) {
|
|
33072
|
-
|
|
33073
|
-
this.checkForEncoderError();
|
|
33074
|
-
}
|
|
33075
|
-
if (!forceClose && this.frameRateLastSample) {
|
|
33076
|
-
const frameRate = this.encodingConfig.transform.frameRate;
|
|
33077
|
-
const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
|
|
33078
|
-
await this.padFrameRate(alignedEnd);
|
|
33079
|
-
}
|
|
33080
|
-
this.closed = true;
|
|
33081
|
-
this.frameRateLastSample?.close();
|
|
33082
|
-
this.frameRateLastSample = null;
|
|
33083
|
-
if (this.customEncoder) {
|
|
33390
|
+
try {
|
|
33084
33391
|
if (!forceClose) {
|
|
33085
|
-
|
|
33392
|
+
this.checkForEncoderError();
|
|
33393
|
+
if (this.frameRateLastSample) {
|
|
33394
|
+
const frameRate = this.encodingConfig.transform.frameRate;
|
|
33395
|
+
const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
|
|
33396
|
+
await this.padFrameRate(alignedEnd);
|
|
33397
|
+
}
|
|
33086
33398
|
}
|
|
33087
|
-
|
|
33088
|
-
} else if (this.encoder) {
|
|
33399
|
+
this.closed = true;
|
|
33089
33400
|
if (!forceClose) {
|
|
33090
|
-
|
|
33091
|
-
|
|
33092
|
-
|
|
33093
|
-
|
|
33094
|
-
|
|
33095
|
-
|
|
33401
|
+
if (this.customEncoder) {
|
|
33402
|
+
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
33403
|
+
} else if (this.encoder) {
|
|
33404
|
+
await this.encoder.flush();
|
|
33405
|
+
await this.alphaEncoder?.flush();
|
|
33406
|
+
await wait(25);
|
|
33407
|
+
}
|
|
33096
33408
|
}
|
|
33097
|
-
|
|
33098
|
-
|
|
33409
|
+
} finally {
|
|
33410
|
+
this.closed = true;
|
|
33411
|
+
this.frameRateLastSample?.close();
|
|
33412
|
+
this.frameRateLastSample = null;
|
|
33413
|
+
if (this.customEncoder) {
|
|
33414
|
+
await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error));
|
|
33415
|
+
} else if (this.encoder) {
|
|
33416
|
+
if (this.encoder.state !== "closed") {
|
|
33417
|
+
this.encoder.close();
|
|
33418
|
+
}
|
|
33419
|
+
if (this.alphaEncoder && this.alphaEncoder.state !== "closed") {
|
|
33420
|
+
this.alphaEncoder.close();
|
|
33421
|
+
}
|
|
33422
|
+
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
33423
|
+
this.alphaFrameQueue.length = 0;
|
|
33424
|
+
this.splitter?.close();
|
|
33099
33425
|
}
|
|
33100
|
-
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
33101
|
-
this.splitter?.close();
|
|
33102
33426
|
}
|
|
33103
33427
|
if (!forceClose) {
|
|
33104
33428
|
this.checkForEncoderError();
|
|
@@ -33762,26 +34086,39 @@ ${cue.notes ?? ""}`;
|
|
|
33762
34086
|
shouldClose = true;
|
|
33763
34087
|
}
|
|
33764
34088
|
if (config.transform?.process) {
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
processed
|
|
33768
|
-
|
|
33769
|
-
|
|
33770
|
-
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
processed
|
|
33774
|
-
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
|
|
33778
|
-
|
|
33779
|
-
|
|
34089
|
+
try {
|
|
34090
|
+
let processed = config.transform.process(audioSample);
|
|
34091
|
+
if (processed instanceof Promise) {
|
|
34092
|
+
processed = await processed;
|
|
34093
|
+
}
|
|
34094
|
+
if (processed === null) {
|
|
34095
|
+
return;
|
|
34096
|
+
}
|
|
34097
|
+
if (!Array.isArray(processed)) {
|
|
34098
|
+
processed = [processed];
|
|
34099
|
+
}
|
|
34100
|
+
try {
|
|
34101
|
+
for (const sample of processed) {
|
|
34102
|
+
if (!(sample instanceof AudioSample)) {
|
|
34103
|
+
throw new TypeError(
|
|
34104
|
+
"The audio process function must return an AudioSample, null, or an array of AudioSamples."
|
|
34105
|
+
);
|
|
34106
|
+
}
|
|
34107
|
+
}
|
|
34108
|
+
for (const sample of processed) {
|
|
34109
|
+
await this.encodeSample(sample, true);
|
|
34110
|
+
}
|
|
34111
|
+
} finally {
|
|
34112
|
+
for (const sample of processed) {
|
|
34113
|
+
if (sample instanceof AudioSample) {
|
|
34114
|
+
sample.close();
|
|
34115
|
+
}
|
|
34116
|
+
}
|
|
34117
|
+
}
|
|
34118
|
+
} finally {
|
|
34119
|
+
if (shouldClose) {
|
|
34120
|
+
audioSample.close();
|
|
33780
34121
|
}
|
|
33781
|
-
await this.encodeSample(sample, true);
|
|
33782
|
-
}
|
|
33783
|
-
if (shouldClose) {
|
|
33784
|
-
audioSample.close();
|
|
33785
34122
|
}
|
|
33786
34123
|
} else {
|
|
33787
34124
|
await this.encodeSample(audioSample, shouldClose);
|
|
@@ -33917,10 +34254,12 @@ ${cue.notes ?? ""}`;
|
|
|
33917
34254
|
ensureEncoder(audioSample) {
|
|
33918
34255
|
this.ensureEncoderPromise = (async () => {
|
|
33919
34256
|
const { numberOfChannels, sampleRate } = audioSample;
|
|
34257
|
+
const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
|
|
33920
34258
|
const encoderConfig = buildAudioEncoderConfig({
|
|
33921
34259
|
numberOfChannels,
|
|
33922
34260
|
sampleRate,
|
|
33923
|
-
...this.encodingConfig
|
|
34261
|
+
...this.encodingConfig,
|
|
34262
|
+
quality
|
|
33924
34263
|
});
|
|
33925
34264
|
this.encodingConfig.onEncoderConfig?.(encoderConfig);
|
|
33926
34265
|
const MatchingCustomEncoder = customAudioEncoders.find((x) => x.supports(
|
|
@@ -34099,24 +34438,27 @@ ${cue.notes ?? ""}`;
|
|
|
34099
34438
|
}
|
|
34100
34439
|
}
|
|
34101
34440
|
async flushAndClose(forceClose) {
|
|
34102
|
-
|
|
34103
|
-
this.checkForEncoderError();
|
|
34104
|
-
}
|
|
34105
|
-
if (!forceClose && this.resampler) {
|
|
34106
|
-
await this.resampler.finalize();
|
|
34107
|
-
}
|
|
34108
|
-
this.resampler = null;
|
|
34109
|
-
this.closed = true;
|
|
34110
|
-
if (this.customEncoder) {
|
|
34441
|
+
try {
|
|
34111
34442
|
if (!forceClose) {
|
|
34112
|
-
|
|
34443
|
+
this.checkForEncoderError();
|
|
34444
|
+
if (this.resampler) {
|
|
34445
|
+
await this.resampler.finalize();
|
|
34446
|
+
}
|
|
34113
34447
|
}
|
|
34114
|
-
|
|
34115
|
-
} else if (this.encoder) {
|
|
34448
|
+
this.closed = true;
|
|
34116
34449
|
if (!forceClose) {
|
|
34117
|
-
|
|
34450
|
+
if (this.customEncoder) {
|
|
34451
|
+
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
34452
|
+
} else if (this.encoder) {
|
|
34453
|
+
await this.encoder.flush();
|
|
34454
|
+
}
|
|
34118
34455
|
}
|
|
34119
|
-
|
|
34456
|
+
} finally {
|
|
34457
|
+
this.closed = true;
|
|
34458
|
+
this.resampler = null;
|
|
34459
|
+
if (this.customEncoder) {
|
|
34460
|
+
await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error));
|
|
34461
|
+
} else if (this.encoder && this.encoder.state !== "closed") {
|
|
34120
34462
|
this.encoder.close();
|
|
34121
34463
|
}
|
|
34122
34464
|
}
|
|
@@ -36801,6 +37143,9 @@ ${cue.notes ?? ""}`;
|
|
|
36801
37143
|
}
|
|
36802
37144
|
this.state = "finalized";
|
|
36803
37145
|
} finally {
|
|
37146
|
+
await Promise.all([...this._unfinalizedTargets].map((target) => target._close().catch(() => {
|
|
37147
|
+
})));
|
|
37148
|
+
this._unfinalizedTargets.clear();
|
|
36804
37149
|
release();
|
|
36805
37150
|
}
|
|
36806
37151
|
})();
|
|
@@ -36823,7 +37168,14 @@ ${cue.notes ?? ""}`;
|
|
|
36823
37168
|
`options.video.codec, when provided, must be one of: ${VIDEO_CODECS.join(", ")}.`
|
|
36824
37169
|
);
|
|
36825
37170
|
}
|
|
36826
|
-
|
|
37171
|
+
const bitrate = videoOptions?.bitrate;
|
|
37172
|
+
if (videoOptions?.quality !== void 0 && !(videoOptions.quality instanceof Quality)) {
|
|
37173
|
+
throw new TypeError("options.video.quality, when provided, must be a Quality.");
|
|
37174
|
+
}
|
|
37175
|
+
if (videoOptions?.quality !== void 0 && bitrate !== void 0) {
|
|
37176
|
+
throw new TypeError("options.video.quality and options.video.bitrate cannot both be provided.");
|
|
37177
|
+
}
|
|
37178
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
36827
37179
|
throw new TypeError("options.video.bitrate, when provided, must be a positive integer or a quality.");
|
|
36828
37180
|
}
|
|
36829
37181
|
if (videoOptions?.width !== void 0 && (!Number.isInteger(videoOptions.width) || videoOptions.width <= 0)) {
|
|
@@ -36893,7 +37245,14 @@ ${cue.notes ?? ""}`;
|
|
|
36893
37245
|
`options.audio.codec, when provided, must be one of: ${AUDIO_CODECS.join(", ")}.`
|
|
36894
37246
|
);
|
|
36895
37247
|
}
|
|
36896
|
-
|
|
37248
|
+
const bitrate = audioOptions?.bitrate;
|
|
37249
|
+
if (audioOptions?.quality !== void 0 && !(audioOptions.quality instanceof Quality)) {
|
|
37250
|
+
throw new TypeError("options.audio.quality, when provided, must be a Quality.");
|
|
37251
|
+
}
|
|
37252
|
+
if (audioOptions?.quality !== void 0 && bitrate !== void 0) {
|
|
37253
|
+
throw new TypeError("options.audio.quality and options.audio.bitrate cannot both be provided.");
|
|
37254
|
+
}
|
|
37255
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
36897
37256
|
throw new TypeError("options.audio.bitrate, when provided, must be a positive integer or a quality.");
|
|
36898
37257
|
}
|
|
36899
37258
|
if (audioOptions?.numberOfChannels !== void 0 && (!Number.isInteger(audioOptions.numberOfChannels) || audioOptions.numberOfChannels <= 0)) {
|
|
@@ -37277,24 +37636,26 @@ ${cue.notes ?? ""}`;
|
|
|
37277
37636
|
if (encodabilityIsTheProblem) {
|
|
37278
37637
|
const codecs = this.discardedTracks.flatMap((x) => {
|
|
37279
37638
|
if (x.reason === "discarded_by_user") return [];
|
|
37639
|
+
let supportedCodecs;
|
|
37280
37640
|
if (x.track.type === "video") {
|
|
37281
|
-
|
|
37641
|
+
supportedCodecs = this.output.format.getSupportedVideoCodecs();
|
|
37282
37642
|
} else if (x.track.type === "audio") {
|
|
37283
|
-
|
|
37643
|
+
supportedCodecs = this.output.format.getSupportedAudioCodecs();
|
|
37284
37644
|
} else {
|
|
37285
|
-
|
|
37645
|
+
supportedCodecs = this.output.format.getSupportedSubtitleCodecs();
|
|
37286
37646
|
}
|
|
37647
|
+
return supportedCodecs.filter((codec) => !x.trackOptions.codec || codec === x.trackOptions.codec);
|
|
37287
37648
|
});
|
|
37288
37649
|
const uniqueCodecs = [...new Set(codecs)];
|
|
37289
37650
|
if (uniqueCodecs.length === 1) {
|
|
37290
37651
|
elements.push(
|
|
37291
37652
|
`
|
|
37292
|
-
Tracks were discarded because your environment is not able to encode '${uniqueCodecs[0]}'.`
|
|
37653
|
+
Tracks were discarded because your environment is not able to encode '${uniqueCodecs[0]}' with the provided parameters.`
|
|
37293
37654
|
);
|
|
37294
37655
|
} else {
|
|
37295
37656
|
elements.push(
|
|
37296
37657
|
`
|
|
37297
|
-
Tracks were discarded because your environment is not able to encode any of the
|
|
37658
|
+
Tracks were discarded because your environment is not able to encode any of the codecs ${uniqueCodecs.map((x) => `'${x}'`).join(", ")} with the provided parameters.`
|
|
37298
37659
|
);
|
|
37299
37660
|
}
|
|
37300
37661
|
if (uniqueCodecs.includes("mp3")) {
|
|
@@ -37497,7 +37858,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37497
37858
|
}
|
|
37498
37859
|
const firstTimestamp = await track.getFirstTimestamp();
|
|
37499
37860
|
let videoCodecs = this.output.format.getSupportedVideoCodecs();
|
|
37500
|
-
const needsTranscode = !!trackOptions.forceTranscode || firstTimestamp < this._startTimestamp || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || !!crop;
|
|
37861
|
+
const needsTranscode = !!trackOptions.forceTranscode || firstTimestamp < this._startTimestamp || !!trackOptions.frameRate || trackOptions.keyFrameInterval !== void 0 || trackOptions.process !== void 0 || trackOptions.quality !== void 0 || trackOptions.bitrate !== void 0 || !videoCodecs.includes(sourceCodec) || trackOptions.codec && trackOptions.codec !== sourceCodec || width !== originalWidth || height !== originalHeight || totalRotation !== 0 && !canUseRotationMetadata || !!crop;
|
|
37501
37862
|
const alpha = trackOptions.alpha ?? "discard";
|
|
37502
37863
|
if (!needsTranscode) {
|
|
37503
37864
|
const source = new EncodedVideoPacketSource(sourceCodec);
|
|
@@ -37541,11 +37902,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37541
37902
|
if (trackOptions.codec) {
|
|
37542
37903
|
videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec);
|
|
37543
37904
|
}
|
|
37544
|
-
const
|
|
37905
|
+
const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high");
|
|
37545
37906
|
const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, {
|
|
37546
37907
|
width: trackOptions.process && trackOptions.processedWidth ? trackOptions.processedWidth : width,
|
|
37547
37908
|
height: trackOptions.process && trackOptions.processedHeight ? trackOptions.processedHeight : height,
|
|
37548
|
-
|
|
37909
|
+
quality
|
|
37549
37910
|
});
|
|
37550
37911
|
if (!encodableCodec) {
|
|
37551
37912
|
this.discardedTracks.push({
|
|
@@ -37557,7 +37918,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37557
37918
|
}
|
|
37558
37919
|
const encodingConfig = {
|
|
37559
37920
|
codec: encodableCodec,
|
|
37560
|
-
|
|
37921
|
+
quality,
|
|
37561
37922
|
keyFrameInterval: trackOptions.keyFrameInterval,
|
|
37562
37923
|
sizeChangeBehavior: trackOptions.fit ?? "passThrough",
|
|
37563
37924
|
alpha,
|
|
@@ -37691,7 +38052,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37691
38052
|
const needsTrimming = firstTimestamp < this._startTimestamp;
|
|
37692
38053
|
let needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
|
|
37693
38054
|
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
|
37694
|
-
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) {
|
|
38055
|
+
if (!trackOptions.forceTranscode && !trackOptions.quality && !trackOptions.bitrate && numberOfChannels === originalNumberOfChannels && sampleRate === originalSampleRate && !needsTrimming && !needsPadding && audioCodecs.includes(sourceCodec) && (!trackOptions.codec || trackOptions.codec === sourceCodec) && !trackOptions.process && trackOptions.sampleFormat === void 0) {
|
|
37695
38056
|
const source = new EncodedAudioPacketSource(sourceCodec);
|
|
37696
38057
|
audioSource = source;
|
|
37697
38058
|
this._registerTrackPump(async (pump) => {
|
|
@@ -37733,17 +38094,17 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37733
38094
|
if (trackOptions.codec) {
|
|
37734
38095
|
audioCodecs = audioCodecs.filter((codec) => codec === trackOptions.codec);
|
|
37735
38096
|
}
|
|
37736
|
-
const
|
|
38097
|
+
const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high");
|
|
37737
38098
|
const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, {
|
|
37738
38099
|
numberOfChannels: trackOptions.process && trackOptions.processedNumberOfChannels ? trackOptions.processedNumberOfChannels : numberOfChannels,
|
|
37739
38100
|
sampleRate: trackOptions.process && trackOptions.processedSampleRate ? trackOptions.processedSampleRate : sampleRate,
|
|
37740
|
-
|
|
38101
|
+
quality
|
|
37741
38102
|
});
|
|
37742
38103
|
if (!encodableCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && audioCodecs.some((codec) => NON_PCM_AUDIO_CODECS.includes(codec)) && (numberOfChannels !== FALLBACK_NUMBER_OF_CHANNELS || sampleRate !== FALLBACK_SAMPLE_RATE)) {
|
|
37743
38104
|
const encodableCodecsWithDefaultParams = await getEncodableAudioCodecs(audioCodecs, {
|
|
37744
38105
|
numberOfChannels: FALLBACK_NUMBER_OF_CHANNELS,
|
|
37745
38106
|
sampleRate: FALLBACK_SAMPLE_RATE,
|
|
37746
|
-
|
|
38107
|
+
quality
|
|
37747
38108
|
});
|
|
37748
38109
|
const nonPcmCodec = encodableCodecsWithDefaultParams.find((codec) => NON_PCM_AUDIO_CODECS.includes(codec));
|
|
37749
38110
|
if (nonPcmCodec) {
|
|
@@ -37764,7 +38125,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37764
38125
|
}
|
|
37765
38126
|
const encodingConfig = {
|
|
37766
38127
|
codec: codecOfChoice,
|
|
37767
|
-
|
|
38128
|
+
quality,
|
|
37768
38129
|
transform: {
|
|
37769
38130
|
sampleFormat: trackOptions.sampleFormat,
|
|
37770
38131
|
process: trackOptions.process
|