@bendyline/squisq-video-react 2.5.28 → 2.5.29
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/{captureVideoCodecs-HVCZBBNT.js → captureVideoCodecs-3QXX66ZI.js} +1 -1
- package/dist/chunk-AARDBHRJ.js +129 -0
- package/dist/{chunk-EJ3DVPFF.js → chunk-MOIVICAD.js} +1292 -0
- package/dist/{chunk-3IMR25SX.js → chunk-MVEZDB4G.js} +1 -1
- package/dist/{chunk-XPWYKETM.js → chunk-QKTQL3NK.js} +1 -1
- package/dist/{chunk-JKRF65OD.js → chunk-QZSP5DGO.js} +1 -1
- package/dist/{chunk-J6MJYH2I.js → chunk-SGLTT5CL.js} +62 -106
- package/dist/chunk-VKGKDTWK.js +292 -0
- package/dist/{chunk-VE3OUBRG.js → chunk-XDJ2FPQQ.js} +1 -1
- package/dist/{chunk-TRCTDIWS.js → chunk-XPPQWQEO.js} +189 -3
- package/dist/{chunk-ZEA6XJL7.js → chunk-YO7JVV3K.js} +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +4 -4
- package/dist/cover-image/index.js +2 -2
- package/dist/dashboard-image/index.js +1 -1
- package/dist/encoder/index.js +1 -1
- package/dist/hooks/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/{lossyEncoder-27NI5K4Y.js → lossyEncoder-U4V3AER7.js} +1 -1
- package/dist/media-edit/index.d.ts +2 -2
- package/dist/media-edit/index.js +1 -1
- package/dist/mediaBakeEngine-IHLCD2GH.js +12 -0
- package/dist/{mediaEditEngine-3WUTBNJN.js → mediaEditEngine-22T4WV4X.js} +5 -2
- package/dist/{mediaEditRenderManager-W_o6RRhT.d.ts → mediaEditRenderManager-DhqIn8yo.d.ts} +97 -5
- package/dist/workers/mediaEdit.worker.js +15 -2
- package/package.json +4 -4
|
@@ -587,6 +587,9 @@ var roundToMultiple = (value, multiple) => {
|
|
|
587
587
|
var roundToDivisor = (value, multiple) => {
|
|
588
588
|
return Math.round(value * multiple) / multiple;
|
|
589
589
|
};
|
|
590
|
+
var floorToDivisor = (value, multiple) => {
|
|
591
|
+
return Math.floor(value * multiple) / multiple;
|
|
592
|
+
};
|
|
590
593
|
var ilog = (x) => {
|
|
591
594
|
let ret = 0;
|
|
592
595
|
while (x) {
|
|
@@ -674,6 +677,9 @@ var missingWebCodecsClassMessage = (className) => {
|
|
|
674
677
|
}
|
|
675
678
|
return `${className} is not available in this environment.`;
|
|
676
679
|
};
|
|
680
|
+
var isCodecReclaimedError = (error) => {
|
|
681
|
+
return error instanceof DOMException && error.name === "QuotaExceededError" && /reclaimed/i.test(error.message);
|
|
682
|
+
};
|
|
677
683
|
var NativePromiseConstructor = (async () => {
|
|
678
684
|
})().constructor;
|
|
679
685
|
var isThenable = (value) => {
|
|
@@ -803,6 +809,9 @@ var validateRectangle = (rect, propertyPath) => {
|
|
|
803
809
|
throw new TypeError(`${propertyPath}.height must be a non-negative integer.`);
|
|
804
810
|
}
|
|
805
811
|
};
|
|
812
|
+
var wait = (ms) => {
|
|
813
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
814
|
+
};
|
|
806
815
|
var toArray = (x) => {
|
|
807
816
|
if (Array.isArray(x)) {
|
|
808
817
|
return x;
|
|
@@ -3860,6 +3869,50 @@ var AVC_LEVEL_TABLE = [
|
|
|
3860
3869
|
{ maxMacroblocks: 139264, maxBitrate: 8e8, maxDpbMbs: 696320, level: 62 }
|
|
3861
3870
|
// Level 6.2
|
|
3862
3871
|
];
|
|
3872
|
+
var HEVC_LEVEL_TABLE = [
|
|
3873
|
+
{ maxPictureSize: 36864, maxBitrate: 128e3, tier: "L", level: 30 },
|
|
3874
|
+
// Level 1 (Low Tier)
|
|
3875
|
+
{ maxPictureSize: 122880, maxBitrate: 15e5, tier: "L", level: 60 },
|
|
3876
|
+
// Level 2 (Low Tier)
|
|
3877
|
+
{ maxPictureSize: 245760, maxBitrate: 3e6, tier: "L", level: 63 },
|
|
3878
|
+
// Level 2.1 (Low Tier)
|
|
3879
|
+
{ maxPictureSize: 552960, maxBitrate: 6e6, tier: "L", level: 90 },
|
|
3880
|
+
// Level 3 (Low Tier)
|
|
3881
|
+
{ maxPictureSize: 983040, maxBitrate: 1e7, tier: "L", level: 93 },
|
|
3882
|
+
// Level 3.1 (Low Tier)
|
|
3883
|
+
{ maxPictureSize: 2228224, maxBitrate: 12e6, tier: "L", level: 120 },
|
|
3884
|
+
// Level 4 (Low Tier)
|
|
3885
|
+
{ maxPictureSize: 2228224, maxBitrate: 3e7, tier: "H", level: 120 },
|
|
3886
|
+
// Level 4 (High Tier)
|
|
3887
|
+
{ maxPictureSize: 2228224, maxBitrate: 2e7, tier: "L", level: 123 },
|
|
3888
|
+
// Level 4.1 (Low Tier)
|
|
3889
|
+
{ maxPictureSize: 2228224, maxBitrate: 5e7, tier: "H", level: 123 },
|
|
3890
|
+
// Level 4.1 (High Tier)
|
|
3891
|
+
{ maxPictureSize: 8912896, maxBitrate: 25e6, tier: "L", level: 150 },
|
|
3892
|
+
// Level 5 (Low Tier)
|
|
3893
|
+
{ maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 150 },
|
|
3894
|
+
// Level 5 (High Tier)
|
|
3895
|
+
{ maxPictureSize: 8912896, maxBitrate: 4e7, tier: "L", level: 153 },
|
|
3896
|
+
// Level 5.1 (Low Tier)
|
|
3897
|
+
{ maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 153 },
|
|
3898
|
+
// Level 5.1 (High Tier)
|
|
3899
|
+
{ maxPictureSize: 8912896, maxBitrate: 6e7, tier: "L", level: 156 },
|
|
3900
|
+
// Level 5.2 (Low Tier)
|
|
3901
|
+
{ maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 156 },
|
|
3902
|
+
// Level 5.2 (High Tier)
|
|
3903
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "L", level: 180 },
|
|
3904
|
+
// Level 6 (Low Tier)
|
|
3905
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 180 },
|
|
3906
|
+
// Level 6 (High Tier)
|
|
3907
|
+
{ maxPictureSize: 35651584, maxBitrate: 12e7, tier: "L", level: 183 },
|
|
3908
|
+
// Level 6.1 (Low Tier)
|
|
3909
|
+
{ maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 183 },
|
|
3910
|
+
// Level 6.1 (High Tier)
|
|
3911
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "L", level: 186 },
|
|
3912
|
+
// Level 6.2 (Low Tier)
|
|
3913
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 186 }
|
|
3914
|
+
// Level 6.2 (High Tier)
|
|
3915
|
+
];
|
|
3863
3916
|
var VP9_LEVEL_TABLE = [
|
|
3864
3917
|
{ maxPictureSize: 36864, maxBitrate: 2e5, level: 10 },
|
|
3865
3918
|
// Level 1
|
|
@@ -3890,6 +3943,56 @@ var VP9_LEVEL_TABLE = [
|
|
|
3890
3943
|
{ maxPictureSize: 35651584, maxBitrate: 48e7, level: 62 }
|
|
3891
3944
|
// Level 6.2
|
|
3892
3945
|
];
|
|
3946
|
+
var AV1_LEVEL_TABLE = [
|
|
3947
|
+
{ maxPictureSize: 147456, maxBitrate: 15e5, tier: "M", level: 0 },
|
|
3948
|
+
// Level 2.0 (Main Tier)
|
|
3949
|
+
{ maxPictureSize: 278784, maxBitrate: 3e6, tier: "M", level: 1 },
|
|
3950
|
+
// Level 2.1 (Main Tier)
|
|
3951
|
+
{ maxPictureSize: 665856, maxBitrate: 6e6, tier: "M", level: 4 },
|
|
3952
|
+
// Level 3.0 (Main Tier)
|
|
3953
|
+
{ maxPictureSize: 1065024, maxBitrate: 1e7, tier: "M", level: 5 },
|
|
3954
|
+
// Level 3.1 (Main Tier)
|
|
3955
|
+
{ maxPictureSize: 2359296, maxBitrate: 12e6, tier: "M", level: 8 },
|
|
3956
|
+
// Level 4.0 (Main Tier)
|
|
3957
|
+
{ maxPictureSize: 2359296, maxBitrate: 3e7, tier: "H", level: 8 },
|
|
3958
|
+
// Level 4.0 (High Tier)
|
|
3959
|
+
{ maxPictureSize: 2359296, maxBitrate: 2e7, tier: "M", level: 9 },
|
|
3960
|
+
// Level 4.1 (Main Tier)
|
|
3961
|
+
{ maxPictureSize: 2359296, maxBitrate: 5e7, tier: "H", level: 9 },
|
|
3962
|
+
// Level 4.1 (High Tier)
|
|
3963
|
+
{ maxPictureSize: 8912896, maxBitrate: 3e7, tier: "M", level: 12 },
|
|
3964
|
+
// Level 5.0 (Main Tier)
|
|
3965
|
+
{ maxPictureSize: 8912896, maxBitrate: 1e8, tier: "H", level: 12 },
|
|
3966
|
+
// Level 5.0 (High Tier)
|
|
3967
|
+
{ maxPictureSize: 8912896, maxBitrate: 4e7, tier: "M", level: 13 },
|
|
3968
|
+
// Level 5.1 (Main Tier)
|
|
3969
|
+
{ maxPictureSize: 8912896, maxBitrate: 16e7, tier: "H", level: 13 },
|
|
3970
|
+
// Level 5.1 (High Tier)
|
|
3971
|
+
{ maxPictureSize: 8912896, maxBitrate: 6e7, tier: "M", level: 14 },
|
|
3972
|
+
// Level 5.2 (Main Tier)
|
|
3973
|
+
{ maxPictureSize: 8912896, maxBitrate: 24e7, tier: "H", level: 14 },
|
|
3974
|
+
// Level 5.2 (High Tier)
|
|
3975
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 15 },
|
|
3976
|
+
// Level 5.3 (Main Tier)
|
|
3977
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 15 },
|
|
3978
|
+
// Level 5.3 (High Tier)
|
|
3979
|
+
{ maxPictureSize: 35651584, maxBitrate: 6e7, tier: "M", level: 16 },
|
|
3980
|
+
// Level 6.0 (Main Tier)
|
|
3981
|
+
{ maxPictureSize: 35651584, maxBitrate: 24e7, tier: "H", level: 16 },
|
|
3982
|
+
// Level 6.0 (High Tier)
|
|
3983
|
+
{ maxPictureSize: 35651584, maxBitrate: 1e8, tier: "M", level: 17 },
|
|
3984
|
+
// Level 6.1 (Main Tier)
|
|
3985
|
+
{ maxPictureSize: 35651584, maxBitrate: 48e7, tier: "H", level: 17 },
|
|
3986
|
+
// Level 6.1 (High Tier)
|
|
3987
|
+
{ maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 18 },
|
|
3988
|
+
// Level 6.2 (Main Tier)
|
|
3989
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 18 },
|
|
3990
|
+
// Level 6.2 (High Tier)
|
|
3991
|
+
{ maxPictureSize: 35651584, maxBitrate: 16e7, tier: "M", level: 19 },
|
|
3992
|
+
// Level 6.3 (Main Tier)
|
|
3993
|
+
{ maxPictureSize: 35651584, maxBitrate: 8e8, tier: "H", level: 19 }
|
|
3994
|
+
// Level 6.3 (High Tier)
|
|
3995
|
+
];
|
|
3893
3996
|
var VP9_DEFAULT_SUFFIX = ".01.01.01.01.00";
|
|
3894
3997
|
var AV1_DEFAULT_SUFFIX = ".0.110.01.01.01.0";
|
|
3895
3998
|
var PRORES_FOURCCS = [
|
|
@@ -3916,6 +4019,72 @@ var DTS_FOURCCS = [
|
|
|
3916
4019
|
"dtse"
|
|
3917
4020
|
// DTS Express
|
|
3918
4021
|
];
|
|
4022
|
+
var PRORES_PROFILE_TARGET_BITRATES = [
|
|
4023
|
+
{ fourCc: "apco", bitrate: 45e6, alpha: false },
|
|
4024
|
+
// 422 Proxy
|
|
4025
|
+
{ fourCc: "apcs", bitrate: 102e6, alpha: false },
|
|
4026
|
+
// 422 LT
|
|
4027
|
+
{ fourCc: "apcn", bitrate: 147e6, alpha: false },
|
|
4028
|
+
// 422 Standard
|
|
4029
|
+
{ fourCc: "apch", bitrate: 22e7, alpha: false },
|
|
4030
|
+
// 422 HQ
|
|
4031
|
+
{ fourCc: "ap4h", bitrate: 33e7, alpha: true },
|
|
4032
|
+
// 4444
|
|
4033
|
+
{ fourCc: "ap4x", bitrate: 5e8, alpha: true }
|
|
4034
|
+
// 4444 XQ
|
|
4035
|
+
];
|
|
4036
|
+
var buildVideoCodecString = (codec, width, height, bitrate, alpha) => {
|
|
4037
|
+
if (codec === "avc") {
|
|
4038
|
+
const profileIndication = 100;
|
|
4039
|
+
const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
|
|
4040
|
+
const levelInfo = AVC_LEVEL_TABLE.find((level) => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate) ?? last(AVC_LEVEL_TABLE);
|
|
4041
|
+
const levelIndication = levelInfo ? levelInfo.level : 0;
|
|
4042
|
+
const hexProfileIndication = profileIndication.toString(16).padStart(2, "0");
|
|
4043
|
+
const hexProfileCompatibility = "00";
|
|
4044
|
+
const hexLevelIndication = levelIndication.toString(16).padStart(2, "0");
|
|
4045
|
+
return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`;
|
|
4046
|
+
} else if (codec === "hevc") {
|
|
4047
|
+
const profilePrefix = "";
|
|
4048
|
+
const profileIdc = 1;
|
|
4049
|
+
const compatibilityFlags = "6";
|
|
4050
|
+
const pictureSize = width * height;
|
|
4051
|
+
const levelInfo = HEVC_LEVEL_TABLE.find((level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(HEVC_LEVEL_TABLE);
|
|
4052
|
+
const constraintFlags = "B0";
|
|
4053
|
+
return `hev1.${profilePrefix}${profileIdc}.${compatibilityFlags}.${levelInfo.tier}${levelInfo.level}.${constraintFlags}`;
|
|
4054
|
+
} else if (codec === "vp8") {
|
|
4055
|
+
return "vp8";
|
|
4056
|
+
} else if (codec === "vp9") {
|
|
4057
|
+
const profile = "00";
|
|
4058
|
+
const pictureSize = width * height;
|
|
4059
|
+
const levelInfo = VP9_LEVEL_TABLE.find((level) => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(VP9_LEVEL_TABLE);
|
|
4060
|
+
const bitDepth = "08";
|
|
4061
|
+
return `vp09.${profile}.${levelInfo.level.toString().padStart(2, "0")}.${bitDepth}`;
|
|
4062
|
+
} else if (codec === "av1") {
|
|
4063
|
+
const profile = 0;
|
|
4064
|
+
const pictureSize = width * height;
|
|
4065
|
+
const levelInfo = AV1_LEVEL_TABLE.find((level2) => pictureSize <= level2.maxPictureSize && bitrate <= level2.maxBitrate) ?? last(AV1_LEVEL_TABLE);
|
|
4066
|
+
const level = levelInfo.level.toString().padStart(2, "0");
|
|
4067
|
+
const bitDepth = "08";
|
|
4068
|
+
return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
|
|
4069
|
+
} else if (codec === "prores") {
|
|
4070
|
+
const referencePixels = 1920 * 1080;
|
|
4071
|
+
const scaleFactor = Math.pow(width * height / referencePixels, 0.95);
|
|
4072
|
+
const candidates = PRORES_PROFILE_TARGET_BITRATES.filter((x) => x.alpha === alpha);
|
|
4073
|
+
let bestFourCc = candidates[0].fourCc;
|
|
4074
|
+
let smallestDifference = Infinity;
|
|
4075
|
+
for (const { fourCc, bitrate: targetBitrate } of candidates) {
|
|
4076
|
+
const difference = Math.abs(targetBitrate * scaleFactor - bitrate);
|
|
4077
|
+
if (difference < smallestDifference) {
|
|
4078
|
+
smallestDifference = difference;
|
|
4079
|
+
bestFourCc = fourCc;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
return bestFourCc;
|
|
4083
|
+
} else {
|
|
4084
|
+
assertNever(codec);
|
|
4085
|
+
}
|
|
4086
|
+
throw new TypeError(`Unhandled codec '${String(codec)}'.`);
|
|
4087
|
+
};
|
|
3919
4088
|
var generateVp9CodecConfigurationFromCodecString = (codecString) => {
|
|
3920
4089
|
const parts = codecString.split(".");
|
|
3921
4090
|
const profile = Number(parts[1]);
|
|
@@ -4312,6 +4481,24 @@ var inferCodecFromCodecString = (codecString) => {
|
|
|
4312
4481
|
}
|
|
4313
4482
|
return null;
|
|
4314
4483
|
};
|
|
4484
|
+
var getVideoEncoderConfigExtension = (codec) => {
|
|
4485
|
+
if (codec === "avc") {
|
|
4486
|
+
return {
|
|
4487
|
+
avc: {
|
|
4488
|
+
format: "avc"
|
|
4489
|
+
// Ensure the format is not Annex B
|
|
4490
|
+
}
|
|
4491
|
+
};
|
|
4492
|
+
} else if (codec === "hevc") {
|
|
4493
|
+
return {
|
|
4494
|
+
hevc: {
|
|
4495
|
+
format: "hevc"
|
|
4496
|
+
// Ensure the format is not Annex B
|
|
4497
|
+
}
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
return {};
|
|
4501
|
+
};
|
|
4315
4502
|
var getAudioEncoderConfigExtension = (codec) => {
|
|
4316
4503
|
if (codec === "aac") {
|
|
4317
4504
|
return {
|
|
@@ -17520,7 +17707,148 @@ var audioSampleToInterleavedFormat = (sample, format) => {
|
|
|
17520
17707
|
};
|
|
17521
17708
|
|
|
17522
17709
|
// ../../node_modules/mediabunny/dist/modules/src/encode.js
|
|
17710
|
+
var canEncodeVideoMemo = /* @__PURE__ */ new Map();
|
|
17523
17711
|
var canEncodeAudioMemo = /* @__PURE__ */ new Map();
|
|
17712
|
+
var validateVideoEncodingConfig = (config) => {
|
|
17713
|
+
if (!config || typeof config !== "object") {
|
|
17714
|
+
throw new TypeError("Encoding config must be an object.");
|
|
17715
|
+
}
|
|
17716
|
+
if (!VIDEO_CODECS.includes(config.codec)) {
|
|
17717
|
+
throw new TypeError(`Invalid video codec '${config.codec}'. Must be one of: ${VIDEO_CODECS.join(", ")}.`);
|
|
17718
|
+
}
|
|
17719
|
+
const bitrate = config.bitrate;
|
|
17720
|
+
if (config.quality === void 0 && bitrate === void 0) {
|
|
17721
|
+
throw new TypeError("config.quality must be provided.");
|
|
17722
|
+
}
|
|
17723
|
+
if (config.quality !== void 0 && bitrate !== void 0) {
|
|
17724
|
+
throw new TypeError("config.quality and config.bitrate cannot both be provided.");
|
|
17725
|
+
}
|
|
17726
|
+
if (config.quality !== void 0 && !(config.quality instanceof Quality)) {
|
|
17727
|
+
throw new TypeError("config.quality, when provided, must be a Quality.");
|
|
17728
|
+
}
|
|
17729
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
17730
|
+
throw new TypeError("config.bitrate, when provided, must be a positive integer or a quality.");
|
|
17731
|
+
}
|
|
17732
|
+
if (config.keyFrameInterval !== void 0 && (!Number.isFinite(config.keyFrameInterval) || config.keyFrameInterval < 0)) {
|
|
17733
|
+
throw new TypeError("config.keyFrameInterval, when provided, must be a non-negative number.");
|
|
17734
|
+
}
|
|
17735
|
+
if (config.sizeChangeBehavior !== void 0 && !["deny", "passThrough", "fill", "contain", "cover"].includes(config.sizeChangeBehavior)) {
|
|
17736
|
+
throw new TypeError("config.sizeChangeBehavior, when provided, must be 'deny', 'passThrough', 'fill', 'contain' or 'cover'.");
|
|
17737
|
+
}
|
|
17738
|
+
if (config.transform !== void 0) {
|
|
17739
|
+
if (typeof config.transform !== "object" || !config.transform) {
|
|
17740
|
+
throw new TypeError("config.transform, when provided, must be an object.");
|
|
17741
|
+
}
|
|
17742
|
+
if (config.transform.width !== void 0 && (!Number.isInteger(config.transform.width) || config.transform.width <= 0)) {
|
|
17743
|
+
throw new TypeError("config.transform.width, when provided, must be a positive integer.");
|
|
17744
|
+
}
|
|
17745
|
+
if (config.transform.height !== void 0 && (!Number.isInteger(config.transform.height) || config.transform.height <= 0)) {
|
|
17746
|
+
throw new TypeError("config.transform.height, when provided, must be a positive integer.");
|
|
17747
|
+
}
|
|
17748
|
+
if (config.transform.fit !== void 0 && !["fill", "contain", "cover"].includes(config.transform.fit)) {
|
|
17749
|
+
throw new TypeError('config.transform.fit, when provided, must be one of "fill", "contain", or "cover".');
|
|
17750
|
+
}
|
|
17751
|
+
if (config.transform.width !== void 0 && config.transform.height !== void 0 && config.transform.fit === void 0 && !["fill", "contain", "cover"].includes(config.sizeChangeBehavior)) {
|
|
17752
|
+
throw new TypeError("When both config.transform.width and config.transform.height are provided, config.transform.fit must also be provided.");
|
|
17753
|
+
}
|
|
17754
|
+
if (config.transform.fit !== void 0 && ["fill", "contain", "cover"].includes(config.sizeChangeBehavior) && config.transform.fit !== config.sizeChangeBehavior) {
|
|
17755
|
+
throw new TypeError("config.transform.fit, when provided, cannot differ from config.sizeChangeBehavior when config.sizeChangeBehavior is 'fill', 'contain' or 'cover', as sizeChangeBehavior already determines the fitting algorithm.");
|
|
17756
|
+
}
|
|
17757
|
+
if (config.transform.rotate !== void 0 && ![0, 90, 180, 270].includes(config.transform.rotate)) {
|
|
17758
|
+
throw new TypeError("config.transform.rotate, when provided, must be 0, 90, 180 or 270.");
|
|
17759
|
+
}
|
|
17760
|
+
if (config.transform.flip !== void 0 && typeof config.transform.flip !== "boolean") {
|
|
17761
|
+
throw new TypeError("config.transform.flip, when provided, must be a boolean.");
|
|
17762
|
+
}
|
|
17763
|
+
if (config.transform.crop !== void 0) {
|
|
17764
|
+
validateCropRectangle(config.transform.crop, "config.transform.");
|
|
17765
|
+
}
|
|
17766
|
+
if (config.transform.process !== void 0 && typeof config.transform.process !== "function") {
|
|
17767
|
+
throw new TypeError("config.transform.process, when provided, must be a function.");
|
|
17768
|
+
}
|
|
17769
|
+
if (config.transform.frameRate !== void 0 && (!Number.isFinite(config.transform.frameRate) || config.transform.frameRate <= 0)) {
|
|
17770
|
+
throw new TypeError("config.transform.frameRate, when provided, must be a finite positive number.");
|
|
17771
|
+
}
|
|
17772
|
+
if (config.transform.force !== void 0 && typeof config.transform.force !== "boolean") {
|
|
17773
|
+
throw new TypeError("config.transform.force, when provided, must be a boolean.");
|
|
17774
|
+
}
|
|
17775
|
+
}
|
|
17776
|
+
if (config.onEncodedPacket !== void 0 && typeof config.onEncodedPacket !== "function") {
|
|
17777
|
+
throw new TypeError("config.onEncodedPacket, when provided, must be a function.");
|
|
17778
|
+
}
|
|
17779
|
+
if (config.onEncoderConfig !== void 0 && typeof config.onEncoderConfig !== "function") {
|
|
17780
|
+
throw new TypeError("config.onEncoderConfig, when provided, must be a function.");
|
|
17781
|
+
}
|
|
17782
|
+
if (config.onEncodedSample !== void 0 && typeof config.onEncodedSample !== "function") {
|
|
17783
|
+
throw new TypeError("config.onEncodedSample, when provided, must be a function.");
|
|
17784
|
+
}
|
|
17785
|
+
validateVideoEncodingAdditionalOptions(config.codec, config);
|
|
17786
|
+
};
|
|
17787
|
+
var validateVideoEncodingAdditionalOptions = (codec, options) => {
|
|
17788
|
+
if (!options || typeof options !== "object") {
|
|
17789
|
+
throw new TypeError("Encoding options must be an object.");
|
|
17790
|
+
}
|
|
17791
|
+
if (options.alpha !== void 0 && !["discard", "keep"].includes(options.alpha)) {
|
|
17792
|
+
throw new TypeError("options.alpha, when provided, must be 'discard' or 'keep'.");
|
|
17793
|
+
}
|
|
17794
|
+
const bitrateMode = options.bitrateMode;
|
|
17795
|
+
if (bitrateMode !== void 0 && !["constant", "variable"].includes(bitrateMode)) {
|
|
17796
|
+
throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'.");
|
|
17797
|
+
}
|
|
17798
|
+
if (options.latencyMode !== void 0 && !["quality", "realtime"].includes(options.latencyMode)) {
|
|
17799
|
+
throw new TypeError("latencyMode, when provided, must be 'quality' or 'realtime'.");
|
|
17800
|
+
}
|
|
17801
|
+
if (options.fullCodecString !== void 0 && typeof options.fullCodecString !== "string") {
|
|
17802
|
+
throw new TypeError("fullCodecString, when provided, must be a string.");
|
|
17803
|
+
}
|
|
17804
|
+
if (options.fullCodecString !== void 0 && inferCodecFromCodecString(options.fullCodecString) !== codec) {
|
|
17805
|
+
throw new TypeError(`fullCodecString, when provided, must be a string that matches the specified codec (${codec}).`);
|
|
17806
|
+
}
|
|
17807
|
+
if (options.hardwareAcceleration !== void 0 && !["no-preference", "prefer-hardware", "prefer-software"].includes(options.hardwareAcceleration)) {
|
|
17808
|
+
throw new TypeError("hardwareAcceleration, when provided, must be 'no-preference', 'prefer-hardware' or 'prefer-software'.");
|
|
17809
|
+
}
|
|
17810
|
+
if (options.scalabilityMode !== void 0 && typeof options.scalabilityMode !== "string") {
|
|
17811
|
+
throw new TypeError("scalabilityMode, when provided, must be a string.");
|
|
17812
|
+
}
|
|
17813
|
+
if (options.contentHint !== void 0 && typeof options.contentHint !== "string") {
|
|
17814
|
+
throw new TypeError("contentHint, when provided, must be a string.");
|
|
17815
|
+
}
|
|
17816
|
+
};
|
|
17817
|
+
var buildVideoEncoderConfigs = (options) => {
|
|
17818
|
+
const fallbackBitrateMode = options.bitrateMode;
|
|
17819
|
+
const rateControl = options.quality._toVideoRateControl(options.codec, options.width, options.height, fallbackBitrateMode);
|
|
17820
|
+
const buildConfig = (bitrate, bitrateMode, bitrateEstimate) => ({
|
|
17821
|
+
codec: options.fullCodecString ?? buildVideoCodecString(options.codec, options.width, options.height, bitrateEstimate, options.alpha === "keep"),
|
|
17822
|
+
width: options.width,
|
|
17823
|
+
height: options.height,
|
|
17824
|
+
displayWidth: options.squarePixelWidth,
|
|
17825
|
+
displayHeight: options.squarePixelHeight,
|
|
17826
|
+
bitrate,
|
|
17827
|
+
bitrateMode,
|
|
17828
|
+
alpha: options.alpha ?? "discard",
|
|
17829
|
+
framerate: options.framerate,
|
|
17830
|
+
latencyMode: options.latencyMode,
|
|
17831
|
+
hardwareAcceleration: options.hardwareAcceleration,
|
|
17832
|
+
scalabilityMode: options.scalabilityMode,
|
|
17833
|
+
contentHint: options.contentHint,
|
|
17834
|
+
...getVideoEncoderConfigExtension(options.codec)
|
|
17835
|
+
});
|
|
17836
|
+
const candidates = [];
|
|
17837
|
+
if (rateControl.quantizer !== null) {
|
|
17838
|
+
candidates.push({
|
|
17839
|
+
config: buildConfig(void 0, "quantizer", rateControl.bitrate),
|
|
17840
|
+
quantizer: rateControl.quantizer
|
|
17841
|
+
});
|
|
17842
|
+
}
|
|
17843
|
+
if (rateControl.bitrateMode !== "quantizer") {
|
|
17844
|
+
candidates.push({
|
|
17845
|
+
config: buildConfig(rateControl.bitrate, rateControl.bitrateMode, rateControl.bitrate),
|
|
17846
|
+
quantizer: null
|
|
17847
|
+
});
|
|
17848
|
+
}
|
|
17849
|
+
assert(candidates.length > 0);
|
|
17850
|
+
return candidates;
|
|
17851
|
+
};
|
|
17524
17852
|
var validateAudioEncodingConfig = (config) => {
|
|
17525
17853
|
if (!config || typeof config !== "object") {
|
|
17526
17854
|
throw new TypeError("Encoding config must be an object.");
|
|
@@ -17788,6 +18116,129 @@ var computeVideoBitrate = (codec, width, height, factor) => {
|
|
|
17788
18116
|
const finalBitrate = codecAdjustedBitrate * factor;
|
|
17789
18117
|
return Math.ceil(finalBitrate / 1e3) * 1e3;
|
|
17790
18118
|
};
|
|
18119
|
+
var buildQuantizerEncodeOptions = (codec, quantizer) => {
|
|
18120
|
+
if (codec === "avc") {
|
|
18121
|
+
return { avc: { quantizer } };
|
|
18122
|
+
} else if (codec === "hevc") {
|
|
18123
|
+
return { hevc: { quantizer } };
|
|
18124
|
+
} else if (codec === "vp9") {
|
|
18125
|
+
return { vp9: { quantizer } };
|
|
18126
|
+
} else if (codec === "av1") {
|
|
18127
|
+
return { av1: { quantizer } };
|
|
18128
|
+
}
|
|
18129
|
+
assert(false);
|
|
18130
|
+
};
|
|
18131
|
+
var QUALITY_HIGH = /* @__PURE__ */ new Quality("high");
|
|
18132
|
+
var canEncodeVideo = async (codec, options = {}) => {
|
|
18133
|
+
const {
|
|
18134
|
+
width = 1280,
|
|
18135
|
+
height = 720,
|
|
18136
|
+
quality,
|
|
18137
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
18138
|
+
bitrate,
|
|
18139
|
+
frameRate,
|
|
18140
|
+
...restOptions
|
|
18141
|
+
} = options;
|
|
18142
|
+
if (!VIDEO_CODECS.includes(codec)) {
|
|
18143
|
+
return false;
|
|
18144
|
+
}
|
|
18145
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
18146
|
+
throw new TypeError("width must be a positive integer.");
|
|
18147
|
+
}
|
|
18148
|
+
if (!Number.isInteger(height) || height <= 0) {
|
|
18149
|
+
throw new TypeError("height must be a positive integer.");
|
|
18150
|
+
}
|
|
18151
|
+
if (quality !== void 0 && !(quality instanceof Quality)) {
|
|
18152
|
+
throw new TypeError("quality, when provided, must be a Quality.");
|
|
18153
|
+
}
|
|
18154
|
+
if (quality !== void 0 && bitrate !== void 0) {
|
|
18155
|
+
throw new TypeError("quality and bitrate cannot both be provided.");
|
|
18156
|
+
}
|
|
18157
|
+
if (bitrate !== void 0 && !(bitrate instanceof Quality) && (!Number.isInteger(bitrate) || bitrate <= 0)) {
|
|
18158
|
+
throw new TypeError("bitrate must be a positive integer or a quality.");
|
|
18159
|
+
}
|
|
18160
|
+
if (frameRate !== void 0 && (!Number.isFinite(frameRate) || frameRate <= 0)) {
|
|
18161
|
+
throw new TypeError("frameRate, when provided, must be a finite positive number.");
|
|
18162
|
+
}
|
|
18163
|
+
validateVideoEncodingAdditionalOptions(codec, restOptions);
|
|
18164
|
+
const resolvedQuality = resolveQuality(quality, bitrate) ?? new Quality("medium");
|
|
18165
|
+
let candidates;
|
|
18166
|
+
try {
|
|
18167
|
+
candidates = buildVideoEncoderConfigs({
|
|
18168
|
+
codec,
|
|
18169
|
+
width,
|
|
18170
|
+
height,
|
|
18171
|
+
quality: resolvedQuality,
|
|
18172
|
+
framerate: frameRate,
|
|
18173
|
+
...restOptions,
|
|
18174
|
+
alpha: "discard"
|
|
18175
|
+
// Since we handle alpha ourselves
|
|
18176
|
+
});
|
|
18177
|
+
} catch {
|
|
18178
|
+
return false;
|
|
18179
|
+
}
|
|
18180
|
+
const key = JSON.stringify(candidates);
|
|
18181
|
+
const memoized = canEncodeVideoMemo.get(key);
|
|
18182
|
+
if (memoized) {
|
|
18183
|
+
return memoized;
|
|
18184
|
+
}
|
|
18185
|
+
const promise = (async () => {
|
|
18186
|
+
for (const { config } of candidates) {
|
|
18187
|
+
if (customVideoEncoders.some((x) => x.supports(codec, config))) {
|
|
18188
|
+
return true;
|
|
18189
|
+
}
|
|
18190
|
+
}
|
|
18191
|
+
if (typeof VideoEncoder === "undefined") {
|
|
18192
|
+
return false;
|
|
18193
|
+
}
|
|
18194
|
+
const hasOddDimension = width % 2 === 1 || height % 2 === 1;
|
|
18195
|
+
if (hasOddDimension && (codec === "avc" || codec === "hevc")) {
|
|
18196
|
+
return false;
|
|
18197
|
+
}
|
|
18198
|
+
for (const { config, quantizer } of candidates) {
|
|
18199
|
+
try {
|
|
18200
|
+
const support = await VideoEncoder.isConfigSupported(config);
|
|
18201
|
+
if (!support.supported) {
|
|
18202
|
+
continue;
|
|
18203
|
+
}
|
|
18204
|
+
} catch {
|
|
18205
|
+
continue;
|
|
18206
|
+
}
|
|
18207
|
+
if (!isFirefox()) {
|
|
18208
|
+
return true;
|
|
18209
|
+
}
|
|
18210
|
+
const success = await new Promise(async (resolve) => {
|
|
18211
|
+
try {
|
|
18212
|
+
const encoder = new VideoEncoder({
|
|
18213
|
+
output: () => {
|
|
18214
|
+
},
|
|
18215
|
+
error: () => resolve(false)
|
|
18216
|
+
});
|
|
18217
|
+
encoder.configure(config);
|
|
18218
|
+
const frameData = new Uint8Array(width * height * 4);
|
|
18219
|
+
const frame = new VideoFrame(frameData, {
|
|
18220
|
+
format: "RGBA",
|
|
18221
|
+
codedWidth: width,
|
|
18222
|
+
codedHeight: height,
|
|
18223
|
+
timestamp: 0
|
|
18224
|
+
});
|
|
18225
|
+
encoder.encode(frame, quantizer !== null ? buildQuantizerEncodeOptions(codec, quantizer) : void 0);
|
|
18226
|
+
frame.close();
|
|
18227
|
+
await encoder.flush();
|
|
18228
|
+
resolve(true);
|
|
18229
|
+
} catch {
|
|
18230
|
+
resolve(false);
|
|
18231
|
+
}
|
|
18232
|
+
});
|
|
18233
|
+
if (success) {
|
|
18234
|
+
return true;
|
|
18235
|
+
}
|
|
18236
|
+
}
|
|
18237
|
+
return false;
|
|
18238
|
+
})();
|
|
18239
|
+
canEncodeVideoMemo.set(key, promise);
|
|
18240
|
+
return promise;
|
|
18241
|
+
};
|
|
17791
18242
|
var canEncodeAudio = async (codec, options = {}) => {
|
|
17792
18243
|
const {
|
|
17793
18244
|
numberOfChannels = 2,
|
|
@@ -17858,10 +18309,19 @@ var resolveQuality = (quality, bitrate) => {
|
|
|
17858
18309
|
}
|
|
17859
18310
|
return bitrate instanceof Quality ? bitrate : new Quality({ bitrate });
|
|
17860
18311
|
};
|
|
18312
|
+
var getFirstEncodableVideoCodec = async (checkedCodecs, options) => {
|
|
18313
|
+
for (const codec of checkedCodecs) {
|
|
18314
|
+
if (await canEncodeVideo(codec, options)) {
|
|
18315
|
+
return codec;
|
|
18316
|
+
}
|
|
18317
|
+
}
|
|
18318
|
+
return null;
|
|
18319
|
+
};
|
|
17861
18320
|
|
|
17862
18321
|
// ../../node_modules/mediabunny/dist/modules/src/custom-coder.js
|
|
17863
18322
|
var customVideoDecoders = [];
|
|
17864
18323
|
var customAudioDecoders = [];
|
|
18324
|
+
var customVideoEncoders = [];
|
|
17865
18325
|
var customAudioEncoders = [];
|
|
17866
18326
|
|
|
17867
18327
|
// ../../node_modules/mediabunny/dist/modules/src/pcm.js
|
|
@@ -22130,6 +22590,64 @@ var AudioResampler = class {
|
|
|
22130
22590
|
};
|
|
22131
22591
|
|
|
22132
22592
|
// ../../node_modules/mediabunny/dist/modules/src/media-source.js
|
|
22593
|
+
var __addDisposableResource2 = function(env, value, async) {
|
|
22594
|
+
if (value !== null && value !== void 0) {
|
|
22595
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
22596
|
+
var dispose, inner;
|
|
22597
|
+
if (async) {
|
|
22598
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
22599
|
+
dispose = value[Symbol.asyncDispose];
|
|
22600
|
+
}
|
|
22601
|
+
if (dispose === void 0) {
|
|
22602
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
22603
|
+
dispose = value[Symbol.dispose];
|
|
22604
|
+
if (async) inner = dispose;
|
|
22605
|
+
}
|
|
22606
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
22607
|
+
if (inner) dispose = function() {
|
|
22608
|
+
try {
|
|
22609
|
+
inner.call(this);
|
|
22610
|
+
} catch (e) {
|
|
22611
|
+
return Promise.reject(e);
|
|
22612
|
+
}
|
|
22613
|
+
};
|
|
22614
|
+
env.stack.push({ value, dispose, async });
|
|
22615
|
+
} else if (async) {
|
|
22616
|
+
env.stack.push({ async: true });
|
|
22617
|
+
}
|
|
22618
|
+
return value;
|
|
22619
|
+
};
|
|
22620
|
+
var __disposeResources2 = /* @__PURE__ */ (function(SuppressedError2) {
|
|
22621
|
+
return function(env) {
|
|
22622
|
+
function fail(e) {
|
|
22623
|
+
env.error = env.hasError ? new SuppressedError2(e, env.error, "An error was suppressed during disposal.") : e;
|
|
22624
|
+
env.hasError = true;
|
|
22625
|
+
}
|
|
22626
|
+
var r, s = 0;
|
|
22627
|
+
function next() {
|
|
22628
|
+
while (r = env.stack.pop()) {
|
|
22629
|
+
try {
|
|
22630
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
22631
|
+
if (r.dispose) {
|
|
22632
|
+
var result = r.dispose.call(r.value);
|
|
22633
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
22634
|
+
fail(e);
|
|
22635
|
+
return next();
|
|
22636
|
+
});
|
|
22637
|
+
} else s |= 1;
|
|
22638
|
+
} catch (e) {
|
|
22639
|
+
fail(e);
|
|
22640
|
+
}
|
|
22641
|
+
}
|
|
22642
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
22643
|
+
if (env.hasError) throw env.error;
|
|
22644
|
+
}
|
|
22645
|
+
return next();
|
|
22646
|
+
};
|
|
22647
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
22648
|
+
var e = new Error(message);
|
|
22649
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
22650
|
+
});
|
|
22133
22651
|
var MediaSource = class {
|
|
22134
22652
|
constructor() {
|
|
22135
22653
|
this._connectedTrack = null;
|
|
@@ -22205,6 +22723,776 @@ var VideoSource = class extends MediaSource {
|
|
|
22205
22723
|
this._codec = codec;
|
|
22206
22724
|
}
|
|
22207
22725
|
};
|
|
22726
|
+
var maybeEnsureIsKeyPacket = (track, packet) => {
|
|
22727
|
+
if (track.metadata.hasOnlyKeyPackets && packet.type !== "key") {
|
|
22728
|
+
throw new Error("Cannot add non-key packets to a hasOnlyKeyPackets video track.");
|
|
22729
|
+
}
|
|
22730
|
+
};
|
|
22731
|
+
var VideoEncoderWrapper = class {
|
|
22732
|
+
setError(error) {
|
|
22733
|
+
if (!this.errorSet) {
|
|
22734
|
+
this.error = error;
|
|
22735
|
+
this.errorSet = true;
|
|
22736
|
+
}
|
|
22737
|
+
}
|
|
22738
|
+
constructor(source, encodingConfig) {
|
|
22739
|
+
this.source = source;
|
|
22740
|
+
this.encodingConfig = encodingConfig;
|
|
22741
|
+
this.ensureEncoderPromise = null;
|
|
22742
|
+
this.encoderInitialized = false;
|
|
22743
|
+
this.encoder = null;
|
|
22744
|
+
this.encoderConfig = null;
|
|
22745
|
+
this.encodersReclaimed = false;
|
|
22746
|
+
this.muxer = null;
|
|
22747
|
+
this.lastMultipleOfKeyFrameInterval = -1;
|
|
22748
|
+
this.emittedEncoderPackets = 0;
|
|
22749
|
+
this.codedWidth = null;
|
|
22750
|
+
this.codedHeight = null;
|
|
22751
|
+
this.outputWidth = null;
|
|
22752
|
+
this.outputHeight = null;
|
|
22753
|
+
this.frameRateLastSample = null;
|
|
22754
|
+
this.frameRateLastTimestamp = null;
|
|
22755
|
+
this.frameRateLastEndTimestamp = null;
|
|
22756
|
+
this.preciseTimings = [];
|
|
22757
|
+
this.customEncoder = null;
|
|
22758
|
+
this.customEncoderCallSerializer = new CallSerializer();
|
|
22759
|
+
this.customEncoderQueueSize = 0;
|
|
22760
|
+
this.defaultEncodeOptions = {};
|
|
22761
|
+
this.alphaEncoder = null;
|
|
22762
|
+
this.splitter = null;
|
|
22763
|
+
this.splitterCreationFailed = false;
|
|
22764
|
+
this.alphaFrameQueue = [];
|
|
22765
|
+
this.error = null;
|
|
22766
|
+
this.errorSet = false;
|
|
22767
|
+
this.lastMuxerPromise = Promise.resolve();
|
|
22768
|
+
this.closed = false;
|
|
22769
|
+
}
|
|
22770
|
+
async add(videoSample, shouldClose, encodeOptions) {
|
|
22771
|
+
const originalSample = videoSample;
|
|
22772
|
+
try {
|
|
22773
|
+
this.checkForEncoderError();
|
|
22774
|
+
this.source._ensureValidAdd();
|
|
22775
|
+
const config = this.encodingConfig;
|
|
22776
|
+
const sizeChangeBehavior = config.sizeChangeBehavior ?? "deny";
|
|
22777
|
+
let isSizeChange = false;
|
|
22778
|
+
if (this.codedWidth !== null && this.codedHeight !== null) {
|
|
22779
|
+
if (videoSample.codedWidth !== this.codedWidth || videoSample.codedHeight !== this.codedHeight) {
|
|
22780
|
+
isSizeChange = true;
|
|
22781
|
+
if (sizeChangeBehavior === "deny") {
|
|
22782
|
+
throw new Error(`Video sample size must remain constant. Expected ${this.codedWidth}x${this.codedHeight}, got ${videoSample.codedWidth}x${videoSample.codedHeight}. To allow the sample size to change over time, set \`sizeChangeBehavior\` to a value other than 'deny' in the encoding options.`);
|
|
22783
|
+
}
|
|
22784
|
+
}
|
|
22785
|
+
} else {
|
|
22786
|
+
this.codedWidth = videoSample.codedWidth;
|
|
22787
|
+
this.codedHeight = videoSample.codedHeight;
|
|
22788
|
+
}
|
|
22789
|
+
const hasTransformConfig = config.transform?.width !== void 0 || config.transform?.height !== void 0 || config.transform?.rotate !== void 0 || config.transform?.flip !== void 0 || config.transform?.crop !== void 0 || config.transform?.force === true;
|
|
22790
|
+
const needsTransform = hasTransformConfig || isSizeChange && sizeChangeBehavior !== "passThrough";
|
|
22791
|
+
if (needsTransform) {
|
|
22792
|
+
let targetWidth = config.transform?.width;
|
|
22793
|
+
let targetHeight = config.transform?.height;
|
|
22794
|
+
let appliedFit = config.transform?.fit ?? "fill";
|
|
22795
|
+
if (isSizeChange && sizeChangeBehavior !== "passThrough") {
|
|
22796
|
+
assert(this.outputWidth);
|
|
22797
|
+
assert(this.outputHeight);
|
|
22798
|
+
assert(sizeChangeBehavior !== "deny");
|
|
22799
|
+
targetWidth = this.outputWidth;
|
|
22800
|
+
targetHeight = this.outputHeight;
|
|
22801
|
+
appliedFit = sizeChangeBehavior;
|
|
22802
|
+
}
|
|
22803
|
+
const transformed = await videoSample.transform({
|
|
22804
|
+
width: targetWidth,
|
|
22805
|
+
height: targetHeight,
|
|
22806
|
+
roundDimensionsTo: 2,
|
|
22807
|
+
crop: config.transform?.crop,
|
|
22808
|
+
rotate: config.transform?.rotate,
|
|
22809
|
+
flip: config.transform?.flip,
|
|
22810
|
+
fit: appliedFit,
|
|
22811
|
+
alpha: config.alpha
|
|
22812
|
+
});
|
|
22813
|
+
if (this.outputWidth === null || this.outputHeight === null) {
|
|
22814
|
+
this.outputWidth = transformed.displayWidth;
|
|
22815
|
+
this.outputHeight = transformed.displayHeight;
|
|
22816
|
+
}
|
|
22817
|
+
if (shouldClose) {
|
|
22818
|
+
videoSample.close();
|
|
22819
|
+
}
|
|
22820
|
+
videoSample = transformed;
|
|
22821
|
+
shouldClose = true;
|
|
22822
|
+
} else {
|
|
22823
|
+
if (this.outputWidth === null || this.outputHeight === null) {
|
|
22824
|
+
this.outputWidth = videoSample.codedWidth;
|
|
22825
|
+
this.outputHeight = videoSample.codedHeight;
|
|
22826
|
+
}
|
|
22827
|
+
}
|
|
22828
|
+
const frameRate = config.transform?.frameRate;
|
|
22829
|
+
if (frameRate !== void 0) {
|
|
22830
|
+
const originalEndTimestamp = videoSample.timestamp + videoSample.duration;
|
|
22831
|
+
const alignedTimestamp = floorToDivisor(videoSample.timestamp, frameRate);
|
|
22832
|
+
if (this.frameRateLastSample !== null) {
|
|
22833
|
+
if (alignedTimestamp <= this.frameRateLastTimestamp) {
|
|
22834
|
+
this.frameRateLastSample.close();
|
|
22835
|
+
this.frameRateLastSample = videoSample.clone();
|
|
22836
|
+
this.frameRateLastEndTimestamp = originalEndTimestamp;
|
|
22837
|
+
return;
|
|
22838
|
+
} else {
|
|
22839
|
+
await this.padFrameRate(alignedTimestamp, encodeOptions);
|
|
22840
|
+
}
|
|
22841
|
+
}
|
|
22842
|
+
if (videoSample === originalSample) {
|
|
22843
|
+
videoSample = videoSample.clone();
|
|
22844
|
+
shouldClose = true;
|
|
22845
|
+
}
|
|
22846
|
+
videoSample.setTimestamp(alignedTimestamp);
|
|
22847
|
+
videoSample.setDuration(1 / frameRate);
|
|
22848
|
+
this.frameRateLastSample?.close();
|
|
22849
|
+
this.frameRateLastSample = videoSample.clone();
|
|
22850
|
+
this.frameRateLastTimestamp = alignedTimestamp;
|
|
22851
|
+
this.frameRateLastEndTimestamp = originalEndTimestamp;
|
|
22852
|
+
}
|
|
22853
|
+
await this.processAndEncode(videoSample, encodeOptions);
|
|
22854
|
+
} finally {
|
|
22855
|
+
if (shouldClose) {
|
|
22856
|
+
videoSample.close();
|
|
22857
|
+
}
|
|
22858
|
+
}
|
|
22859
|
+
}
|
|
22860
|
+
/**
|
|
22861
|
+
* Runs the process function (if any) and encodes the resulting samples.
|
|
22862
|
+
*/
|
|
22863
|
+
async processAndEncode(videoSample, encodeOptions) {
|
|
22864
|
+
const config = this.encodingConfig;
|
|
22865
|
+
let samplesToEncode;
|
|
22866
|
+
if (config.transform?.process) {
|
|
22867
|
+
let processed = config.transform.process(videoSample);
|
|
22868
|
+
if (isThenable(processed)) {
|
|
22869
|
+
processed = await processed;
|
|
22870
|
+
}
|
|
22871
|
+
if (processed === null) {
|
|
22872
|
+
return;
|
|
22873
|
+
}
|
|
22874
|
+
if (!Array.isArray(processed)) {
|
|
22875
|
+
processed = [processed];
|
|
22876
|
+
}
|
|
22877
|
+
const mappedSamples = [];
|
|
22878
|
+
try {
|
|
22879
|
+
for (const x of processed) {
|
|
22880
|
+
if (x instanceof VideoSample) {
|
|
22881
|
+
mappedSamples.push(x);
|
|
22882
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
22883
|
+
mappedSamples.push(new VideoSample(x));
|
|
22884
|
+
} else {
|
|
22885
|
+
mappedSamples.push(new VideoSample(x, {
|
|
22886
|
+
timestamp: videoSample.timestamp,
|
|
22887
|
+
duration: videoSample.duration
|
|
22888
|
+
}));
|
|
22889
|
+
}
|
|
22890
|
+
}
|
|
22891
|
+
} catch (error) {
|
|
22892
|
+
for (const sample of mappedSamples) {
|
|
22893
|
+
if (sample !== videoSample) {
|
|
22894
|
+
sample.close();
|
|
22895
|
+
}
|
|
22896
|
+
}
|
|
22897
|
+
for (const x of processed) {
|
|
22898
|
+
if (x instanceof VideoSample && x !== videoSample) {
|
|
22899
|
+
x.close();
|
|
22900
|
+
} else if (typeof VideoFrame !== "undefined" && x instanceof VideoFrame) {
|
|
22901
|
+
x.close();
|
|
22902
|
+
}
|
|
22903
|
+
}
|
|
22904
|
+
throw error;
|
|
22905
|
+
}
|
|
22906
|
+
samplesToEncode = mappedSamples;
|
|
22907
|
+
} else {
|
|
22908
|
+
samplesToEncode = [videoSample];
|
|
22909
|
+
}
|
|
22910
|
+
try {
|
|
22911
|
+
for (const sampleToEncode of samplesToEncode) {
|
|
22912
|
+
if (!this.encoderInitialized) {
|
|
22913
|
+
if (!this.ensureEncoderPromise) {
|
|
22914
|
+
this.ensureEncoder(sampleToEncode);
|
|
22915
|
+
}
|
|
22916
|
+
if (!this.encoderInitialized) {
|
|
22917
|
+
await this.ensureEncoderPromise;
|
|
22918
|
+
}
|
|
22919
|
+
}
|
|
22920
|
+
assert(this.encoderInitialized);
|
|
22921
|
+
if (this.closed) {
|
|
22922
|
+
break;
|
|
22923
|
+
}
|
|
22924
|
+
if (this.encodersReclaimed) {
|
|
22925
|
+
this.recreateWebCodecsEncoders();
|
|
22926
|
+
}
|
|
22927
|
+
const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
|
|
22928
|
+
const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
|
|
22929
|
+
const mergedEncodeOptions = {
|
|
22930
|
+
...this.defaultEncodeOptions,
|
|
22931
|
+
...sampleToEncode.encodeOptions,
|
|
22932
|
+
...encodeOptions
|
|
22933
|
+
};
|
|
22934
|
+
const finalEncodeOptions = {
|
|
22935
|
+
...mergedEncodeOptions,
|
|
22936
|
+
keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
|
|
22937
|
+
};
|
|
22938
|
+
this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
|
|
22939
|
+
this.encodingConfig.onEncodedSample?.(sampleToEncode);
|
|
22940
|
+
if (this.customEncoder) {
|
|
22941
|
+
this.customEncoderQueueSize++;
|
|
22942
|
+
const clonedSample = sampleToEncode.clone();
|
|
22943
|
+
const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).catch((error) => this.setError(error)).finally(() => {
|
|
22944
|
+
this.customEncoderQueueSize--;
|
|
22945
|
+
clonedSample.close();
|
|
22946
|
+
});
|
|
22947
|
+
if (this.customEncoderQueueSize >= 4) {
|
|
22948
|
+
await promise;
|
|
22949
|
+
}
|
|
22950
|
+
} else {
|
|
22951
|
+
assert(this.encoder);
|
|
22952
|
+
const videoFrame = sampleToEncode.toVideoFrame();
|
|
22953
|
+
const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, videoFrame.timestamp, (x) => x.microsecondTimestamp);
|
|
22954
|
+
const existingEntry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
22955
|
+
if (existingEntry && existingEntry.microsecondTimestamp === videoFrame.timestamp) {
|
|
22956
|
+
if (existingEntry.timestamp !== sampleToEncode.timestamp) {
|
|
22957
|
+
existingEntry.timestampIsValid = false;
|
|
22958
|
+
}
|
|
22959
|
+
if (existingEntry.duration !== sampleToEncode.duration) {
|
|
22960
|
+
existingEntry.durationIsValid = false;
|
|
22961
|
+
}
|
|
22962
|
+
} else {
|
|
22963
|
+
this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
|
|
22964
|
+
microsecondTimestamp: videoFrame.timestamp,
|
|
22965
|
+
timestamp: sampleToEncode.timestamp,
|
|
22966
|
+
duration: sampleToEncode.duration,
|
|
22967
|
+
timestampIsValid: true,
|
|
22968
|
+
durationIsValid: true
|
|
22969
|
+
});
|
|
22970
|
+
if (this.preciseTimings.length > 128) {
|
|
22971
|
+
this.preciseTimings.shift();
|
|
22972
|
+
}
|
|
22973
|
+
}
|
|
22974
|
+
if (!this.alphaEncoder) {
|
|
22975
|
+
try {
|
|
22976
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
22977
|
+
} finally {
|
|
22978
|
+
videoFrame.close();
|
|
22979
|
+
}
|
|
22980
|
+
} else {
|
|
22981
|
+
const frameDefinitelyHasNoAlpha = !!videoFrame.format && !videoFrame.format.includes("A");
|
|
22982
|
+
if (frameDefinitelyHasNoAlpha || this.splitterCreationFailed) {
|
|
22983
|
+
this.alphaFrameQueue.push(null);
|
|
22984
|
+
try {
|
|
22985
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
22986
|
+
} finally {
|
|
22987
|
+
videoFrame.close();
|
|
22988
|
+
}
|
|
22989
|
+
} else {
|
|
22990
|
+
if (!this.splitter) {
|
|
22991
|
+
this.splitter = new ColorAlphaSplitter();
|
|
22992
|
+
}
|
|
22993
|
+
const { colorFrame, alphaFrame } = await this.splitter.split(videoFrame);
|
|
22994
|
+
this.alphaFrameQueue.push(alphaFrame);
|
|
22995
|
+
try {
|
|
22996
|
+
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
22997
|
+
} finally {
|
|
22998
|
+
colorFrame.close();
|
|
22999
|
+
}
|
|
23000
|
+
}
|
|
23001
|
+
}
|
|
23002
|
+
if (this.encoder.encodeQueueSize >= 4) {
|
|
23003
|
+
await new Promise((resolve) => this.encoder.addEventListener("dequeue", resolve, { once: true }));
|
|
23004
|
+
}
|
|
23005
|
+
}
|
|
23006
|
+
await this.lastMuxerPromise;
|
|
23007
|
+
}
|
|
23008
|
+
} finally {
|
|
23009
|
+
for (const sample of samplesToEncode) {
|
|
23010
|
+
if (sample !== videoSample) {
|
|
23011
|
+
sample.close();
|
|
23012
|
+
}
|
|
23013
|
+
}
|
|
23014
|
+
}
|
|
23015
|
+
}
|
|
23016
|
+
/** Repeats the last frame rate sample to fill the gap up to the given timestamp. */
|
|
23017
|
+
async padFrameRate(until, encodeOptions) {
|
|
23018
|
+
const frameRate = this.encodingConfig.transform.frameRate;
|
|
23019
|
+
assert(this.frameRateLastSample);
|
|
23020
|
+
const frameDifference = Math.round((until - this.frameRateLastTimestamp) * frameRate);
|
|
23021
|
+
for (let i = 1; i < frameDifference; i++) {
|
|
23022
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
23023
|
+
try {
|
|
23024
|
+
const sample = __addDisposableResource2(env_1, this.frameRateLastSample.clone(), false);
|
|
23025
|
+
sample.setTimestamp(this.frameRateLastTimestamp + i / frameRate);
|
|
23026
|
+
sample.setDuration(1 / frameRate);
|
|
23027
|
+
await this.processAndEncode(sample, encodeOptions);
|
|
23028
|
+
} catch (e_1) {
|
|
23029
|
+
env_1.error = e_1;
|
|
23030
|
+
env_1.hasError = true;
|
|
23031
|
+
} finally {
|
|
23032
|
+
__disposeResources2(env_1);
|
|
23033
|
+
}
|
|
23034
|
+
}
|
|
23035
|
+
}
|
|
23036
|
+
ensureEncoder(videoSample) {
|
|
23037
|
+
this.ensureEncoderPromise = (async () => {
|
|
23038
|
+
const quality = resolveQuality(this.encodingConfig.quality, this.encodingConfig.bitrate);
|
|
23039
|
+
assert(quality !== void 0);
|
|
23040
|
+
const candidates = buildVideoEncoderConfigs({
|
|
23041
|
+
...this.encodingConfig,
|
|
23042
|
+
quality,
|
|
23043
|
+
width: videoSample.codedWidth,
|
|
23044
|
+
height: videoSample.codedHeight,
|
|
23045
|
+
squarePixelWidth: videoSample.squarePixelWidth,
|
|
23046
|
+
squarePixelHeight: videoSample.squarePixelHeight,
|
|
23047
|
+
framerate: this.source._connectedTrack?.metadata.frameRate
|
|
23048
|
+
});
|
|
23049
|
+
let selected = null;
|
|
23050
|
+
let MatchingCustomEncoder;
|
|
23051
|
+
for (const candidate of candidates) {
|
|
23052
|
+
const candidateConfig = candidate.config;
|
|
23053
|
+
this.encodingConfig.onEncoderConfig?.(candidateConfig);
|
|
23054
|
+
MatchingCustomEncoder = customVideoEncoders.find((x) => x.supports(this.encodingConfig.codec, candidateConfig));
|
|
23055
|
+
if (MatchingCustomEncoder) {
|
|
23056
|
+
selected = candidate;
|
|
23057
|
+
break;
|
|
23058
|
+
}
|
|
23059
|
+
if (typeof VideoEncoder === "undefined") {
|
|
23060
|
+
continue;
|
|
23061
|
+
}
|
|
23062
|
+
candidateConfig.alpha = "discard";
|
|
23063
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
23064
|
+
candidateConfig.latencyMode = "quality";
|
|
23065
|
+
}
|
|
23066
|
+
const hasOddDimension = candidateConfig.width % 2 === 1 || candidateConfig.height % 2 === 1;
|
|
23067
|
+
if (hasOddDimension && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) {
|
|
23068
|
+
throw new Error(`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.`);
|
|
23069
|
+
}
|
|
23070
|
+
try {
|
|
23071
|
+
const support = await VideoEncoder.isConfigSupported(candidateConfig);
|
|
23072
|
+
if (support.supported) {
|
|
23073
|
+
selected = candidate;
|
|
23074
|
+
break;
|
|
23075
|
+
}
|
|
23076
|
+
} catch {
|
|
23077
|
+
}
|
|
23078
|
+
}
|
|
23079
|
+
if (!selected) {
|
|
23080
|
+
if (typeof VideoEncoder === "undefined") {
|
|
23081
|
+
throw new Error(missingWebCodecsClassMessage("VideoEncoder"));
|
|
23082
|
+
}
|
|
23083
|
+
const firstConfig = candidates[0].config;
|
|
23084
|
+
const rateControls = candidates.map(({ config, quantizer }) => quantizer !== null ? `quantizer ${quantizer}` : `${config.bitrate} bps`);
|
|
23085
|
+
throw new Error(`This specific encoder configuration (${firstConfig.codec}, ${rateControls.join(" / ")}, ${firstConfig.width}x${firstConfig.height}, hardware acceleration: ${firstConfig.hardwareAcceleration ?? "no-preference"}) is not supported in this environment. Consider using another codec or changing your video parameters.`);
|
|
23086
|
+
}
|
|
23087
|
+
const encoderConfig = selected.config;
|
|
23088
|
+
if (selected.quantizer !== null) {
|
|
23089
|
+
this.defaultEncodeOptions = buildQuantizerEncodeOptions(this.encodingConfig.codec, selected.quantizer);
|
|
23090
|
+
} else {
|
|
23091
|
+
this.source._nominalBitrate = encoderConfig.bitrate ?? null;
|
|
23092
|
+
}
|
|
23093
|
+
if (MatchingCustomEncoder) {
|
|
23094
|
+
this.customEncoder = new MatchingCustomEncoder();
|
|
23095
|
+
this.customEncoder.codec = this.encodingConfig.codec;
|
|
23096
|
+
this.customEncoder.config = encoderConfig;
|
|
23097
|
+
this.customEncoder.onPacket = (packet, meta) => {
|
|
23098
|
+
if (!(packet instanceof EncodedPacket)) {
|
|
23099
|
+
throw new TypeError("The first argument passed to onPacket must be an EncodedPacket.");
|
|
23100
|
+
}
|
|
23101
|
+
if (meta !== void 0 && (!meta || typeof meta !== "object")) {
|
|
23102
|
+
throw new TypeError("The second argument passed to onPacket must be an object or undefined.");
|
|
23103
|
+
}
|
|
23104
|
+
maybeEnsureIsKeyPacket(this.source._connectedTrack, packet);
|
|
23105
|
+
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
23106
|
+
this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
23107
|
+
this.setError(error);
|
|
23108
|
+
});
|
|
23109
|
+
};
|
|
23110
|
+
this.customEncoder.onError = (error) => {
|
|
23111
|
+
this.setError(error);
|
|
23112
|
+
};
|
|
23113
|
+
await this.customEncoder.init();
|
|
23114
|
+
} else {
|
|
23115
|
+
this.encoderConfig = encoderConfig;
|
|
23116
|
+
this.createWebCodecsEncoders();
|
|
23117
|
+
}
|
|
23118
|
+
assert(this.source._connectedTrack);
|
|
23119
|
+
this.muxer = this.source._connectedTrack.output._muxer;
|
|
23120
|
+
this.encoderInitialized = true;
|
|
23121
|
+
})();
|
|
23122
|
+
}
|
|
23123
|
+
createWebCodecsEncoders() {
|
|
23124
|
+
const encoderConfig = this.encoderConfig;
|
|
23125
|
+
assert(encoderConfig);
|
|
23126
|
+
const colorChunkQueue = [];
|
|
23127
|
+
const nullAlphaChunkQueue = [];
|
|
23128
|
+
let encodedAlphaChunkCount = 0;
|
|
23129
|
+
let alphaEncoderQueue = 0;
|
|
23130
|
+
const handleError = (error, stack2) => {
|
|
23131
|
+
if (isCodecReclaimedError(error)) {
|
|
23132
|
+
this.encodersReclaimed = true;
|
|
23133
|
+
return;
|
|
23134
|
+
}
|
|
23135
|
+
error.stack = stack2;
|
|
23136
|
+
this.setError(error);
|
|
23137
|
+
};
|
|
23138
|
+
const stack = new Error("Encoding error").stack;
|
|
23139
|
+
this.encoder = new VideoEncoder({
|
|
23140
|
+
output: (chunk, meta) => {
|
|
23141
|
+
if (!this.alphaEncoder) {
|
|
23142
|
+
this.addPacket(chunk, null, meta);
|
|
23143
|
+
return;
|
|
23144
|
+
}
|
|
23145
|
+
const alphaFrame = this.alphaFrameQueue.shift();
|
|
23146
|
+
assert(alphaFrame !== void 0);
|
|
23147
|
+
if (alphaFrame) {
|
|
23148
|
+
this.alphaEncoder.encode(alphaFrame, {
|
|
23149
|
+
...this.defaultEncodeOptions,
|
|
23150
|
+
// Crucial: The alpha frame is forced to be a key frame whenever the color frame
|
|
23151
|
+
// also is. Without this, playback can glitch and even crash in some browsers.
|
|
23152
|
+
// This is the reason why the two encoders are wired in series and not in parallel.
|
|
23153
|
+
keyFrame: chunk.type === "key"
|
|
23154
|
+
});
|
|
23155
|
+
alphaEncoderQueue++;
|
|
23156
|
+
alphaFrame.close();
|
|
23157
|
+
colorChunkQueue.push({ chunk, meta });
|
|
23158
|
+
} else {
|
|
23159
|
+
if (alphaEncoderQueue === 0) {
|
|
23160
|
+
this.addPacket(chunk, null, meta);
|
|
23161
|
+
} else {
|
|
23162
|
+
nullAlphaChunkQueue.push(encodedAlphaChunkCount + alphaEncoderQueue);
|
|
23163
|
+
colorChunkQueue.push({ chunk, meta });
|
|
23164
|
+
}
|
|
23165
|
+
}
|
|
23166
|
+
},
|
|
23167
|
+
error: (error) => handleError(error, stack)
|
|
23168
|
+
});
|
|
23169
|
+
this.encoder.configure(encoderConfig);
|
|
23170
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
23171
|
+
const stack2 = new Error("Encoding error").stack;
|
|
23172
|
+
this.alphaEncoder = new VideoEncoder({
|
|
23173
|
+
// We ignore the alpha chunk's metadata
|
|
23174
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
23175
|
+
output: (chunk, meta) => {
|
|
23176
|
+
alphaEncoderQueue--;
|
|
23177
|
+
const colorChunk = colorChunkQueue.shift();
|
|
23178
|
+
assert(colorChunk !== void 0);
|
|
23179
|
+
this.addPacket(colorChunk.chunk, chunk, colorChunk.meta);
|
|
23180
|
+
encodedAlphaChunkCount++;
|
|
23181
|
+
while (nullAlphaChunkQueue.length > 0 && nullAlphaChunkQueue[0] === encodedAlphaChunkCount) {
|
|
23182
|
+
nullAlphaChunkQueue.shift();
|
|
23183
|
+
const colorChunk2 = colorChunkQueue.shift();
|
|
23184
|
+
assert(colorChunk2 !== void 0);
|
|
23185
|
+
this.addPacket(colorChunk2.chunk, null, colorChunk2.meta);
|
|
23186
|
+
}
|
|
23187
|
+
},
|
|
23188
|
+
error: (error) => handleError(error, stack2)
|
|
23189
|
+
});
|
|
23190
|
+
this.alphaEncoder.configure(encoderConfig);
|
|
23191
|
+
}
|
|
23192
|
+
}
|
|
23193
|
+
addPacket(colorChunk, alphaChunk, meta) {
|
|
23194
|
+
const sideData = {};
|
|
23195
|
+
if (alphaChunk) {
|
|
23196
|
+
const alphaData = new Uint8Array(alphaChunk.byteLength);
|
|
23197
|
+
alphaChunk.copyTo(alphaData);
|
|
23198
|
+
sideData.alpha = alphaData;
|
|
23199
|
+
}
|
|
23200
|
+
let packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData);
|
|
23201
|
+
const preciseTimingIndex = binarySearchLessOrEqual(this.preciseTimings, colorChunk.timestamp, (x) => x.microsecondTimestamp);
|
|
23202
|
+
const entry = preciseTimingIndex !== -1 ? this.preciseTimings[preciseTimingIndex] : null;
|
|
23203
|
+
let actualType = null;
|
|
23204
|
+
if (this.emittedEncoderPackets === 0 && packet.type === "delta" && meta?.decoderConfig) {
|
|
23205
|
+
actualType = determineVideoPacketType(this.encodingConfig.codec, meta.decoderConfig, packet.data);
|
|
23206
|
+
}
|
|
23207
|
+
if (entry && entry.microsecondTimestamp === colorChunk.timestamp || actualType !== null) {
|
|
23208
|
+
packet = packet.clone({
|
|
23209
|
+
timestamp: entry?.timestampIsValid ? entry.timestamp : void 0,
|
|
23210
|
+
duration: entry?.durationIsValid ? entry.duration : void 0,
|
|
23211
|
+
type: actualType ?? void 0
|
|
23212
|
+
});
|
|
23213
|
+
}
|
|
23214
|
+
maybeEnsureIsKeyPacket(this.source._connectedTrack, packet);
|
|
23215
|
+
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
23216
|
+
this.lastMuxerPromise = this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
23217
|
+
this.setError(error);
|
|
23218
|
+
});
|
|
23219
|
+
this.emittedEncoderPackets++;
|
|
23220
|
+
}
|
|
23221
|
+
// Browsers may reclaim codecs that have been inactive for a while (see
|
|
23222
|
+
// https://github.com/Vanilagy/mediabunny/issues/531), closing them and reporting a QuotaExceededError. Since a
|
|
23223
|
+
// reclaimed encoder was idle, no frames are lost, so we transparently replace the encoders with fresh ones.
|
|
23224
|
+
recreateWebCodecsEncoders() {
|
|
23225
|
+
assert(this.encoder);
|
|
23226
|
+
if (this.encoder.state !== "closed") {
|
|
23227
|
+
this.encoder.close();
|
|
23228
|
+
}
|
|
23229
|
+
if (this.alphaEncoder && this.alphaEncoder.state !== "closed") {
|
|
23230
|
+
this.alphaEncoder.close();
|
|
23231
|
+
}
|
|
23232
|
+
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
23233
|
+
this.alphaFrameQueue.length = 0;
|
|
23234
|
+
this.encodersReclaimed = false;
|
|
23235
|
+
this.lastMultipleOfKeyFrameInterval = -1;
|
|
23236
|
+
this.createWebCodecsEncoders();
|
|
23237
|
+
}
|
|
23238
|
+
async flushAndClose(forceClose) {
|
|
23239
|
+
try {
|
|
23240
|
+
if (!forceClose) {
|
|
23241
|
+
this.checkForEncoderError();
|
|
23242
|
+
if (this.frameRateLastSample) {
|
|
23243
|
+
const frameRate = this.encodingConfig.transform.frameRate;
|
|
23244
|
+
const alignedEnd = floorToDivisor(this.frameRateLastEndTimestamp, frameRate);
|
|
23245
|
+
await this.padFrameRate(alignedEnd);
|
|
23246
|
+
}
|
|
23247
|
+
}
|
|
23248
|
+
this.closed = true;
|
|
23249
|
+
if (!forceClose) {
|
|
23250
|
+
if (this.customEncoder) {
|
|
23251
|
+
void this.customEncoderCallSerializer.call(() => this.customEncoder.flush());
|
|
23252
|
+
} else if (this.encoder && !this.encodersReclaimed) {
|
|
23253
|
+
await this.encoder.flush();
|
|
23254
|
+
await this.alphaEncoder?.flush();
|
|
23255
|
+
await wait(25);
|
|
23256
|
+
}
|
|
23257
|
+
}
|
|
23258
|
+
} finally {
|
|
23259
|
+
this.closed = true;
|
|
23260
|
+
this.frameRateLastSample?.close();
|
|
23261
|
+
this.frameRateLastSample = null;
|
|
23262
|
+
if (this.customEncoder) {
|
|
23263
|
+
await this.customEncoderCallSerializer.call(() => this.customEncoder.close()).catch((error) => this.setError(error));
|
|
23264
|
+
} else if (this.encoder) {
|
|
23265
|
+
if (this.encoder.state !== "closed") {
|
|
23266
|
+
this.encoder.close();
|
|
23267
|
+
}
|
|
23268
|
+
if (this.alphaEncoder && this.alphaEncoder.state !== "closed") {
|
|
23269
|
+
this.alphaEncoder.close();
|
|
23270
|
+
}
|
|
23271
|
+
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
23272
|
+
this.alphaFrameQueue.length = 0;
|
|
23273
|
+
this.splitter?.close();
|
|
23274
|
+
}
|
|
23275
|
+
}
|
|
23276
|
+
if (!forceClose) {
|
|
23277
|
+
this.checkForEncoderError();
|
|
23278
|
+
}
|
|
23279
|
+
}
|
|
23280
|
+
getQueueSize() {
|
|
23281
|
+
if (this.customEncoder) {
|
|
23282
|
+
return this.customEncoderQueueSize;
|
|
23283
|
+
} else {
|
|
23284
|
+
return this.encoder?.encodeQueueSize ?? 0;
|
|
23285
|
+
}
|
|
23286
|
+
}
|
|
23287
|
+
checkForEncoderError() {
|
|
23288
|
+
if (this.errorSet) {
|
|
23289
|
+
throw this.error;
|
|
23290
|
+
}
|
|
23291
|
+
}
|
|
23292
|
+
};
|
|
23293
|
+
var splitterWorkerUrl = null;
|
|
23294
|
+
var ColorAlphaSplitter = class {
|
|
23295
|
+
constructor() {
|
|
23296
|
+
this.worker = null;
|
|
23297
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
23298
|
+
this.nextRequestId = 0;
|
|
23299
|
+
}
|
|
23300
|
+
split(sourceFrame) {
|
|
23301
|
+
if (!this.worker) {
|
|
23302
|
+
if (!splitterWorkerUrl) {
|
|
23303
|
+
const blob = new Blob([`(${colorAlphaSplitterWorkerCode.toString()})()`], { type: "application/javascript" });
|
|
23304
|
+
splitterWorkerUrl = URL.createObjectURL(blob);
|
|
23305
|
+
}
|
|
23306
|
+
this.worker = new Worker(splitterWorkerUrl);
|
|
23307
|
+
this.worker.addEventListener("message", (event) => {
|
|
23308
|
+
const data = event.data;
|
|
23309
|
+
const pending2 = this.pendingRequests.get(data.id);
|
|
23310
|
+
if (!pending2) {
|
|
23311
|
+
return;
|
|
23312
|
+
}
|
|
23313
|
+
this.pendingRequests.delete(data.id);
|
|
23314
|
+
if ("error" in data) {
|
|
23315
|
+
pending2.reject(new Error(data.error));
|
|
23316
|
+
} else {
|
|
23317
|
+
pending2.resolve({ colorFrame: data.colorFrame, alphaFrame: data.alphaFrame });
|
|
23318
|
+
}
|
|
23319
|
+
});
|
|
23320
|
+
this.worker.addEventListener("error", (event) => {
|
|
23321
|
+
const error = new Error(event.message || "Color/alpha splitter worker error.");
|
|
23322
|
+
for (const pending2 of this.pendingRequests.values()) {
|
|
23323
|
+
pending2.reject(error);
|
|
23324
|
+
}
|
|
23325
|
+
this.pendingRequests.clear();
|
|
23326
|
+
});
|
|
23327
|
+
}
|
|
23328
|
+
const id = this.nextRequestId++;
|
|
23329
|
+
const pending = promiseWithResolvers();
|
|
23330
|
+
this.pendingRequests.set(id, pending);
|
|
23331
|
+
this.worker.postMessage({ id, sourceFrame }, { transfer: [sourceFrame] });
|
|
23332
|
+
return pending.promise;
|
|
23333
|
+
}
|
|
23334
|
+
close() {
|
|
23335
|
+
this.worker?.terminate();
|
|
23336
|
+
this.worker = null;
|
|
23337
|
+
const error = new Error("Color/alpha splitter closed.");
|
|
23338
|
+
for (const pending of this.pendingRequests.values()) {
|
|
23339
|
+
pending.reject(error);
|
|
23340
|
+
}
|
|
23341
|
+
this.pendingRequests.clear();
|
|
23342
|
+
}
|
|
23343
|
+
};
|
|
23344
|
+
var colorAlphaSplitterWorkerCode = () => {
|
|
23345
|
+
let cpuSourceBuffer = null;
|
|
23346
|
+
let chain = Promise.resolve();
|
|
23347
|
+
self.addEventListener("message", (event) => {
|
|
23348
|
+
const { id, sourceFrame } = event.data;
|
|
23349
|
+
chain = chain.then(async () => {
|
|
23350
|
+
try {
|
|
23351
|
+
const { colorFrame, alphaFrame } = await split(sourceFrame);
|
|
23352
|
+
self.postMessage({ id, colorFrame, alphaFrame }, { transfer: [colorFrame, alphaFrame] });
|
|
23353
|
+
} catch (error) {
|
|
23354
|
+
self.postMessage({ id, error: error.message });
|
|
23355
|
+
} finally {
|
|
23356
|
+
sourceFrame.close();
|
|
23357
|
+
}
|
|
23358
|
+
});
|
|
23359
|
+
});
|
|
23360
|
+
const split = async (sourceFrame) => {
|
|
23361
|
+
const format = sourceFrame.format;
|
|
23362
|
+
if (!format) {
|
|
23363
|
+
throw new Error("CPU color/alpha splitting requires a known VideoFrame format.");
|
|
23364
|
+
}
|
|
23365
|
+
const sourceSize = sourceFrame.allocationSize();
|
|
23366
|
+
if (!cpuSourceBuffer || cpuSourceBuffer.byteLength !== sourceSize) {
|
|
23367
|
+
cpuSourceBuffer = new Uint8Array(sourceSize);
|
|
23368
|
+
}
|
|
23369
|
+
await sourceFrame.copyTo(cpuSourceBuffer);
|
|
23370
|
+
if (format === "RGBA" || format === "BGRA") {
|
|
23371
|
+
return splitInterleavedRgba(cpuSourceBuffer, format, sourceFrame);
|
|
23372
|
+
} else if (format === "I420A" || format === "I420AP10" || format === "I420AP12" || format === "I422A" || format === "I422AP10" || format === "I422AP12" || format === "I444A" || format === "I444AP10" || format === "I444AP12") {
|
|
23373
|
+
return splitPlanarYuvA(cpuSourceBuffer, format, sourceFrame);
|
|
23374
|
+
}
|
|
23375
|
+
throw new Error(`CPU color/alpha splitting does not support format '${format}'.`);
|
|
23376
|
+
};
|
|
23377
|
+
const splitInterleavedRgba = (source, format, sourceFrame) => {
|
|
23378
|
+
const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
|
|
23379
|
+
const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
|
|
23380
|
+
const pixelCount = width * height;
|
|
23381
|
+
const chromaW = Math.ceil(width / 2);
|
|
23382
|
+
const chromaH = Math.ceil(height / 2);
|
|
23383
|
+
const alphaSize = pixelCount + chromaW * chromaH * 2;
|
|
23384
|
+
const alphaBuffer = new Uint8Array(alphaSize);
|
|
23385
|
+
for (let i = 0, j = 3; i < pixelCount; i++, j += 4) {
|
|
23386
|
+
alphaBuffer[i] = source[j];
|
|
23387
|
+
}
|
|
23388
|
+
alphaBuffer.fill(128, pixelCount);
|
|
23389
|
+
const colorFrame = new VideoFrame(source, {
|
|
23390
|
+
format: format === "RGBA" ? "RGBX" : "BGRX",
|
|
23391
|
+
codedWidth: width,
|
|
23392
|
+
codedHeight: height,
|
|
23393
|
+
timestamp: sourceFrame.timestamp,
|
|
23394
|
+
duration: sourceFrame.duration ?? void 0
|
|
23395
|
+
// No transfer!
|
|
23396
|
+
});
|
|
23397
|
+
const alphaInit = {
|
|
23398
|
+
format: "I420",
|
|
23399
|
+
codedWidth: width,
|
|
23400
|
+
codedHeight: height,
|
|
23401
|
+
timestamp: sourceFrame.timestamp,
|
|
23402
|
+
duration: sourceFrame.duration ?? void 0,
|
|
23403
|
+
transfer: [alphaBuffer.buffer]
|
|
23404
|
+
};
|
|
23405
|
+
const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
|
|
23406
|
+
return { colorFrame, alphaFrame };
|
|
23407
|
+
};
|
|
23408
|
+
const splitPlanarYuvA = (source, format, sourceFrame) => {
|
|
23409
|
+
const width = sourceFrame.visibleRect?.width ?? sourceFrame.codedWidth;
|
|
23410
|
+
const height = sourceFrame.visibleRect?.height ?? sourceFrame.codedHeight;
|
|
23411
|
+
const is10 = format.includes("P10");
|
|
23412
|
+
const is12 = format.includes("P12");
|
|
23413
|
+
const bytesPerSample = is10 || is12 ? 2 : 1;
|
|
23414
|
+
let chromaW;
|
|
23415
|
+
let chromaH;
|
|
23416
|
+
if (format.startsWith("I420")) {
|
|
23417
|
+
chromaW = Math.ceil(width / 2);
|
|
23418
|
+
chromaH = Math.ceil(height / 2);
|
|
23419
|
+
} else if (format.startsWith("I422")) {
|
|
23420
|
+
chromaW = Math.ceil(width / 2);
|
|
23421
|
+
chromaH = height;
|
|
23422
|
+
} else {
|
|
23423
|
+
chromaW = width;
|
|
23424
|
+
chromaH = height;
|
|
23425
|
+
}
|
|
23426
|
+
const ySamples = width * height;
|
|
23427
|
+
const uvSamples = chromaW * chromaH;
|
|
23428
|
+
const yBytes = ySamples * bytesPerSample;
|
|
23429
|
+
const uvBytes = uvSamples * bytesPerSample;
|
|
23430
|
+
const aBytes = ySamples * bytesPerSample;
|
|
23431
|
+
const colorBytes = yBytes + uvBytes * 2;
|
|
23432
|
+
const colorFormat = format.replace("A", "");
|
|
23433
|
+
const alphaChromaW = Math.ceil(width / 2);
|
|
23434
|
+
const alphaChromaH = Math.ceil(height / 2);
|
|
23435
|
+
const alphaUvSamples = alphaChromaW * alphaChromaH;
|
|
23436
|
+
const alphaUvBytes = alphaUvSamples * bytesPerSample;
|
|
23437
|
+
const alphaSize = aBytes + 2 * alphaUvBytes;
|
|
23438
|
+
const alphaBuffer = new Uint8Array(alphaSize);
|
|
23439
|
+
const aPlaneStart = colorBytes;
|
|
23440
|
+
alphaBuffer.set(source.subarray(aPlaneStart, aPlaneStart + aBytes), 0);
|
|
23441
|
+
const uvOffset = aBytes;
|
|
23442
|
+
const neutralChroma = is10 ? 512 : is12 ? 2048 : 128;
|
|
23443
|
+
if (bytesPerSample === 1) {
|
|
23444
|
+
alphaBuffer.fill(neutralChroma, uvOffset);
|
|
23445
|
+
} else {
|
|
23446
|
+
const uvView = new Uint16Array(alphaBuffer.buffer, uvOffset, 2 * alphaUvSamples);
|
|
23447
|
+
uvView.fill(neutralChroma);
|
|
23448
|
+
}
|
|
23449
|
+
const alphaFormat = is10 ? "I420P10" : is12 ? "I420P12" : "I420";
|
|
23450
|
+
const colorFrame = new VideoFrame(source.subarray(0, colorBytes), {
|
|
23451
|
+
format: colorFormat,
|
|
23452
|
+
codedWidth: width,
|
|
23453
|
+
codedHeight: height,
|
|
23454
|
+
timestamp: sourceFrame.timestamp,
|
|
23455
|
+
duration: sourceFrame.duration ?? void 0
|
|
23456
|
+
});
|
|
23457
|
+
const alphaInit = {
|
|
23458
|
+
format: alphaFormat,
|
|
23459
|
+
codedWidth: width,
|
|
23460
|
+
codedHeight: height,
|
|
23461
|
+
timestamp: sourceFrame.timestamp,
|
|
23462
|
+
duration: sourceFrame.duration ?? void 0,
|
|
23463
|
+
transfer: [alphaBuffer.buffer]
|
|
23464
|
+
};
|
|
23465
|
+
const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
|
|
23466
|
+
return { colorFrame, alphaFrame };
|
|
23467
|
+
};
|
|
23468
|
+
};
|
|
23469
|
+
var VideoSampleSource = class extends VideoSource {
|
|
23470
|
+
/**
|
|
23471
|
+
* Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified
|
|
23472
|
+
* {@link VideoEncodingConfig}.
|
|
23473
|
+
*/
|
|
23474
|
+
constructor(encodingConfig) {
|
|
23475
|
+
validateVideoEncodingConfig(encodingConfig);
|
|
23476
|
+
super(encodingConfig.codec);
|
|
23477
|
+
this._encoder = new VideoEncoderWrapper(this, encodingConfig);
|
|
23478
|
+
}
|
|
23479
|
+
/**
|
|
23480
|
+
* Encodes a video sample (frame) and then adds it to the output.
|
|
23481
|
+
*
|
|
23482
|
+
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
|
|
23483
|
+
* to respect writer and encoder backpressure.
|
|
23484
|
+
*/
|
|
23485
|
+
add(videoSample, encodeOptions) {
|
|
23486
|
+
if (!(videoSample instanceof VideoSample)) {
|
|
23487
|
+
throw new TypeError("videoSample must be a VideoSample.");
|
|
23488
|
+
}
|
|
23489
|
+
return this._encoder.add(videoSample, false, encodeOptions);
|
|
23490
|
+
}
|
|
23491
|
+
/** @internal */
|
|
23492
|
+
_flushAndClose(forceClose) {
|
|
23493
|
+
return this._encoder.flushAndClose(forceClose);
|
|
23494
|
+
}
|
|
23495
|
+
};
|
|
22208
23496
|
var AudioSource = class extends MediaSource {
|
|
22209
23497
|
/** Internal constructor. */
|
|
22210
23498
|
constructor(codec) {
|
|
@@ -27895,11 +29183,15 @@ export {
|
|
|
27895
29183
|
ADTS,
|
|
27896
29184
|
FLAC,
|
|
27897
29185
|
AudioSample,
|
|
29186
|
+
QUALITY_HIGH,
|
|
29187
|
+
canEncodeVideo,
|
|
27898
29188
|
canEncodeAudio,
|
|
29189
|
+
getFirstEncodableVideoCodec,
|
|
27899
29190
|
VideoSampleSink,
|
|
27900
29191
|
AudioSampleSink,
|
|
27901
29192
|
Input,
|
|
27902
29193
|
BufferTarget,
|
|
29194
|
+
VideoSampleSource,
|
|
27903
29195
|
AudioSampleSource,
|
|
27904
29196
|
Mp4OutputFormat,
|
|
27905
29197
|
WebMOutputFormat,
|