mediabunny 1.50.9 → 1.52.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 +848 -329
- package/dist/bundles/mediabunny.min.cjs +16 -16
- package/dist/bundles/mediabunny.min.mjs +16 -16
- package/dist/bundles/mediabunny.mjs +848 -329
- package/dist/bundles/mediabunny.node.cjs +848 -329
- package/dist/mediabunny.d.ts +174 -15
- package/dist/modules/src/conversion.d.ts +59 -6
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/conversion.js +289 -131
- 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/hls/hls-muxer.js +5 -5
- package/dist/modules/src/index.d.ts +2 -2
- 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 +4 -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 +6 -3
- package/dist/modules/src/matroska/matroska-muxer.js +1 -1
- 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/mpeg-ts/mpeg-ts-muxer.js +1 -1
- package/dist/modules/src/ogg/ogg-muxer.js +1 -1
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output.d.ts +10 -1
- package/dist/modules/src/output.d.ts.map +1 -1
- package/dist/modules/src/output.js +36 -13
- 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 +390 -153
- package/src/encode.ts +524 -93
- package/src/hls/hls-muxer.ts +5 -5
- package/src/index.ts +10 -0
- package/src/isobmff/isobmff-boxes.ts +6 -2
- package/src/isobmff/isobmff-muxer.ts +8 -3
- package/src/matroska/matroska-muxer.ts +1 -1
- package/src/media-sink.ts +11 -5
- package/src/media-source.ts +251 -136
- package/src/misc.ts +4 -0
- package/src/mpeg-ts/mpeg-ts-muxer.ts +1 -1
- package/src/ogg/ogg-muxer.ts +1 -1
- package/src/output-format.ts +3 -3
- package/src/output.ts +45 -14
- 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.");
|
|
21528
|
+
}
|
|
21529
|
+
if (config.quality !== void 0 && !(config.quality instanceof Quality)) {
|
|
21530
|
+
throw new TypeError("config.quality, when provided, must be a Quality.");
|
|
21474
21531
|
}
|
|
21475
|
-
if (
|
|
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,10 @@ var Mediabunny = (() => {
|
|
|
27156
27399
|
// Reserved
|
|
27157
27400
|
u16(1),
|
|
27158
27401
|
// Frame count
|
|
27159
|
-
Array(32).fill(0),
|
|
27160
27402
|
// Compressor name
|
|
27161
|
-
|
|
27403
|
+
ascii("Mediabunny"),
|
|
27404
|
+
Array(32 - "Mediabunny".length).fill(0),
|
|
27405
|
+
u16(trackData.info.hasAlphaChannel ? 32 : 24),
|
|
27162
27406
|
// Depth
|
|
27163
27407
|
i16(65535)
|
|
27164
27408
|
// Pre-defined
|
|
@@ -29000,7 +29244,7 @@ var Mediabunny = (() => {
|
|
|
29000
29244
|
this.initWriter = initWriter;
|
|
29001
29245
|
this.initBoxWriter = new IsobmffBoxWriter(initWriter);
|
|
29002
29246
|
}
|
|
29003
|
-
const holdsAvc = this.output.
|
|
29247
|
+
const holdsAvc = this.output.tracks.some((x) => x.isVideoTrack() && x.source._codec === "avc");
|
|
29004
29248
|
{
|
|
29005
29249
|
const boxWriter = this.initBoxWriter ?? this.boxWriter;
|
|
29006
29250
|
assert(boxWriter);
|
|
@@ -29024,7 +29268,7 @@ var Mediabunny = (() => {
|
|
|
29024
29268
|
}
|
|
29025
29269
|
if (this.fastStart === "in-memory") {
|
|
29026
29270
|
} else if (this.fastStart === "reserve") {
|
|
29027
|
-
for (const track of this.output.
|
|
29271
|
+
for (const track of this.output.tracks) {
|
|
29028
29272
|
if (track.metadata.maximumPacketCount === void 0) {
|
|
29029
29273
|
throw new Error(
|
|
29030
29274
|
"All tracks must specify maximumPacketCount in their metadata when using fastStart: 'reserve'."
|
|
@@ -29045,7 +29289,7 @@ var Mediabunny = (() => {
|
|
|
29045
29289
|
release();
|
|
29046
29290
|
}
|
|
29047
29291
|
allTracksAreKnown() {
|
|
29048
|
-
for (const track of this.output.
|
|
29292
|
+
for (const track of this.output.tracks) {
|
|
29049
29293
|
if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) {
|
|
29050
29294
|
return false;
|
|
29051
29295
|
}
|
|
@@ -29114,6 +29358,7 @@ var Mediabunny = (() => {
|
|
|
29114
29358
|
num: displayAspectWidth * decoderConfig.codedHeight,
|
|
29115
29359
|
den: displayAspectHeight * decoderConfig.codedWidth
|
|
29116
29360
|
});
|
|
29361
|
+
const hasAlphaChannel = decoderConfig.codec === "ap4h" || decoderConfig.codec === "ap4x";
|
|
29117
29362
|
const newTrackData = {
|
|
29118
29363
|
muxer: this,
|
|
29119
29364
|
track,
|
|
@@ -29123,7 +29368,8 @@ var Mediabunny = (() => {
|
|
|
29123
29368
|
height: decoderConfig.codedHeight,
|
|
29124
29369
|
pixelAspectRatio,
|
|
29125
29370
|
decoderConfig,
|
|
29126
|
-
requiresAnnexBTransformation
|
|
29371
|
+
requiresAnnexBTransformation,
|
|
29372
|
+
hasAlphaChannel
|
|
29127
29373
|
},
|
|
29128
29374
|
timescale,
|
|
29129
29375
|
samples: [],
|
|
@@ -30439,7 +30685,7 @@ var Mediabunny = (() => {
|
|
|
30439
30685
|
return this.ebmlWriter.dataOffsets.get(this.segment);
|
|
30440
30686
|
}
|
|
30441
30687
|
allTracksAreKnown() {
|
|
30442
|
-
for (const track of this.output.
|
|
30688
|
+
for (const track of this.output.tracks) {
|
|
30443
30689
|
if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) {
|
|
30444
30690
|
return false;
|
|
30445
30691
|
}
|
|
@@ -31288,7 +31534,7 @@ ${cue.notes ?? ""}`;
|
|
|
31288
31534
|
throw new Error("Subtitle tracks are not supported.");
|
|
31289
31535
|
}
|
|
31290
31536
|
allTracksAreKnown() {
|
|
31291
|
-
for (const track of this.output.
|
|
31537
|
+
for (const track of this.output.tracks) {
|
|
31292
31538
|
if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) {
|
|
31293
31539
|
return false;
|
|
31294
31540
|
}
|
|
@@ -31743,7 +31989,7 @@ ${cue.notes ?? ""}`;
|
|
|
31743
31989
|
return result;
|
|
31744
31990
|
}
|
|
31745
31991
|
allTracksAreKnown() {
|
|
31746
|
-
for (const track of this.output.
|
|
31992
|
+
for (const track of this.output.tracks) {
|
|
31747
31993
|
if (!track.source._closed && !this.trackDatas.some((x) => x.track === track)) {
|
|
31748
31994
|
return false;
|
|
31749
31995
|
}
|
|
@@ -32654,6 +32900,8 @@ ${cue.notes ?? ""}`;
|
|
|
32654
32900
|
this.customEncoder = null;
|
|
32655
32901
|
this.customEncoderCallSerializer = new CallSerializer();
|
|
32656
32902
|
this.customEncoderQueueSize = 0;
|
|
32903
|
+
// Set when the encoder uses quantizer-based rate control; carries the quantizer value applied to each frame
|
|
32904
|
+
this.defaultEncodeOptions = {};
|
|
32657
32905
|
// Alpha stuff
|
|
32658
32906
|
this.alphaEncoder = null;
|
|
32659
32907
|
this.splitter = null;
|
|
@@ -32783,18 +33031,36 @@ ${cue.notes ?? ""}`;
|
|
|
32783
33031
|
if (!Array.isArray(processed)) {
|
|
32784
33032
|
processed = [processed];
|
|
32785
33033
|
}
|
|
32786
|
-
|
|
32787
|
-
|
|
32788
|
-
|
|
33034
|
+
const mappedSamples = [];
|
|
33035
|
+
try {
|
|
33036
|
+
for (const x of processed) {
|
|
33037
|
+
if (x instanceof VideoSample) {
|
|
33038
|
+
mappedSamples.push(x);
|
|
33039
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
33040
|
+
mappedSamples.push(new VideoSample(x));
|
|
33041
|
+
} else {
|
|
33042
|
+
mappedSamples.push(new VideoSample(x, {
|
|
33043
|
+
timestamp: videoSample.timestamp,
|
|
33044
|
+
duration: videoSample.duration
|
|
33045
|
+
}));
|
|
33046
|
+
}
|
|
32789
33047
|
}
|
|
32790
|
-
|
|
32791
|
-
|
|
33048
|
+
} catch (error) {
|
|
33049
|
+
for (const sample of mappedSamples) {
|
|
33050
|
+
if (sample !== videoSample) {
|
|
33051
|
+
sample.close();
|
|
33052
|
+
}
|
|
32792
33053
|
}
|
|
32793
|
-
|
|
32794
|
-
|
|
32795
|
-
|
|
32796
|
-
|
|
32797
|
-
|
|
33054
|
+
for (const x of processed) {
|
|
33055
|
+
if (x instanceof VideoSample && x !== videoSample) {
|
|
33056
|
+
x.close();
|
|
33057
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
33058
|
+
x.close();
|
|
33059
|
+
}
|
|
33060
|
+
}
|
|
33061
|
+
throw error;
|
|
33062
|
+
}
|
|
33063
|
+
samplesToEncode = mappedSamples;
|
|
32798
33064
|
} else {
|
|
32799
33065
|
samplesToEncode = [videoSample];
|
|
32800
33066
|
}
|
|
@@ -32814,7 +33080,11 @@ ${cue.notes ?? ""}`;
|
|
|
32814
33080
|
}
|
|
32815
33081
|
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
|
|
32816
33082
|
const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
|
|
32817
|
-
const mergedEncodeOptions = {
|
|
33083
|
+
const mergedEncodeOptions = {
|
|
33084
|
+
...this.defaultEncodeOptions,
|
|
33085
|
+
...sampleToEncode.encodeOptions,
|
|
33086
|
+
...encodeOptions
|
|
33087
|
+
};
|
|
32818
33088
|
const finalEncodeOptions = {
|
|
32819
33089
|
...mergedEncodeOptions,
|
|
32820
33090
|
keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
|
|
@@ -32860,22 +33130,31 @@ ${cue.notes ?? ""}`;
|
|
|
32860
33130
|
}
|
|
32861
33131
|
}
|
|
32862
33132
|
if (!this.alphaEncoder) {
|
|
32863
|
-
|
|
32864
|
-
|
|
33133
|
+
try {
|
|
33134
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
33135
|
+
} finally {
|
|
33136
|
+
videoFrame.close();
|
|
33137
|
+
}
|
|
32865
33138
|
} else {
|
|
32866
33139
|
const frameDefinitelyHasNoAlpha = !!videoFrame.format && !videoFrame.format.includes("A");
|
|
32867
33140
|
if (frameDefinitelyHasNoAlpha || this.splitterCreationFailed) {
|
|
32868
33141
|
this.alphaFrameQueue.push(null);
|
|
32869
|
-
|
|
32870
|
-
|
|
33142
|
+
try {
|
|
33143
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
33144
|
+
} finally {
|
|
33145
|
+
videoFrame.close();
|
|
33146
|
+
}
|
|
32871
33147
|
} else {
|
|
32872
33148
|
if (!this.splitter) {
|
|
32873
33149
|
this.splitter = new ColorAlphaSplitter();
|
|
32874
33150
|
}
|
|
32875
33151
|
const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame);
|
|
32876
33152
|
this.alphaFrameQueue.push(alphaFrame);
|
|
32877
|
-
|
|
32878
|
-
|
|
33153
|
+
try {
|
|
33154
|
+
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
33155
|
+
} finally {
|
|
33156
|
+
colorFrame.close();
|
|
33157
|
+
}
|
|
32879
33158
|
}
|
|
32880
33159
|
}
|
|
32881
33160
|
if (this.encoder.encodeQueueSize >= 4) {
|
|
@@ -32900,28 +33179,83 @@ ${cue.notes ?? ""}`;
|
|
|
32900
33179
|
assert(this.frameRateLastSample);
|
|
32901
33180
|
const frameDifference = Math.round((until - this.frameRateLastTimestamp) * frameRate);
|
|
32902
33181
|
for (let i = 1; i < frameDifference; i++) {
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
|
|
32906
|
-
|
|
32907
|
-
|
|
33182
|
+
var _stack = [];
|
|
33183
|
+
try {
|
|
33184
|
+
const sample = __using(_stack, this.frameRateLastSample.clone());
|
|
33185
|
+
sample.setTimestamp(this.frameRateLastTimestamp + i / frameRate);
|
|
33186
|
+
sample.setDuration(1 / frameRate);
|
|
33187
|
+
await this.processAndEncode(sample, encodeOptions);
|
|
33188
|
+
} catch (_) {
|
|
33189
|
+
var _error = _, _hasError = true;
|
|
33190
|
+
} finally {
|
|
33191
|
+
__callDispose(_stack, _error, _hasError);
|
|
33192
|
+
}
|
|
32908
33193
|
}
|
|
32909
33194
|
}
|
|
32910
33195
|
ensureEncoder(videoSample) {
|
|
32911
33196
|
this.ensureEncoderPromise = (async () => {
|
|
32912
|
-
const
|
|
33197
|
+
const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
|
|
33198
|
+
assert(quality !== void 0);
|
|
33199
|
+
const candidates = buildVideoEncoderConfigs({
|
|
32913
33200
|
...this.encodingConfig,
|
|
33201
|
+
quality,
|
|
32914
33202
|
width: videoSample.codedWidth,
|
|
32915
33203
|
height: videoSample.codedHeight,
|
|
32916
33204
|
squarePixelWidth: videoSample.squarePixelWidth,
|
|
32917
33205
|
squarePixelHeight: videoSample.squarePixelHeight,
|
|
32918
33206
|
framerate: this.source._connectedTrack?.metadata.frameRate
|
|
32919
33207
|
});
|
|
32920
|
-
|
|
32921
|
-
|
|
32922
|
-
|
|
32923
|
-
|
|
32924
|
-
|
|
33208
|
+
let selected = null;
|
|
33209
|
+
let MatchingCustomEncoder;
|
|
33210
|
+
for (const candidate of candidates) {
|
|
33211
|
+
const candidateConfig = candidate.config;
|
|
33212
|
+
this.encodingConfig.onEncoderConfig?.(candidateConfig);
|
|
33213
|
+
MatchingCustomEncoder = customVideoEncoders.find((x) => x.supports(
|
|
33214
|
+
this.encodingConfig.codec,
|
|
33215
|
+
candidateConfig
|
|
33216
|
+
));
|
|
33217
|
+
if (MatchingCustomEncoder) {
|
|
33218
|
+
selected = candidate;
|
|
33219
|
+
break;
|
|
33220
|
+
}
|
|
33221
|
+
if (typeof VideoEncoder === "undefined") {
|
|
33222
|
+
continue;
|
|
33223
|
+
}
|
|
33224
|
+
candidateConfig.alpha = "discard";
|
|
33225
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
33226
|
+
candidateConfig.latencyMode = "quality";
|
|
33227
|
+
}
|
|
33228
|
+
const hasOddDimension = candidateConfig.width % 2 === 1 || candidateConfig.height % 2 === 1;
|
|
33229
|
+
if (hasOddDimension && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) {
|
|
33230
|
+
throw new Error(
|
|
33231
|
+
`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.`
|
|
33232
|
+
);
|
|
33233
|
+
}
|
|
33234
|
+
const support = await VideoEncoder.isConfigSupported(candidateConfig);
|
|
33235
|
+
if (support.supported) {
|
|
33236
|
+
selected = candidate;
|
|
33237
|
+
break;
|
|
33238
|
+
}
|
|
33239
|
+
}
|
|
33240
|
+
if (!selected) {
|
|
33241
|
+
if (typeof VideoEncoder === "undefined") {
|
|
33242
|
+
throw new Error("VideoEncoder is not supported by this browser.");
|
|
33243
|
+
}
|
|
33244
|
+
const firstConfig = candidates[0].config;
|
|
33245
|
+
const rateControls = candidates.map(
|
|
33246
|
+
({ config, quantizer }) => quantizer !== null ? `quantizer ${quantizer}` : `${config.bitrate} bps`
|
|
33247
|
+
);
|
|
33248
|
+
throw new Error(
|
|
33249
|
+
`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.`
|
|
33250
|
+
);
|
|
33251
|
+
}
|
|
33252
|
+
const encoderConfig = selected.config;
|
|
33253
|
+
if (selected.quantizer !== null) {
|
|
33254
|
+
this.defaultEncodeOptions = buildQuantizerEncodeOptions(
|
|
33255
|
+
this.encodingConfig.codec,
|
|
33256
|
+
selected.quantizer
|
|
33257
|
+
);
|
|
33258
|
+
}
|
|
32925
33259
|
if (MatchingCustomEncoder) {
|
|
32926
33260
|
this.customEncoder = new MatchingCustomEncoder();
|
|
32927
33261
|
this.customEncoder.codec = this.encodingConfig.codec;
|
|
@@ -32944,25 +33278,6 @@ ${cue.notes ?? ""}`;
|
|
|
32944
33278
|
};
|
|
32945
33279
|
await this.customEncoder.init();
|
|
32946
33280
|
} 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
33281
|
const colorChunkQueue = [];
|
|
32967
33282
|
const nullAlphaChunkQueue = [];
|
|
32968
33283
|
let encodedAlphaChunkCount = 0;
|
|
@@ -33014,6 +33329,7 @@ ${cue.notes ?? ""}`;
|
|
|
33014
33329
|
assert(alphaFrame !== void 0);
|
|
33015
33330
|
if (alphaFrame) {
|
|
33016
33331
|
this.alphaEncoder.encode(alphaFrame, {
|
|
33332
|
+
...this.defaultEncodeOptions,
|
|
33017
33333
|
// Crucial: The alpha frame is forced to be a key frame whenever the color frame
|
|
33018
33334
|
// also is. Without this, playback can glitch and even crash in some browsers.
|
|
33019
33335
|
// This is the reason why the two encoders are wired in series and not in parallel.
|
|
@@ -33069,36 +33385,42 @@ ${cue.notes ?? ""}`;
|
|
|
33069
33385
|
})();
|
|
33070
33386
|
}
|
|
33071
33387
|
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) {
|
|
33388
|
+
try {
|
|
33084
33389
|
if (!forceClose) {
|
|
33085
|
-
|
|
33390
|
+
this.checkForEncoderError();
|
|
33391
|
+
if (this.frameRateLastSample) {
|
|
33392
|
+
const frameRate = this.encodingConfig.transform.frameRate;
|
|
33393
|
+
const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
|
|
33394
|
+
await this.padFrameRate(alignedEnd);
|
|
33395
|
+
}
|
|
33086
33396
|
}
|
|
33087
|
-
|
|
33088
|
-
} else if (this.encoder) {
|
|
33397
|
+
this.closed = true;
|
|
33089
33398
|
if (!forceClose) {
|
|
33090
|
-
|
|
33091
|
-
|
|
33092
|
-
|
|
33093
|
-
|
|
33094
|
-
|
|
33095
|
-
|
|
33399
|
+
if (this.customEncoder) {
|
|
33400
|
+
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
33401
|
+
} else if (this.encoder) {
|
|
33402
|
+
await this.encoder.flush();
|
|
33403
|
+
await this.alphaEncoder?.flush();
|
|
33404
|
+
await wait(25);
|
|
33405
|
+
}
|
|
33096
33406
|
}
|
|
33097
|
-
|
|
33098
|
-
|
|
33407
|
+
} finally {
|
|
33408
|
+
this.closed = true;
|
|
33409
|
+
this.frameRateLastSample?.close();
|
|
33410
|
+
this.frameRateLastSample = null;
|
|
33411
|
+
if (this.customEncoder) {
|
|
33412
|
+
await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error));
|
|
33413
|
+
} else if (this.encoder) {
|
|
33414
|
+
if (this.encoder.state !== "closed") {
|
|
33415
|
+
this.encoder.close();
|
|
33416
|
+
}
|
|
33417
|
+
if (this.alphaEncoder && this.alphaEncoder.state !== "closed") {
|
|
33418
|
+
this.alphaEncoder.close();
|
|
33419
|
+
}
|
|
33420
|
+
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
33421
|
+
this.alphaFrameQueue.length = 0;
|
|
33422
|
+
this.splitter?.close();
|
|
33099
33423
|
}
|
|
33100
|
-
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
33101
|
-
this.splitter?.close();
|
|
33102
33424
|
}
|
|
33103
33425
|
if (!forceClose) {
|
|
33104
33426
|
this.checkForEncoderError();
|
|
@@ -33762,26 +34084,39 @@ ${cue.notes ?? ""}`;
|
|
|
33762
34084
|
shouldClose = true;
|
|
33763
34085
|
}
|
|
33764
34086
|
if (config.transform?.process) {
|
|
33765
|
-
|
|
33766
|
-
|
|
33767
|
-
processed
|
|
33768
|
-
|
|
33769
|
-
|
|
33770
|
-
|
|
33771
|
-
|
|
33772
|
-
|
|
33773
|
-
processed
|
|
33774
|
-
|
|
33775
|
-
|
|
33776
|
-
|
|
33777
|
-
|
|
33778
|
-
|
|
33779
|
-
|
|
34087
|
+
try {
|
|
34088
|
+
let processed = config.transform.process(audioSample);
|
|
34089
|
+
if (processed instanceof Promise) {
|
|
34090
|
+
processed = await processed;
|
|
34091
|
+
}
|
|
34092
|
+
if (processed === null) {
|
|
34093
|
+
return;
|
|
34094
|
+
}
|
|
34095
|
+
if (!Array.isArray(processed)) {
|
|
34096
|
+
processed = [processed];
|
|
34097
|
+
}
|
|
34098
|
+
try {
|
|
34099
|
+
for (const sample of processed) {
|
|
34100
|
+
if (!(sample instanceof AudioSample)) {
|
|
34101
|
+
throw new TypeError(
|
|
34102
|
+
"The audio process function must return an AudioSample, null, or an array of AudioSamples."
|
|
34103
|
+
);
|
|
34104
|
+
}
|
|
34105
|
+
}
|
|
34106
|
+
for (const sample of processed) {
|
|
34107
|
+
await this.encodeSample(sample, true);
|
|
34108
|
+
}
|
|
34109
|
+
} finally {
|
|
34110
|
+
for (const sample of processed) {
|
|
34111
|
+
if (sample instanceof AudioSample) {
|
|
34112
|
+
sample.close();
|
|
34113
|
+
}
|
|
34114
|
+
}
|
|
34115
|
+
}
|
|
34116
|
+
} finally {
|
|
34117
|
+
if (shouldClose) {
|
|
34118
|
+
audioSample.close();
|
|
33780
34119
|
}
|
|
33781
|
-
await this.encodeSample(sample, true);
|
|
33782
|
-
}
|
|
33783
|
-
if (shouldClose) {
|
|
33784
|
-
audioSample.close();
|
|
33785
34120
|
}
|
|
33786
34121
|
} else {
|
|
33787
34122
|
await this.encodeSample(audioSample, shouldClose);
|
|
@@ -33917,10 +34252,12 @@ ${cue.notes ?? ""}`;
|
|
|
33917
34252
|
ensureEncoder(audioSample) {
|
|
33918
34253
|
this.ensureEncoderPromise = (async () => {
|
|
33919
34254
|
const { numberOfChannels, sampleRate } = audioSample;
|
|
34255
|
+
const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
|
|
33920
34256
|
const encoderConfig = buildAudioEncoderConfig({
|
|
33921
34257
|
numberOfChannels,
|
|
33922
34258
|
sampleRate,
|
|
33923
|
-
...this.encodingConfig
|
|
34259
|
+
...this.encodingConfig,
|
|
34260
|
+
quality
|
|
33924
34261
|
});
|
|
33925
34262
|
this.encodingConfig.onEncoderConfig?.(encoderConfig);
|
|
33926
34263
|
const MatchingCustomEncoder = customAudioEncoders.find((x) => x.supports(
|
|
@@ -34099,24 +34436,27 @@ ${cue.notes ?? ""}`;
|
|
|
34099
34436
|
}
|
|
34100
34437
|
}
|
|
34101
34438
|
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) {
|
|
34439
|
+
try {
|
|
34111
34440
|
if (!forceClose) {
|
|
34112
|
-
|
|
34441
|
+
this.checkForEncoderError();
|
|
34442
|
+
if (this.resampler) {
|
|
34443
|
+
await this.resampler.finalize();
|
|
34444
|
+
}
|
|
34113
34445
|
}
|
|
34114
|
-
|
|
34115
|
-
} else if (this.encoder) {
|
|
34446
|
+
this.closed = true;
|
|
34116
34447
|
if (!forceClose) {
|
|
34117
|
-
|
|
34448
|
+
if (this.customEncoder) {
|
|
34449
|
+
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
34450
|
+
} else if (this.encoder) {
|
|
34451
|
+
await this.encoder.flush();
|
|
34452
|
+
}
|
|
34118
34453
|
}
|
|
34119
|
-
|
|
34454
|
+
} finally {
|
|
34455
|
+
this.closed = true;
|
|
34456
|
+
this.resampler = null;
|
|
34457
|
+
if (this.customEncoder) {
|
|
34458
|
+
await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error));
|
|
34459
|
+
} else if (this.encoder && this.encoder.state !== "closed") {
|
|
34120
34460
|
this.encoder.close();
|
|
34121
34461
|
}
|
|
34122
34462
|
}
|
|
@@ -34567,8 +34907,8 @@ ${cue.notes ?? ""}`;
|
|
|
34567
34907
|
}
|
|
34568
34908
|
async start() {
|
|
34569
34909
|
const release = await this.mutex.acquire();
|
|
34570
|
-
const someRelative = this.output.
|
|
34571
|
-
const someNotRelative = this.output.
|
|
34910
|
+
const someRelative = this.output.tracks.some((t) => t.metadata.isRelativeToUnixEpoch);
|
|
34911
|
+
const someNotRelative = this.output.tracks.some((t) => !t.metadata.isRelativeToUnixEpoch);
|
|
34572
34912
|
if (someRelative && someNotRelative) {
|
|
34573
34913
|
throw new Error(
|
|
34574
34914
|
"All tracks must agree on `relativeToUnixEpoch`: some tracks are relative to the Unix epoch and some are not."
|
|
@@ -34580,12 +34920,12 @@ ${cue.notes ?? ""}`;
|
|
|
34580
34920
|
let hasVideo = false;
|
|
34581
34921
|
let illegalPairingDetected = false;
|
|
34582
34922
|
let keyPacketsOnlyPairingWarned = false;
|
|
34583
|
-
for (const track of this.output.
|
|
34923
|
+
for (const track of this.output.tracks) {
|
|
34584
34924
|
if (track.type === "video") {
|
|
34585
34925
|
hasVideo = true;
|
|
34586
34926
|
}
|
|
34587
34927
|
const pairableGroups = /* @__PURE__ */ new Map();
|
|
34588
|
-
for (const otherTrack of this.output.
|
|
34928
|
+
for (const otherTrack of this.output.tracks) {
|
|
34589
34929
|
if (track === otherTrack) {
|
|
34590
34930
|
continue;
|
|
34591
34931
|
}
|
|
@@ -34639,7 +34979,7 @@ ${cue.notes ?? ""}`;
|
|
|
34639
34979
|
const variantStreams = [];
|
|
34640
34980
|
const unpairedVideoTracks = [];
|
|
34641
34981
|
const unpairedAudioTracks = [];
|
|
34642
|
-
for (const track of this.output.
|
|
34982
|
+
for (const track of this.output.tracks) {
|
|
34643
34983
|
const assignedGroupKeys = groupAssignment.get(track);
|
|
34644
34984
|
if (assignedGroupKeys) {
|
|
34645
34985
|
assert(assignedGroupKeys.length > 0);
|
|
@@ -36361,6 +36701,10 @@ ${cue.notes ?? ""}`;
|
|
|
36361
36701
|
* {@link BaseTrackMetadata.group}.
|
|
36362
36702
|
*/
|
|
36363
36703
|
this.defaultTrackGroup = new OutputTrackGroup();
|
|
36704
|
+
/**
|
|
36705
|
+
* The tracks that have been added to this output. Treat it as a readonly field; to add tracks, use the methods.
|
|
36706
|
+
*/
|
|
36707
|
+
this.tracks = [];
|
|
36364
36708
|
/** @internal */
|
|
36365
36709
|
this._onFinalize = null;
|
|
36366
36710
|
/** @internal */
|
|
@@ -36368,8 +36712,6 @@ ${cue.notes ?? ""}`;
|
|
|
36368
36712
|
/** @internal */
|
|
36369
36713
|
this._rootWriterPromise = null;
|
|
36370
36714
|
/** @internal */
|
|
36371
|
-
this._tracks = [];
|
|
36372
|
-
/** @internal */
|
|
36373
36715
|
this._startPromise = null;
|
|
36374
36716
|
/** @internal */
|
|
36375
36717
|
this._cancelPromise = null;
|
|
@@ -36549,7 +36891,7 @@ ${cue.notes ?? ""}`;
|
|
|
36549
36891
|
const metadataCopy = { ...metadata };
|
|
36550
36892
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36551
36893
|
return this._addTrack(new OutputVideoTrack2(
|
|
36552
|
-
this.
|
|
36894
|
+
this.tracks.length + 1,
|
|
36553
36895
|
this,
|
|
36554
36896
|
source,
|
|
36555
36897
|
metadataCopy
|
|
@@ -36564,7 +36906,7 @@ ${cue.notes ?? ""}`;
|
|
|
36564
36906
|
const metadataCopy = { ...metadata };
|
|
36565
36907
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36566
36908
|
return this._addTrack(new OutputAudioTrack2(
|
|
36567
|
-
this.
|
|
36909
|
+
this.tracks.length + 1,
|
|
36568
36910
|
this,
|
|
36569
36911
|
source,
|
|
36570
36912
|
metadataCopy
|
|
@@ -36579,7 +36921,7 @@ ${cue.notes ?? ""}`;
|
|
|
36579
36921
|
const metadataCopy = { ...metadata };
|
|
36580
36922
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36581
36923
|
return this._addTrack(new OutputSubtitleTrack2(
|
|
36582
|
-
this.
|
|
36924
|
+
this.tracks.length + 1,
|
|
36583
36925
|
this,
|
|
36584
36926
|
source,
|
|
36585
36927
|
metadataCopy
|
|
@@ -36607,7 +36949,7 @@ ${cue.notes ?? ""}`;
|
|
|
36607
36949
|
throw new Error("Source is already used for a track.");
|
|
36608
36950
|
}
|
|
36609
36951
|
const supportedTrackCounts = this.format.getSupportedTrackCounts();
|
|
36610
|
-
const presentTracksOfThisType = this.
|
|
36952
|
+
const presentTracksOfThisType = this.tracks.reduce(
|
|
36611
36953
|
(count, t) => count + (t.type === track.type ? 1 : 0),
|
|
36612
36954
|
0
|
|
36613
36955
|
);
|
|
@@ -36618,7 +36960,7 @@ ${cue.notes ?? ""}`;
|
|
|
36618
36960
|
);
|
|
36619
36961
|
}
|
|
36620
36962
|
const maxTotalCount = supportedTrackCounts.total.max;
|
|
36621
|
-
if (this.
|
|
36963
|
+
if (this.tracks.length === maxTotalCount) {
|
|
36622
36964
|
throw new Error(
|
|
36623
36965
|
`${this.format._name} does not support more than ${maxTotalCount} tracks${maxTotalCount === 1 ? "" : "s"} in total.`
|
|
36624
36966
|
);
|
|
@@ -36657,10 +36999,32 @@ ${cue.notes ?? ""}`;
|
|
|
36657
36999
|
);
|
|
36658
37000
|
}
|
|
36659
37001
|
}
|
|
36660
|
-
this.
|
|
37002
|
+
this.tracks.push(track);
|
|
36661
37003
|
track.source._connectedTrack = track;
|
|
36662
37004
|
return track;
|
|
36663
37005
|
}
|
|
37006
|
+
/**
|
|
37007
|
+
* Whether the output has enough tracks (of the correct type) to be started, based on the requirements of the output
|
|
37008
|
+
* format.
|
|
37009
|
+
*/
|
|
37010
|
+
hasEnoughTracks() {
|
|
37011
|
+
const supportedTrackCounts = this.format.getSupportedTrackCounts();
|
|
37012
|
+
for (const trackType of ALL_TRACK_TYPES) {
|
|
37013
|
+
const presentTracksOfThisType = this.tracks.reduce(
|
|
37014
|
+
(count, track) => count + (track.type === trackType ? 1 : 0),
|
|
37015
|
+
0
|
|
37016
|
+
);
|
|
37017
|
+
const minCount = supportedTrackCounts[trackType].min;
|
|
37018
|
+
if (presentTracksOfThisType < minCount) {
|
|
37019
|
+
return false;
|
|
37020
|
+
}
|
|
37021
|
+
}
|
|
37022
|
+
const totalMinCount = supportedTrackCounts.total.min;
|
|
37023
|
+
if (this.tracks.length < totalMinCount) {
|
|
37024
|
+
return false;
|
|
37025
|
+
}
|
|
37026
|
+
return true;
|
|
37027
|
+
}
|
|
36664
37028
|
/**
|
|
36665
37029
|
* Starts the creation of the output file. This method should be called after all tracks have been added. Only after
|
|
36666
37030
|
* the output has started can media samples be added to the tracks.
|
|
@@ -36670,7 +37034,7 @@ ${cue.notes ?? ""}`;
|
|
|
36670
37034
|
async start() {
|
|
36671
37035
|
const supportedTrackCounts = this.format.getSupportedTrackCounts();
|
|
36672
37036
|
for (const trackType of ALL_TRACK_TYPES) {
|
|
36673
|
-
const presentTracksOfThisType = this.
|
|
37037
|
+
const presentTracksOfThisType = this.tracks.reduce(
|
|
36674
37038
|
(count, track) => count + (track.type === trackType ? 1 : 0),
|
|
36675
37039
|
0
|
|
36676
37040
|
);
|
|
@@ -36682,7 +37046,7 @@ ${cue.notes ?? ""}`;
|
|
|
36682
37046
|
}
|
|
36683
37047
|
}
|
|
36684
37048
|
const totalMinCount = supportedTrackCounts.total.min;
|
|
36685
|
-
if (this.
|
|
37049
|
+
if (this.tracks.length < totalMinCount) {
|
|
36686
37050
|
throw new Error(
|
|
36687
37051
|
totalMinCount === supportedTrackCounts.total.max ? `${this.format._name} requires exactly ${totalMinCount} track${totalMinCount === 1 ? "" : "s"}.` : `${this.format._name} requires at least ${totalMinCount} track${totalMinCount === 1 ? "" : "s"}.`
|
|
36688
37052
|
);
|
|
@@ -36699,7 +37063,7 @@ ${cue.notes ?? ""}`;
|
|
|
36699
37063
|
const release = await this._mutex.acquire();
|
|
36700
37064
|
try {
|
|
36701
37065
|
await this._muxer.start();
|
|
36702
|
-
const promises = this.
|
|
37066
|
+
const promises = this.tracks.map((track) => track.source._start());
|
|
36703
37067
|
await Promise.all(promises);
|
|
36704
37068
|
} finally {
|
|
36705
37069
|
release();
|
|
@@ -36734,7 +37098,7 @@ ${cue.notes ?? ""}`;
|
|
|
36734
37098
|
this.state = "canceled";
|
|
36735
37099
|
const release = await this._mutex.acquire();
|
|
36736
37100
|
try {
|
|
36737
|
-
const promises = this.
|
|
37101
|
+
const promises = this.tracks.map((x) => x.source._flushOrWaitForOngoingClose(true));
|
|
36738
37102
|
await Promise.all(promises);
|
|
36739
37103
|
await Promise.all([...this._unfinalizedTargets].map((target) => target._close()));
|
|
36740
37104
|
this._unfinalizedTargets.clear();
|
|
@@ -36762,7 +37126,7 @@ ${cue.notes ?? ""}`;
|
|
|
36762
37126
|
this.state = "finalizing";
|
|
36763
37127
|
const release = await this._mutex.acquire();
|
|
36764
37128
|
try {
|
|
36765
|
-
const promises = this.
|
|
37129
|
+
const promises = this.tracks.map((x) => x.source._flushOrWaitForOngoingClose(false));
|
|
36766
37130
|
await Promise.all(promises);
|
|
36767
37131
|
await this._muxer.finalize();
|
|
36768
37132
|
if (this._rootWriterPromise) {
|
|
@@ -36777,6 +37141,9 @@ ${cue.notes ?? ""}`;
|
|
|
36777
37141
|
}
|
|
36778
37142
|
this.state = "finalized";
|
|
36779
37143
|
} finally {
|
|
37144
|
+
await Promise.all([...this._unfinalizedTargets].map((target) => target._close().catch(() => {
|
|
37145
|
+
})));
|
|
37146
|
+
this._unfinalizedTargets.clear();
|
|
36780
37147
|
release();
|
|
36781
37148
|
}
|
|
36782
37149
|
})();
|
|
@@ -36799,7 +37166,14 @@ ${cue.notes ?? ""}`;
|
|
|
36799
37166
|
`options.video.codec, when provided, must be one of: ${VIDEO_CODECS.join(", ")}.`
|
|
36800
37167
|
);
|
|
36801
37168
|
}
|
|
36802
|
-
|
|
37169
|
+
const bitrate = videoOptions?.bitrate;
|
|
37170
|
+
if (videoOptions?.quality !== void 0 && !(videoOptions.quality instanceof Quality)) {
|
|
37171
|
+
throw new TypeError("options.video.quality, when provided, must be a Quality.");
|
|
37172
|
+
}
|
|
37173
|
+
if (videoOptions?.quality !== void 0 && bitrate !== void 0) {
|
|
37174
|
+
throw new TypeError("options.video.quality and options.video.bitrate cannot both be provided.");
|
|
37175
|
+
}
|
|
37176
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
36803
37177
|
throw new TypeError("options.video.bitrate, when provided, must be a positive integer or a quality.");
|
|
36804
37178
|
}
|
|
36805
37179
|
if (videoOptions?.width !== void 0 && (!Number.isInteger(videoOptions.width) || videoOptions.width <= 0)) {
|
|
@@ -36869,7 +37243,14 @@ ${cue.notes ?? ""}`;
|
|
|
36869
37243
|
`options.audio.codec, when provided, must be one of: ${AUDIO_CODECS.join(", ")}.`
|
|
36870
37244
|
);
|
|
36871
37245
|
}
|
|
36872
|
-
|
|
37246
|
+
const bitrate = audioOptions?.bitrate;
|
|
37247
|
+
if (audioOptions?.quality !== void 0 && !(audioOptions.quality instanceof Quality)) {
|
|
37248
|
+
throw new TypeError("options.audio.quality, when provided, must be a Quality.");
|
|
37249
|
+
}
|
|
37250
|
+
if (audioOptions?.quality !== void 0 && bitrate !== void 0) {
|
|
37251
|
+
throw new TypeError("options.audio.quality and options.audio.bitrate cannot both be provided.");
|
|
37252
|
+
}
|
|
37253
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
36873
37254
|
throw new TypeError("options.audio.bitrate, when provided, must be a positive integer or a quality.");
|
|
36874
37255
|
}
|
|
36875
37256
|
if (audioOptions?.numberOfChannels !== void 0 && (!Number.isInteger(audioOptions.numberOfChannels) || audioOptions.numberOfChannels <= 0)) {
|
|
@@ -36901,14 +37282,15 @@ ${cue.notes ?? ""}`;
|
|
|
36901
37282
|
var Conversion = class _Conversion {
|
|
36902
37283
|
/** Creates a new Conversion instance (duh). */
|
|
36903
37284
|
constructor(options) {
|
|
36904
|
-
/**
|
|
36905
|
-
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
|
|
36910
|
-
|
|
36911
|
-
|
|
37285
|
+
/**
|
|
37286
|
+
* The current state of the conversion.
|
|
37287
|
+
*
|
|
37288
|
+
* - `'idle'`: The conversion is not currently executing and isn't done; `execute` can be called.
|
|
37289
|
+
* - `'executing'`: A call to `execute` is currently running.
|
|
37290
|
+
* - `'canceled'`: The conversion has been canceled and can no longer be executed.
|
|
37291
|
+
* - `'done'`: The conversion has run to completion. Subsequent calls to `execute` do nothing.
|
|
37292
|
+
*/
|
|
37293
|
+
this.state = "idle";
|
|
36912
37294
|
/** @internal */
|
|
36913
37295
|
this._nextOutputTrackId = 0;
|
|
36914
37296
|
/** @internal */
|
|
@@ -36916,18 +37298,22 @@ ${cue.notes ?? ""}`;
|
|
|
36916
37298
|
/** @internal */
|
|
36917
37299
|
this._outputOwnTrackGroups = [];
|
|
36918
37300
|
/** @internal */
|
|
36919
|
-
this.
|
|
37301
|
+
this._trackPumps = [];
|
|
37302
|
+
/** @internal */
|
|
37303
|
+
this._composable = false;
|
|
36920
37304
|
/** @internal */
|
|
36921
37305
|
this._executed = false;
|
|
36922
37306
|
/** @internal */
|
|
36923
|
-
this.
|
|
37307
|
+
this._executionUntil = Infinity;
|
|
37308
|
+
/** @internal */
|
|
37309
|
+
this._pauseRequested = false;
|
|
37310
|
+
/** @internal */
|
|
37311
|
+
this._synchronizer = new TrackSynchronizer(this);
|
|
36924
37312
|
/** @internal */
|
|
36925
37313
|
this._totalDuration = null;
|
|
36926
37314
|
/** @internal */
|
|
36927
37315
|
this._maxTimestamps = /* @__PURE__ */ new Map();
|
|
36928
37316
|
// Track ID -> timestamp
|
|
36929
|
-
/** @internal */
|
|
36930
|
-
this._canceled = false;
|
|
36931
37317
|
/**
|
|
36932
37318
|
* A callback that is fired whenever the conversion progresses. Gets passed as first argument a number between
|
|
36933
37319
|
* 0 and 1, indicating the completion of the conversion. Note that a progress of 1 doesn't necessarily mean the
|
|
@@ -36944,7 +37330,8 @@ ${cue.notes ?? ""}`;
|
|
|
36944
37330
|
this._lastProgress = 0;
|
|
36945
37331
|
/**
|
|
36946
37332
|
* Whether this conversion, as it has been configured, is valid and can be executed. If this field is `false`, check
|
|
36947
|
-
* the `discardedTracks` field for reasons.
|
|
37333
|
+
* the `discardedTracks` field for reasons. Composable conversions are always valid, even if they utilize
|
|
37334
|
+
* zero tracks.
|
|
36948
37335
|
*
|
|
36949
37336
|
* Note: a conversion having discarded tracks does not automatically mean it is invalid; if the remaining, utilized
|
|
36950
37337
|
* tracks make for a valid output file, the conversion is still allowed.
|
|
@@ -36971,8 +37358,23 @@ ${cue.notes ?? ""}`;
|
|
|
36971
37358
|
"options.tracks, when provided, must be either 'all' or 'primary'."
|
|
36972
37359
|
);
|
|
36973
37360
|
}
|
|
36974
|
-
if (options.
|
|
36975
|
-
throw new TypeError("options.
|
|
37361
|
+
if (options.composable !== void 0 && typeof options.composable !== "boolean") {
|
|
37362
|
+
throw new TypeError("options.composable, when provided, must be a boolean.");
|
|
37363
|
+
}
|
|
37364
|
+
const composable = options.composable ?? false;
|
|
37365
|
+
if (!composable) {
|
|
37366
|
+
if (options.output.tracks.length > 0 || Object.keys(options.output._metadataTags).length > 0 || options.output.state !== "pending") {
|
|
37367
|
+
throw new TypeError("options.output must be fresh: no tracks or metadata tags added and not started.");
|
|
37368
|
+
}
|
|
37369
|
+
} else {
|
|
37370
|
+
if (options.tags !== void 0) {
|
|
37371
|
+
throw new TypeError(
|
|
37372
|
+
"options.tags cannot be set by a composable conversion; set metadata directly on the output instead."
|
|
37373
|
+
);
|
|
37374
|
+
}
|
|
37375
|
+
if (options.output.state !== "pending") {
|
|
37376
|
+
throw new TypeError("options.output must not have been started yet.");
|
|
37377
|
+
}
|
|
36976
37378
|
}
|
|
36977
37379
|
if (options.video !== void 0 && typeof options.video !== "function") {
|
|
36978
37380
|
if (Array.isArray(options.video)) {
|
|
@@ -37016,11 +37418,9 @@ ${cue.notes ?? ""}`;
|
|
|
37016
37418
|
throw new TypeError("options.showWarnings, when provided, must be a boolean.");
|
|
37017
37419
|
}
|
|
37018
37420
|
this._options = options;
|
|
37421
|
+
this._composable = composable;
|
|
37019
37422
|
this.input = options.input;
|
|
37020
37423
|
this.output = options.output;
|
|
37021
|
-
const { promise: started, resolve: start } = promiseWithResolvers();
|
|
37022
|
-
this._started = started;
|
|
37023
|
-
this._start = start;
|
|
37024
37424
|
}
|
|
37025
37425
|
/** Initializes a new conversion process without starting the conversion. */
|
|
37026
37426
|
static async init(options) {
|
|
@@ -37132,7 +37532,7 @@ ${cue.notes ?? ""}`;
|
|
|
37132
37532
|
const track = filteredTracks[i];
|
|
37133
37533
|
const options = filteredTrackOptions[i];
|
|
37134
37534
|
for (const option of options) {
|
|
37135
|
-
if (this.
|
|
37535
|
+
if (this.output.tracks.length === outputTrackCounts.total.max) {
|
|
37136
37536
|
this.discardedTracks.push({
|
|
37137
37537
|
track,
|
|
37138
37538
|
reason: "max_track_count_reached",
|
|
@@ -37140,7 +37540,11 @@ ${cue.notes ?? ""}`;
|
|
|
37140
37540
|
});
|
|
37141
37541
|
continue;
|
|
37142
37542
|
}
|
|
37143
|
-
|
|
37543
|
+
const addedCountOfType = this.output.tracks.reduce(
|
|
37544
|
+
(count, t) => count + (t.type === track.type ? 1 : 0),
|
|
37545
|
+
0
|
|
37546
|
+
);
|
|
37547
|
+
if (addedCountOfType === outputTrackCounts[track.type].max) {
|
|
37144
37548
|
this.discardedTracks.push({
|
|
37145
37549
|
track,
|
|
37146
37550
|
reason: "max_track_count_of_type_reached",
|
|
@@ -37171,22 +37575,28 @@ ${cue.notes ?? ""}`;
|
|
|
37171
37575
|
}
|
|
37172
37576
|
}
|
|
37173
37577
|
}
|
|
37174
|
-
|
|
37175
|
-
|
|
37176
|
-
|
|
37177
|
-
|
|
37178
|
-
|
|
37179
|
-
|
|
37180
|
-
|
|
37181
|
-
|
|
37578
|
+
if (!this._composable) {
|
|
37579
|
+
const inputTags = await this.input.getMetadataTags();
|
|
37580
|
+
let outputTags;
|
|
37581
|
+
if (this._options.tags) {
|
|
37582
|
+
const result = typeof this._options.tags === "function" ? await this._options.tags(inputTags) : this._options.tags;
|
|
37583
|
+
validateMetadataTags(result);
|
|
37584
|
+
outputTags = result;
|
|
37585
|
+
} else {
|
|
37586
|
+
outputTags = inputTags;
|
|
37587
|
+
}
|
|
37588
|
+
const inputAndOutputFormatMatch = inputFormat.mimeType === this.output.format.mimeType;
|
|
37589
|
+
const rawTagsAreUnchanged = inputTags.raw === outputTags.raw;
|
|
37590
|
+
if (inputTags.raw && rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
|
|
37591
|
+
delete outputTags.raw;
|
|
37592
|
+
}
|
|
37593
|
+
this.output.setMetadataTags(outputTags);
|
|
37182
37594
|
}
|
|
37183
|
-
|
|
37184
|
-
|
|
37185
|
-
|
|
37186
|
-
|
|
37595
|
+
if (!this._composable) {
|
|
37596
|
+
this.isValid = this.output.hasEnoughTracks();
|
|
37597
|
+
} else {
|
|
37598
|
+
this.isValid = true;
|
|
37187
37599
|
}
|
|
37188
|
-
this.output.setMetadataTags(outputTags);
|
|
37189
|
-
this.isValid = this._totalTrackCount >= outputTrackCounts.total.min && this._addedCounts.video >= outputTrackCounts.video.min && this._addedCounts.audio >= outputTrackCounts.audio.min && this._addedCounts.subtitle >= outputTrackCounts.subtitle.min;
|
|
37190
37600
|
if (this._options.showWarnings ?? true) {
|
|
37191
37601
|
const warnElements = [];
|
|
37192
37602
|
const unintentionallyDiscardedTracks = this.discardedTracks.filter((x) => x.reason !== "discarded_by_user");
|
|
@@ -37224,24 +37634,26 @@ ${cue.notes ?? ""}`;
|
|
|
37224
37634
|
if (encodabilityIsTheProblem) {
|
|
37225
37635
|
const codecs = this.discardedTracks.flatMap((x) => {
|
|
37226
37636
|
if (x.reason === "discarded_by_user") return [];
|
|
37637
|
+
let supportedCodecs;
|
|
37227
37638
|
if (x.track.type === "video") {
|
|
37228
|
-
|
|
37639
|
+
supportedCodecs = this.output.format.getSupportedVideoCodecs();
|
|
37229
37640
|
} else if (x.track.type === "audio") {
|
|
37230
|
-
|
|
37641
|
+
supportedCodecs = this.output.format.getSupportedAudioCodecs();
|
|
37231
37642
|
} else {
|
|
37232
|
-
|
|
37643
|
+
supportedCodecs = this.output.format.getSupportedSubtitleCodecs();
|
|
37233
37644
|
}
|
|
37645
|
+
return supportedCodecs.filter((codec) => !x.trackOptions.codec || codec === x.trackOptions.codec);
|
|
37234
37646
|
});
|
|
37235
37647
|
const uniqueCodecs = [...new Set(codecs)];
|
|
37236
37648
|
if (uniqueCodecs.length === 1) {
|
|
37237
37649
|
elements.push(
|
|
37238
37650
|
`
|
|
37239
|
-
Tracks were discarded because your environment is not able to encode '${uniqueCodecs[0]}'.`
|
|
37651
|
+
Tracks were discarded because your environment is not able to encode '${uniqueCodecs[0]}' with the provided parameters.`
|
|
37240
37652
|
);
|
|
37241
37653
|
} else {
|
|
37242
37654
|
elements.push(
|
|
37243
37655
|
`
|
|
37244
|
-
Tracks were discarded because your environment is not able to encode any of the
|
|
37656
|
+
Tracks were discarded because your environment is not able to encode any of the codecs ${uniqueCodecs.map((x) => `'${x}'`).join(", ")} with the provided parameters.`
|
|
37245
37657
|
);
|
|
37246
37658
|
}
|
|
37247
37659
|
if (uniqueCodecs.includes("mp3")) {
|
|
@@ -37272,59 +37684,116 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37272
37684
|
return elements;
|
|
37273
37685
|
}
|
|
37274
37686
|
/**
|
|
37275
|
-
* Executes the conversion process
|
|
37687
|
+
* Executes the conversion process and resolves when the conversion is complete. When
|
|
37688
|
+
* {@link ConversionExecuteOptions.until} is provided, the conversion will be suspended once that output timestamp
|
|
37689
|
+
* is reached and can be resumed with another call to `execute`. An ongoing execution may also be suspended via
|
|
37690
|
+
* {@link ConversionExecuteOptions.pauseSignal}.
|
|
37276
37691
|
*
|
|
37277
|
-
*
|
|
37692
|
+
* Execution will throw if `isValid` is `false`.
|
|
37278
37693
|
*/
|
|
37279
|
-
async execute() {
|
|
37694
|
+
async execute(options = {}) {
|
|
37695
|
+
if (!options || typeof options !== "object") {
|
|
37696
|
+
throw new TypeError("options must be an object.");
|
|
37697
|
+
}
|
|
37698
|
+
if (options.until !== void 0 && (typeof options.until !== "number" || Number.isNaN(options.until))) {
|
|
37699
|
+
throw new TypeError("options.until, when provided, must be a number.");
|
|
37700
|
+
}
|
|
37701
|
+
if (options.pauseSignal !== void 0 && !(options.pauseSignal instanceof AbortSignal)) {
|
|
37702
|
+
throw new TypeError("options.pauseSignal, when provided, must be an AbortSignal.");
|
|
37703
|
+
}
|
|
37280
37704
|
if (!this.isValid) {
|
|
37281
37705
|
throw new Error(
|
|
37282
37706
|
"Cannot execute this conversion because its output configuration is invalid. Make sure to always check the isValid field before executing a conversion.\n" + this._getInvalidityExplanation().join("")
|
|
37283
37707
|
);
|
|
37284
37708
|
}
|
|
37285
|
-
if (this.
|
|
37286
|
-
throw new Error("
|
|
37709
|
+
if (this.state === "executing") {
|
|
37710
|
+
throw new Error("Cannot call execute() while a previous call to execute() is still running.");
|
|
37711
|
+
}
|
|
37712
|
+
if (this.state === "canceled") {
|
|
37713
|
+
throw new ConversionCanceledError();
|
|
37287
37714
|
}
|
|
37288
|
-
this.
|
|
37289
|
-
|
|
37290
|
-
this._synchronizer.declareTrack(id);
|
|
37715
|
+
if (this.state === "done") {
|
|
37716
|
+
return;
|
|
37291
37717
|
}
|
|
37292
|
-
if (this.
|
|
37293
|
-
|
|
37294
|
-
|
|
37295
|
-
if (await track.isLive()) {
|
|
37296
|
-
return Infinity;
|
|
37297
|
-
}
|
|
37298
|
-
return await track.getDurationFromMetadata() ?? await track.computeDuration();
|
|
37299
|
-
});
|
|
37300
|
-
const duration = Math.max(0, ...await Promise.all(durationPromises));
|
|
37301
|
-
this._computeProgress = true;
|
|
37302
|
-
this._totalDuration = Math.min(
|
|
37303
|
-
duration - this._startTimestamp,
|
|
37304
|
-
this._endTimestamp - this._startTimestamp
|
|
37718
|
+
if (this._composable && this.output.state === "pending") {
|
|
37719
|
+
throw new Error(
|
|
37720
|
+
"A composable conversion requires the output to be started. Call start() on the output before executing the conversion."
|
|
37305
37721
|
);
|
|
37722
|
+
}
|
|
37723
|
+
this.state = "executing";
|
|
37724
|
+
this._executionUntil = options.until ?? Infinity;
|
|
37725
|
+
this._pauseRequested = options.pauseSignal?.aborted ?? false;
|
|
37726
|
+
const onPause = () => {
|
|
37727
|
+
if (this.state !== "executing") {
|
|
37728
|
+
return;
|
|
37729
|
+
}
|
|
37730
|
+
this._pauseRequested = true;
|
|
37731
|
+
this._synchronizer.resolveAll();
|
|
37732
|
+
};
|
|
37733
|
+
options.pauseSignal?.addEventListener("abort", onPause);
|
|
37734
|
+
for (const pump of this._trackPumps) {
|
|
37735
|
+
if (!pump.done) {
|
|
37736
|
+
pump.resolvers = promiseWithResolvers();
|
|
37737
|
+
}
|
|
37738
|
+
}
|
|
37739
|
+
if (!this._executed) {
|
|
37740
|
+
this._executed = true;
|
|
37306
37741
|
for (const id of this._outputTrackIds) {
|
|
37307
|
-
this.
|
|
37742
|
+
this._synchronizer.declareTrack(id);
|
|
37743
|
+
}
|
|
37744
|
+
if (this.onProgress) {
|
|
37745
|
+
const uniqueUtilizedTracks = new Set(this.utilizedTracks);
|
|
37746
|
+
const durationPromises = [...uniqueUtilizedTracks].map(async (track) => {
|
|
37747
|
+
if (await track.isLive()) {
|
|
37748
|
+
return Infinity;
|
|
37749
|
+
}
|
|
37750
|
+
return await track.getDurationFromMetadata() ?? await track.computeDuration();
|
|
37751
|
+
});
|
|
37752
|
+
const duration = Math.max(0, ...await Promise.all(durationPromises));
|
|
37753
|
+
this._computeProgress = true;
|
|
37754
|
+
this._totalDuration = Math.min(
|
|
37755
|
+
duration - this._startTimestamp,
|
|
37756
|
+
this._endTimestamp - this._startTimestamp
|
|
37757
|
+
);
|
|
37758
|
+
for (const id of this._outputTrackIds) {
|
|
37759
|
+
this._maxTimestamps.set(id, 0);
|
|
37760
|
+
}
|
|
37761
|
+
this.onProgress?.(0, 0);
|
|
37762
|
+
}
|
|
37763
|
+
if (!this._composable) {
|
|
37764
|
+
await this.output.start();
|
|
37765
|
+
}
|
|
37766
|
+
for (const pump of this._trackPumps) {
|
|
37767
|
+
pump.start();
|
|
37768
|
+
}
|
|
37769
|
+
} else {
|
|
37770
|
+
for (const pump of this._trackPumps) {
|
|
37771
|
+
pump.wake?.();
|
|
37308
37772
|
}
|
|
37309
|
-
this.onProgress?.(0, 0);
|
|
37310
37773
|
}
|
|
37311
|
-
await this.output.start();
|
|
37312
|
-
this._start();
|
|
37313
37774
|
try {
|
|
37314
|
-
await Promise.all(this.
|
|
37775
|
+
await Promise.all(this._trackPumps.map((x) => x.resolvers.promise));
|
|
37315
37776
|
} catch (error) {
|
|
37316
|
-
if (
|
|
37777
|
+
if (this.state !== "canceled") {
|
|
37317
37778
|
void this.cancel();
|
|
37318
37779
|
}
|
|
37319
37780
|
throw error;
|
|
37781
|
+
} finally {
|
|
37782
|
+
options.pauseSignal?.removeEventListener("abort", onPause);
|
|
37320
37783
|
}
|
|
37321
|
-
if (this.
|
|
37784
|
+
if (this.state === "canceled") {
|
|
37322
37785
|
throw new ConversionCanceledError();
|
|
37323
37786
|
}
|
|
37324
|
-
|
|
37325
|
-
|
|
37326
|
-
|
|
37327
|
-
this.
|
|
37787
|
+
const isDone = this._trackPumps.every((x) => x.done);
|
|
37788
|
+
this.state = isDone ? "done" : "idle";
|
|
37789
|
+
if (isDone) {
|
|
37790
|
+
if (!this._composable) {
|
|
37791
|
+
await this.output.finalize();
|
|
37792
|
+
}
|
|
37793
|
+
if (this._computeProgress) {
|
|
37794
|
+
const minTimestamp = Math.min(...this._maxTimestamps.values());
|
|
37795
|
+
this.onProgress?.(1, minTimestamp);
|
|
37796
|
+
}
|
|
37328
37797
|
}
|
|
37329
37798
|
}
|
|
37330
37799
|
/**
|
|
@@ -37332,15 +37801,21 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37332
37801
|
* Does nothing if the conversion is already complete.
|
|
37333
37802
|
*/
|
|
37334
37803
|
async cancel() {
|
|
37335
|
-
if (this.
|
|
37804
|
+
if (this.state === "done") {
|
|
37336
37805
|
return;
|
|
37337
37806
|
}
|
|
37338
|
-
if (this.
|
|
37807
|
+
if (this.state === "canceled") {
|
|
37339
37808
|
Logging._warn("Conversion already canceled.");
|
|
37340
37809
|
return;
|
|
37341
37810
|
}
|
|
37342
|
-
this.
|
|
37343
|
-
|
|
37811
|
+
this.state = "canceled";
|
|
37812
|
+
for (const pump of this._trackPumps) {
|
|
37813
|
+
pump.wake?.();
|
|
37814
|
+
}
|
|
37815
|
+
this._synchronizer.resolveAll();
|
|
37816
|
+
if (!this._composable) {
|
|
37817
|
+
await this.output.cancel();
|
|
37818
|
+
}
|
|
37344
37819
|
}
|
|
37345
37820
|
/** @internal */
|
|
37346
37821
|
async _processVideoTrack(track, trackOptions, outputTrackId) {
|
|
@@ -37381,19 +37856,18 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37381
37856
|
}
|
|
37382
37857
|
const firstTimestamp = await track.getFirstTimestamp();
|
|
37383
37858
|
let videoCodecs = this.output.format.getSupportedVideoCodecs();
|
|
37384
|
-
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;
|
|
37859
|
+
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;
|
|
37385
37860
|
const alpha = trackOptions.alpha ?? "discard";
|
|
37386
37861
|
if (!needsTranscode) {
|
|
37387
37862
|
const source = new EncodedVideoPacketSource(sourceCodec);
|
|
37388
37863
|
videoSource = source;
|
|
37389
|
-
this.
|
|
37390
|
-
await this._started;
|
|
37864
|
+
this._registerTrackPump(async (pump) => {
|
|
37391
37865
|
const sink = new EncodedPacketSink(track);
|
|
37392
37866
|
const decoderConfig = await track.getDecoderConfig();
|
|
37393
37867
|
const meta = { decoderConfig: decoderConfig ?? void 0 };
|
|
37394
37868
|
for await (const packet of sink.packets(void 0, void 0, { verifyKeyPackets: true })) {
|
|
37395
|
-
if (this.
|
|
37396
|
-
|
|
37869
|
+
if (this.state === "canceled") {
|
|
37870
|
+
break;
|
|
37397
37871
|
}
|
|
37398
37872
|
if (packet.timestamp >= this._endTimestamp) {
|
|
37399
37873
|
break;
|
|
@@ -37408,10 +37882,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37408
37882
|
if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
|
|
37409
37883
|
await this._synchronizer.wait(modifiedPacket.timestamp);
|
|
37410
37884
|
}
|
|
37885
|
+
await this._checkpoint(pump, modifiedPacket.timestamp);
|
|
37411
37886
|
}
|
|
37412
37887
|
source.close();
|
|
37413
37888
|
this._synchronizer.closeTrack(outputTrackId);
|
|
37414
|
-
})
|
|
37889
|
+
});
|
|
37415
37890
|
} else {
|
|
37416
37891
|
const canDecode2 = await track.canDecode();
|
|
37417
37892
|
if (!canDecode2) {
|
|
@@ -37425,11 +37900,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37425
37900
|
if (trackOptions.codec) {
|
|
37426
37901
|
videoCodecs = videoCodecs.filter((codec) => codec === trackOptions.codec);
|
|
37427
37902
|
}
|
|
37428
|
-
const
|
|
37903
|
+
const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high");
|
|
37429
37904
|
const encodableCodec = await getFirstEncodableVideoCodec(videoCodecs, {
|
|
37430
37905
|
width: trackOptions.process && trackOptions.processedWidth ? trackOptions.processedWidth : width,
|
|
37431
37906
|
height: trackOptions.process && trackOptions.processedHeight ? trackOptions.processedHeight : height,
|
|
37432
|
-
|
|
37907
|
+
quality
|
|
37433
37908
|
});
|
|
37434
37909
|
if (!encodableCodec) {
|
|
37435
37910
|
this.discardedTracks.push({
|
|
@@ -37441,7 +37916,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37441
37916
|
}
|
|
37442
37917
|
const encodingConfig = {
|
|
37443
37918
|
codec: encodableCodec,
|
|
37444
|
-
|
|
37919
|
+
quality,
|
|
37445
37920
|
keyFrameInterval: trackOptions.keyFrameInterval,
|
|
37446
37921
|
sizeChangeBehavior: trackOptions.fit ?? "passThrough",
|
|
37447
37922
|
alpha,
|
|
@@ -37507,15 +37982,14 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37507
37982
|
};
|
|
37508
37983
|
const source = new VideoSampleSource(encodingConfig);
|
|
37509
37984
|
videoSource = source;
|
|
37510
|
-
this.
|
|
37511
|
-
await this._started;
|
|
37985
|
+
this._registerTrackPump(async (pump) => {
|
|
37512
37986
|
const sink = new VideoSampleSink(track);
|
|
37513
37987
|
for await (var _sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
|
37514
37988
|
var _stack2 = [];
|
|
37515
37989
|
try {
|
|
37516
37990
|
const sample = __using(_stack2, _sample);
|
|
37517
|
-
if (this.
|
|
37518
|
-
|
|
37991
|
+
if (this.state === "canceled") {
|
|
37992
|
+
break;
|
|
37519
37993
|
}
|
|
37520
37994
|
const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
|
|
37521
37995
|
sample.setTimestamp(adjustedSampleTimestamp);
|
|
@@ -37526,6 +38000,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37526
38000
|
if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) {
|
|
37527
38001
|
await this._synchronizer.wait(lastSampleTimestamp);
|
|
37528
38002
|
}
|
|
38003
|
+
await this._checkpoint(pump, lastSampleTimestamp);
|
|
37529
38004
|
}
|
|
37530
38005
|
} catch (_2) {
|
|
37531
38006
|
var _error2 = _2, _hasError2 = true;
|
|
@@ -37535,10 +38010,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37535
38010
|
}
|
|
37536
38011
|
source.close();
|
|
37537
38012
|
this._synchronizer.closeTrack(outputTrackId);
|
|
37538
|
-
})
|
|
38013
|
+
});
|
|
37539
38014
|
}
|
|
37540
38015
|
let ownGroup = null;
|
|
37541
|
-
if (!trackOptions.group) {
|
|
38016
|
+
if (!trackOptions.group && !this._composable) {
|
|
37542
38017
|
ownGroup = new OutputTrackGroup();
|
|
37543
38018
|
}
|
|
37544
38019
|
const videoTrackLanguageCode = await track.getLanguageCode();
|
|
@@ -37551,8 +38026,6 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37551
38026
|
rotation: outputTrackRotation,
|
|
37552
38027
|
group: ownGroup ?? trackOptions.group
|
|
37553
38028
|
});
|
|
37554
|
-
this._addedCounts.video++;
|
|
37555
|
-
this._totalTrackCount++;
|
|
37556
38029
|
this.utilizedTracks.push(track);
|
|
37557
38030
|
this._outputTrackIds.push(outputTrackId);
|
|
37558
38031
|
this._outputOwnTrackGroups.push(ownGroup);
|
|
@@ -37577,17 +38050,16 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37577
38050
|
const needsTrimming = firstTimestamp < this._startTimestamp;
|
|
37578
38051
|
let needsPadding = firstTimestamp > this._startTimestamp && !this.output.format.supportsTimestampedMediaData;
|
|
37579
38052
|
let audioCodecs = this.output.format.getSupportedAudioCodecs();
|
|
37580
|
-
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) {
|
|
38053
|
+
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) {
|
|
37581
38054
|
const source = new EncodedAudioPacketSource(sourceCodec);
|
|
37582
38055
|
audioSource = source;
|
|
37583
|
-
this.
|
|
37584
|
-
await this._started;
|
|
38056
|
+
this._registerTrackPump(async (pump) => {
|
|
37585
38057
|
const sink = new EncodedPacketSink(track);
|
|
37586
38058
|
const decoderConfig = await track.getDecoderConfig();
|
|
37587
38059
|
const meta = { decoderConfig: decoderConfig ?? void 0 };
|
|
37588
38060
|
for await (const packet of sink.packets()) {
|
|
37589
|
-
if (this.
|
|
37590
|
-
|
|
38061
|
+
if (this.state === "canceled") {
|
|
38062
|
+
break;
|
|
37591
38063
|
}
|
|
37592
38064
|
if (packet.timestamp >= this._endTimestamp) {
|
|
37593
38065
|
break;
|
|
@@ -37601,10 +38073,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37601
38073
|
if (this._synchronizer.shouldWait(outputTrackId, modifiedPacket.timestamp)) {
|
|
37602
38074
|
await this._synchronizer.wait(modifiedPacket.timestamp);
|
|
37603
38075
|
}
|
|
38076
|
+
await this._checkpoint(pump, modifiedPacket.timestamp);
|
|
37604
38077
|
}
|
|
37605
38078
|
source.close();
|
|
37606
38079
|
this._synchronizer.closeTrack(outputTrackId);
|
|
37607
|
-
})
|
|
38080
|
+
});
|
|
37608
38081
|
} else {
|
|
37609
38082
|
const canDecode2 = await track.canDecode();
|
|
37610
38083
|
if (!canDecode2) {
|
|
@@ -37619,17 +38092,17 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37619
38092
|
if (trackOptions.codec) {
|
|
37620
38093
|
audioCodecs = audioCodecs.filter((codec) => codec === trackOptions.codec);
|
|
37621
38094
|
}
|
|
37622
|
-
const
|
|
38095
|
+
const quality = resolveQuality(trackOptions.quality, trackOptions.bitrate) ?? new Quality("high");
|
|
37623
38096
|
const encodableCodecs = await getEncodableAudioCodecs(audioCodecs, {
|
|
37624
38097
|
numberOfChannels: trackOptions.process && trackOptions.processedNumberOfChannels ? trackOptions.processedNumberOfChannels : numberOfChannels,
|
|
37625
38098
|
sampleRate: trackOptions.process && trackOptions.processedSampleRate ? trackOptions.processedSampleRate : sampleRate,
|
|
37626
|
-
|
|
38099
|
+
quality
|
|
37627
38100
|
});
|
|
37628
38101
|
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)) {
|
|
37629
38102
|
const encodableCodecsWithDefaultParams = await getEncodableAudioCodecs(audioCodecs, {
|
|
37630
38103
|
numberOfChannels: FALLBACK_NUMBER_OF_CHANNELS,
|
|
37631
38104
|
sampleRate: FALLBACK_SAMPLE_RATE,
|
|
37632
|
-
|
|
38105
|
+
quality
|
|
37633
38106
|
});
|
|
37634
38107
|
const nonPcmCodec = encodableCodecsWithDefaultParams.find((codec) => NON_PCM_AUDIO_CODECS.includes(codec));
|
|
37635
38108
|
if (nonPcmCodec) {
|
|
@@ -37650,7 +38123,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37650
38123
|
}
|
|
37651
38124
|
const encodingConfig = {
|
|
37652
38125
|
codec: codecOfChoice,
|
|
37653
|
-
|
|
38126
|
+
quality,
|
|
37654
38127
|
transform: {
|
|
37655
38128
|
sampleFormat: trackOptions.sampleFormat,
|
|
37656
38129
|
process: trackOptions.process
|
|
@@ -37669,8 +38142,7 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37669
38142
|
};
|
|
37670
38143
|
const source = new AudioSampleSource(encodingConfig);
|
|
37671
38144
|
audioSource = source;
|
|
37672
|
-
this.
|
|
37673
|
-
await this._started;
|
|
38145
|
+
this._registerTrackPump(async (pump) => {
|
|
37674
38146
|
const sink = new AudioSampleSink(track);
|
|
37675
38147
|
for await (var _sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
|
37676
38148
|
var _stack3 = [];
|
|
@@ -37678,8 +38150,8 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37678
38150
|
const sample = __using(_stack3, _sample);
|
|
37679
38151
|
var _stack2 = [];
|
|
37680
38152
|
try {
|
|
37681
|
-
if (this.
|
|
37682
|
-
|
|
38153
|
+
if (this.state === "canceled") {
|
|
38154
|
+
break;
|
|
37683
38155
|
}
|
|
37684
38156
|
if (needsPadding) {
|
|
37685
38157
|
var _stack = [];
|
|
@@ -37700,7 +38172,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37700
38172
|
sampleRate: originalSampleRate,
|
|
37701
38173
|
timestamp: 0
|
|
37702
38174
|
}));
|
|
37703
|
-
await this._registerAudioSample(
|
|
38175
|
+
await this._registerAudioSample(
|
|
38176
|
+
pump,
|
|
38177
|
+
silentSample,
|
|
38178
|
+
source,
|
|
38179
|
+
outputTrackId,
|
|
38180
|
+
() => lastSampleTimestamp
|
|
38181
|
+
);
|
|
37704
38182
|
needsPadding = false;
|
|
37705
38183
|
} catch (_) {
|
|
37706
38184
|
var _error = _, _hasError = true;
|
|
@@ -37730,7 +38208,13 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37730
38208
|
}
|
|
37731
38209
|
const finalSample = __using(_stack2, finalSampleLet);
|
|
37732
38210
|
finalSample.setTimestamp(finalSample.timestamp - this._startTimestamp);
|
|
37733
|
-
await this._registerAudioSample(
|
|
38211
|
+
await this._registerAudioSample(
|
|
38212
|
+
pump,
|
|
38213
|
+
finalSample,
|
|
38214
|
+
source,
|
|
38215
|
+
outputTrackId,
|
|
38216
|
+
() => lastSampleTimestamp
|
|
38217
|
+
);
|
|
37734
38218
|
} catch (_2) {
|
|
37735
38219
|
var _error2 = _2, _hasError2 = true;
|
|
37736
38220
|
} finally {
|
|
@@ -37744,10 +38228,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37744
38228
|
}
|
|
37745
38229
|
source.close();
|
|
37746
38230
|
this._synchronizer.closeTrack(outputTrackId);
|
|
37747
|
-
})
|
|
38231
|
+
});
|
|
37748
38232
|
}
|
|
37749
38233
|
let ownGroup = null;
|
|
37750
|
-
if (!trackOptions.group) {
|
|
38234
|
+
if (!trackOptions.group && !this._composable) {
|
|
37751
38235
|
ownGroup = new OutputTrackGroup();
|
|
37752
38236
|
}
|
|
37753
38237
|
const audioTrackLanguageCode = await track.getLanguageCode();
|
|
@@ -37758,14 +38242,12 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37758
38242
|
disposition: await track.getDisposition(),
|
|
37759
38243
|
group: ownGroup ?? trackOptions.group
|
|
37760
38244
|
});
|
|
37761
|
-
this._addedCounts.audio++;
|
|
37762
|
-
this._totalTrackCount++;
|
|
37763
38245
|
this.utilizedTracks.push(track);
|
|
37764
38246
|
this._outputTrackIds.push(outputTrackId);
|
|
37765
38247
|
this._outputOwnTrackGroups.push(ownGroup);
|
|
37766
38248
|
}
|
|
37767
38249
|
/** @internal */
|
|
37768
|
-
async _registerAudioSample(sample, source, outputTrackId, getLastSampleTimestamp) {
|
|
38250
|
+
async _registerAudioSample(pump, sample, source, outputTrackId, getLastSampleTimestamp) {
|
|
37769
38251
|
this._reportProgress(outputTrackId, sample.timestamp + sample.duration);
|
|
37770
38252
|
await source.add(sample);
|
|
37771
38253
|
sample.close();
|
|
@@ -37774,6 +38256,33 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37774
38256
|
if (this._synchronizer.shouldWait(outputTrackId, lastSampleTimestamp)) {
|
|
37775
38257
|
await this._synchronizer.wait(lastSampleTimestamp);
|
|
37776
38258
|
}
|
|
38259
|
+
await this._checkpoint(pump, lastSampleTimestamp);
|
|
38260
|
+
}
|
|
38261
|
+
}
|
|
38262
|
+
/** @internal */
|
|
38263
|
+
_registerTrackPump(fn) {
|
|
38264
|
+
const pump = {
|
|
38265
|
+
done: false,
|
|
38266
|
+
resolvers: promiseWithResolvers(),
|
|
38267
|
+
wake: null,
|
|
38268
|
+
start: () => {
|
|
38269
|
+
void fn(pump).then(() => {
|
|
38270
|
+
pump.done = true;
|
|
38271
|
+
pump.resolvers.resolve();
|
|
38272
|
+
}, (error) => {
|
|
38273
|
+
pump.resolvers.reject(error);
|
|
38274
|
+
});
|
|
38275
|
+
}
|
|
38276
|
+
};
|
|
38277
|
+
this._trackPumps.push(pump);
|
|
38278
|
+
}
|
|
38279
|
+
/** @internal */
|
|
38280
|
+
async _checkpoint(pump, timestamp) {
|
|
38281
|
+
while (this.state !== "canceled" && (timestamp >= this._executionUntil || this._pauseRequested)) {
|
|
38282
|
+
pump.resolvers.resolve();
|
|
38283
|
+
const { promise, resolve } = promiseWithResolvers();
|
|
38284
|
+
pump.wake = resolve;
|
|
38285
|
+
await promise;
|
|
37777
38286
|
}
|
|
37778
38287
|
}
|
|
37779
38288
|
/** @internal */
|
|
@@ -37803,10 +38312,11 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37803
38312
|
};
|
|
37804
38313
|
var MAX_TIMESTAMP_GAP = 1;
|
|
37805
38314
|
var TrackSynchronizer = class {
|
|
37806
|
-
constructor() {
|
|
38315
|
+
constructor(conversion) {
|
|
37807
38316
|
this.maxTimestamps = /* @__PURE__ */ new Map();
|
|
37808
38317
|
// Track ID -> timestamp
|
|
37809
38318
|
this.resolvers = [];
|
|
38319
|
+
this.conversion = conversion;
|
|
37810
38320
|
}
|
|
37811
38321
|
declareTrack(trackId) {
|
|
37812
38322
|
this.maxTimestamps.set(trackId, 0);
|
|
@@ -37816,6 +38326,9 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37816
38326
|
assert(currentValue !== void 0);
|
|
37817
38327
|
this.maxTimestamps.set(trackId, Math.max(timestamp, currentValue));
|
|
37818
38328
|
const newMin = this.computeMinAndMaybeResolve();
|
|
38329
|
+
if (this.conversion.state === "canceled" || this.conversion._pauseRequested || timestamp >= this.conversion._executionUntil) {
|
|
38330
|
+
return false;
|
|
38331
|
+
}
|
|
37819
38332
|
return timestamp - newMin > MAX_TIMESTAMP_GAP;
|
|
37820
38333
|
}
|
|
37821
38334
|
wait(timestamp) {
|
|
@@ -37830,6 +38343,12 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
37830
38343
|
this.maxTimestamps.delete(trackId);
|
|
37831
38344
|
this.computeMinAndMaybeResolve();
|
|
37832
38345
|
}
|
|
38346
|
+
resolveAll() {
|
|
38347
|
+
for (const entry of this.resolvers) {
|
|
38348
|
+
entry.resolve();
|
|
38349
|
+
}
|
|
38350
|
+
this.resolvers.length = 0;
|
|
38351
|
+
}
|
|
37833
38352
|
computeMinAndMaybeResolve() {
|
|
37834
38353
|
let newMin = Infinity;
|
|
37835
38354
|
for (const [, timestamp] of this.maxTimestamps) {
|