@xmaxai/web-sdk 1.0.9 → 1.0.10
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/README.md +8 -1
- package/dist/RecordingProcessor-4TP5NICO.cjs.map +1 -1
- package/dist/{TrajectoryEffectRendering-C7E2KJbV.d.cts → TrajectoryEffectRendering-BKKPO7Uk.d.cts} +2 -2
- package/dist/{TrajectoryEffectRendering-C7E2KJbV.d.ts → TrajectoryEffectRendering-BKKPO7Uk.d.ts} +2 -2
- package/dist/chunk-4DE2IREA.cjs.map +1 -1
- package/dist/{chunk-ILZL4W2G.js → chunk-LOYJFYQT.js} +461 -209
- package/dist/chunk-LOYJFYQT.js.map +1 -0
- package/dist/{chunk-KROY2O4A.cjs → chunk-UF2RD42G.cjs} +580 -328
- package/dist/chunk-UF2RD42G.cjs.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +42 -19
- package/dist/index.d.ts +42 -19
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +3 -3
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ILZL4W2G.js.map +0 -1
- package/dist/chunk-KROY2O4A.cjs.map +0 -1
|
@@ -205,7 +205,7 @@ var XmaxError = class _XmaxError extends Error {
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
// src/Foundation/Runtime/RuntimeInfo.ts
|
|
208
|
-
var XMAX_SDK_VERSION = "1.0.
|
|
208
|
+
var XMAX_SDK_VERSION = "1.0.10";
|
|
209
209
|
var _RuntimeInfo = class _RuntimeInfo {
|
|
210
210
|
/**
|
|
211
211
|
* 序列化为接口传输使用的 snake_case 字段。
|
|
@@ -501,7 +501,7 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
|
|
|
501
501
|
* @param init.width 视频宽度,单位为像素。
|
|
502
502
|
* @param init.height 视频高度,单位为像素。
|
|
503
503
|
* @param init.fps 视频帧率,必须大于 0。
|
|
504
|
-
* @param init.minimumBitrate 最低上传码率,单位为 kbps
|
|
504
|
+
* @param init.minimumBitrate 最低上传码率,单位为 kbps;缺省为 100,仅支持该参数的 RTC 链路生效。
|
|
505
505
|
* @param init.maximumBitrate 最高上传码率,单位为 kbps;缺省按最终上传尺寸和帧率计算。
|
|
506
506
|
* @param init.encoderPreference 上传编码策略偏好,默认值为 maintainFramerate。
|
|
507
507
|
*/
|
|
@@ -525,7 +525,7 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
|
|
|
525
525
|
* 上传编码配置
|
|
526
526
|
*/
|
|
527
527
|
/**
|
|
528
|
-
* 最低上传码率,单位为 kbps
|
|
528
|
+
* 最低上传码率,单位为 kbps;缺省为 100,0 表示不设最低码率;仅支持该参数的 RTC 链路生效。
|
|
529
529
|
*/
|
|
530
530
|
__publicField(this, "minimumBitrate");
|
|
531
531
|
/**
|
|
@@ -598,10 +598,6 @@ var RtcProvider = /* @__PURE__ */ ((RtcProvider2) => {
|
|
|
598
598
|
|
|
599
599
|
// src/Service/Realtime/RealtimeModel.ts
|
|
600
600
|
var RealtimeModel = /* @__PURE__ */ ((RealtimeModel2) => {
|
|
601
|
-
RealtimeModel2["x2_0"] = "x2.0";
|
|
602
|
-
RealtimeModel2["x2_0_trtc"] = "x2.0-trtc";
|
|
603
|
-
RealtimeModel2["x2_1_preview"] = "x2.1-preview";
|
|
604
|
-
RealtimeModel2["x2_1_preview_1014"] = "x2.1-preview-1014";
|
|
605
601
|
RealtimeModel2["x2_1_1_preview_cn"] = "x2.1.1-preview-cn";
|
|
606
602
|
RealtimeModel2["x2_1_1_preview_us"] = "x2.1.1-preview-us";
|
|
607
603
|
return RealtimeModel2;
|
|
@@ -613,36 +609,17 @@ function isKnownModel(model) {
|
|
|
613
609
|
return Object.values(RealtimeModel).includes(model);
|
|
614
610
|
}
|
|
615
611
|
function modelDefaults(model) {
|
|
616
|
-
return isKnownModel(model) ? model : "x2.1-preview" /*
|
|
612
|
+
return isKnownModel(model) ? model : "x2.1.1-preview-cn" /* x2_1_1_preview_cn */;
|
|
617
613
|
}
|
|
618
614
|
var MODEL_DISPLAY_NAMES = {
|
|
619
|
-
["x2.0" /* x2_0 */]: "X2.0",
|
|
620
|
-
["x2.0-trtc" /* x2_0_trtc */]: "X2.1-preview-trtc",
|
|
621
|
-
["x2.1-preview" /* x2_1_preview */]: "X2.1-preview-agora",
|
|
622
|
-
["x2.1-preview-1014" /* x2_1_preview_1014 */]: "X2.1-preview-1014",
|
|
623
615
|
["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "X2.1.1-preview-cn",
|
|
624
616
|
["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "X2.1.1-preview-us"
|
|
625
617
|
};
|
|
626
618
|
var MODEL_BASE_URLS = {
|
|
627
|
-
["x2.1-preview" /* x2_1_preview */]: "https://cloud.xmax.22duck.cn/open/api/v1",
|
|
628
|
-
["x2.1-preview-1014" /* x2_1_preview_1014 */]: "https://dev.xmaxai.com/open/api/v1",
|
|
629
619
|
["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "https://cloud.xmax.22duck.cn/open/api/v1",
|
|
630
620
|
["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "https://api.xmax.ai/open/api/v1"
|
|
631
621
|
};
|
|
632
622
|
var RESOLUTION_BUCKETS = {
|
|
633
|
-
["x2.0" /* x2_0 */]: [],
|
|
634
|
-
["x2.0-trtc" /* x2_0_trtc */]: [
|
|
635
|
-
{ width: 1024, height: 1920 },
|
|
636
|
-
{ width: 1920, height: 1024 }
|
|
637
|
-
],
|
|
638
|
-
["x2.1-preview" /* x2_1_preview */]: [
|
|
639
|
-
{ width: 1024, height: 1920 },
|
|
640
|
-
{ width: 1920, height: 1024 }
|
|
641
|
-
],
|
|
642
|
-
["x2.1-preview-1014" /* x2_1_preview_1014 */]: [
|
|
643
|
-
{ width: 1024, height: 1920 },
|
|
644
|
-
{ width: 1920, height: 1024 }
|
|
645
|
-
],
|
|
646
623
|
["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: [
|
|
647
624
|
{ width: 1024, height: 1920 },
|
|
648
625
|
{ width: 1920, height: 1024 }
|
|
@@ -653,10 +630,6 @@ var RESOLUTION_BUCKETS = {
|
|
|
653
630
|
]
|
|
654
631
|
};
|
|
655
632
|
var MAXIMUM_INPUT_PIXELS = {
|
|
656
|
-
["x2.0" /* x2_0 */]: 128e4,
|
|
657
|
-
["x2.0-trtc" /* x2_0_trtc */]: 21e5,
|
|
658
|
-
["x2.1-preview" /* x2_1_preview */]: 21e5,
|
|
659
|
-
["x2.1-preview-1014" /* x2_1_preview_1014 */]: 21e5,
|
|
660
633
|
["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: 21e5,
|
|
661
634
|
["x2.1.1-preview-us" /* x2_1_1_preview_us */]: 21e5
|
|
662
635
|
};
|
|
@@ -682,16 +655,10 @@ function defaultFrameRate(_model) {
|
|
|
682
655
|
return 30;
|
|
683
656
|
}
|
|
684
657
|
function defaultCameraVideoFormat(model) {
|
|
685
|
-
|
|
686
|
-
return new RealtimeVideoFormat({ width: 832, height: 1472, fps: 30 });
|
|
687
|
-
}
|
|
658
|
+
modelDefaults(model);
|
|
688
659
|
return new RealtimeVideoFormat({ width: 1024, height: 1920, fps: 30 });
|
|
689
660
|
}
|
|
690
661
|
var MODEL_RTC_PROVIDERS = {
|
|
691
|
-
["x2.0" /* x2_0 */]: Object.freeze(["vertc" /* vertc */]),
|
|
692
|
-
["x2.0-trtc" /* x2_0_trtc */]: Object.freeze(["trtc" /* trtc */]),
|
|
693
|
-
["x2.1-preview" /* x2_1_preview */]: Object.freeze(["agora" /* agora */]),
|
|
694
|
-
["x2.1-preview-1014" /* x2_1_preview_1014 */]: Object.freeze(["agora" /* agora */]),
|
|
695
662
|
["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: Object.freeze(["agora" /* agora */]),
|
|
696
663
|
["x2.1.1-preview-us" /* x2_1_1_preview_us */]: Object.freeze(["agora" /* agora */])
|
|
697
664
|
};
|
|
@@ -699,8 +666,8 @@ function supportedRtcProviders(model) {
|
|
|
699
666
|
return isKnownModel(model) ? MODEL_RTC_PROVIDERS[model] : CUSTOM_MODEL_RTC_PROVIDERS;
|
|
700
667
|
}
|
|
701
668
|
var CUSTOM_MODEL_RTC_PROVIDERS = Object.freeze([
|
|
702
|
-
...MODEL_RTC_PROVIDERS["x2.1-preview" /*
|
|
703
|
-
...Object.values(RtcProvider).filter((provider) => !MODEL_RTC_PROVIDERS["x2.1-preview" /*
|
|
669
|
+
...MODEL_RTC_PROVIDERS["x2.1.1-preview-cn" /* x2_1_1_preview_cn */],
|
|
670
|
+
...Object.values(RtcProvider).filter((provider) => !MODEL_RTC_PROVIDERS["x2.1.1-preview-cn" /* x2_1_1_preview_cn */].includes(provider))
|
|
704
671
|
]);
|
|
705
672
|
|
|
706
673
|
// src/Foundation/Media/Video/FrameInterpolationSupport.ts
|
|
@@ -722,9 +689,9 @@ var MediaService = class {
|
|
|
722
689
|
/**
|
|
723
690
|
* 创建媒体服务。
|
|
724
691
|
*
|
|
725
|
-
* @param model 媒体输入规则使用的模型,默认为 `x2.
|
|
692
|
+
* @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
|
|
726
693
|
*/
|
|
727
|
-
constructor(model = "x2.
|
|
694
|
+
constructor(model = "x2.1.1-preview-cn" /* x2_1_1_preview_cn */) {
|
|
728
695
|
/**
|
|
729
696
|
* 模型约束
|
|
730
697
|
*/
|
|
@@ -3968,7 +3935,9 @@ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
|
|
|
3968
3935
|
"ref_image_path",
|
|
3969
3936
|
"mirror",
|
|
3970
3937
|
"ref_video_path",
|
|
3971
|
-
"sample_method"
|
|
3938
|
+
"sample_method",
|
|
3939
|
+
"min_bitrate",
|
|
3940
|
+
"max_bitrate"
|
|
3972
3941
|
]);
|
|
3973
3942
|
function snapshotExtraParams(params) {
|
|
3974
3943
|
if (params === void 0) return void 0;
|
|
@@ -5980,6 +5949,20 @@ var RenderController = class {
|
|
|
5980
5949
|
__publicField(this, "track");
|
|
5981
5950
|
__publicField(this, "target");
|
|
5982
5951
|
__publicField(this, "interactionEnabled", false);
|
|
5952
|
+
__publicField(this, "ready", false);
|
|
5953
|
+
}
|
|
5954
|
+
/**
|
|
5955
|
+
* 当前远端视图是否已完成渲染预热。
|
|
5956
|
+
*/
|
|
5957
|
+
get isReady() {
|
|
5958
|
+
return this.ready;
|
|
5959
|
+
}
|
|
5960
|
+
/**
|
|
5961
|
+
* 新任务重新等待首帧和预热,避免复用上一次任务的展示就绪状态。
|
|
5962
|
+
*/
|
|
5963
|
+
prepareForGeneration() {
|
|
5964
|
+
this.ready = false;
|
|
5965
|
+
this.target?.prepareForGeneration?.();
|
|
5983
5966
|
}
|
|
5984
5967
|
/**
|
|
5985
5968
|
* 注册新轨道并使旧绑定失效;本地轨道不经过该入口。
|
|
@@ -5991,6 +5974,14 @@ var RenderController = class {
|
|
|
5991
5974
|
TrajectoryRegistry.register(track, new TrajectoryBinding(listener, track.videoFormat));
|
|
5992
5975
|
}
|
|
5993
5976
|
VideoRenderRegistry.register(track, {
|
|
5977
|
+
// 临时缓解生成开头卡顿:首帧后继续播放 1000 ms,再渐入。
|
|
5978
|
+
renderWarmupMs: 1e3,
|
|
5979
|
+
renderWarmupCompletedHandler: () => {
|
|
5980
|
+
if (this.track === track) {
|
|
5981
|
+
this.ready = true;
|
|
5982
|
+
this.callbacks.onRenderWarmupCompleted?.();
|
|
5983
|
+
}
|
|
5984
|
+
},
|
|
5994
5985
|
frameDisplayHandler: () => {
|
|
5995
5986
|
if (this.track === track) {
|
|
5996
5987
|
this.callbacks.onFrameDisplayed?.();
|
|
@@ -6001,6 +5992,7 @@ var RenderController = class {
|
|
|
6001
5992
|
return;
|
|
6002
5993
|
}
|
|
6003
5994
|
this.target?.setFrameInterpolation?.(void 0);
|
|
5995
|
+
this.ready = false;
|
|
6004
5996
|
view.isMirrored = false;
|
|
6005
5997
|
this.target = view;
|
|
6006
5998
|
this.callbacks.onAttached?.();
|
|
@@ -6008,6 +6000,7 @@ var RenderController = class {
|
|
|
6008
6000
|
},
|
|
6009
6001
|
detachHandler: (view) => {
|
|
6010
6002
|
if (this.target === view) {
|
|
6003
|
+
this.ready = false;
|
|
6011
6004
|
this.callbacks.onDetached?.();
|
|
6012
6005
|
this.target = void 0;
|
|
6013
6006
|
}
|
|
@@ -6020,6 +6013,7 @@ var RenderController = class {
|
|
|
6020
6013
|
* 更新媒体画面;暂停显示时同时禁用交互。
|
|
6021
6014
|
*/
|
|
6022
6015
|
setRemoteMedia(track) {
|
|
6016
|
+
this.ready = false;
|
|
6023
6017
|
if (this.track) {
|
|
6024
6018
|
TrajectoryRegistry.binding(this.track)?.update(!!track && this.interactionEnabled);
|
|
6025
6019
|
}
|
|
@@ -6044,6 +6038,7 @@ var RenderController = class {
|
|
|
6044
6038
|
* 先使能力失效再清空画面,迟到首帧和旧手势不能恢复已结束任务。
|
|
6045
6039
|
*/
|
|
6046
6040
|
resetRemoteTrack() {
|
|
6041
|
+
this.ready = false;
|
|
6047
6042
|
const track = this.track;
|
|
6048
6043
|
this.track = void 0;
|
|
6049
6044
|
this.interactionEnabled = false;
|
|
@@ -6501,6 +6496,7 @@ var RealtimeConnectionState = /* @__PURE__ */ ((RealtimeConnectionState2) => {
|
|
|
6501
6496
|
RealtimeConnectionState2["ready"] = "Ready";
|
|
6502
6497
|
RealtimeConnectionState2["connecting"] = "Connecting";
|
|
6503
6498
|
RealtimeConnectionState2["connected"] = "Connected";
|
|
6499
|
+
RealtimeConnectionState2["starting"] = "Starting";
|
|
6504
6500
|
RealtimeConnectionState2["generating"] = "Generating";
|
|
6505
6501
|
RealtimeConnectionState2["disconnecting"] = "Disconnecting";
|
|
6506
6502
|
return RealtimeConnectionState2;
|
|
@@ -6606,6 +6602,8 @@ var RoomEvent = class _RoomEvent {
|
|
|
6606
6602
|
* @param options.videoFormat 模型生成使用的视频规格。
|
|
6607
6603
|
* @param options.context 本次生成条件。
|
|
6608
6604
|
* @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
|
|
6605
|
+
* @param options.minimumBitrate 服务端回传的最低码率,单位为 kbps。
|
|
6606
|
+
* @param options.maximumBitrate 服务端回传的最高码率,单位为 kbps。
|
|
6609
6607
|
* @param options.mirror 是否由后端镜像输入画面,默认 false。
|
|
6610
6608
|
* @param options.faceRestoration 是否回贴原始人脸,默认 false。
|
|
6611
6609
|
* @param options.extraParams 已校验的本次扩展参数,合并到 params。
|
|
@@ -6616,6 +6614,8 @@ var RoomEvent = class _RoomEvent {
|
|
|
6616
6614
|
event: "start",
|
|
6617
6615
|
params: {
|
|
6618
6616
|
..._RoomEvent.generationParameters(options),
|
|
6617
|
+
min_bitrate: options.minimumBitrate,
|
|
6618
|
+
max_bitrate: options.maximumBitrate,
|
|
6619
6619
|
mirror: options.mirror ?? false,
|
|
6620
6620
|
...options.context.referenceVideo && {
|
|
6621
6621
|
ref_video_path: options.context.referenceVideo.path,
|
|
@@ -6652,12 +6652,14 @@ var RoomEvent = class _RoomEvent {
|
|
|
6652
6652
|
});
|
|
6653
6653
|
}
|
|
6654
6654
|
/**
|
|
6655
|
-
*
|
|
6655
|
+
* 回传尺寸和码率范围调整信令,码率单位为 kbps。
|
|
6656
6656
|
*/
|
|
6657
6657
|
static changeTargetSize(options) {
|
|
6658
6658
|
return _RoomEvent.encode({
|
|
6659
6659
|
event: "change_target_size",
|
|
6660
6660
|
params: {
|
|
6661
|
+
min_bitrate: options.minimumBitrate,
|
|
6662
|
+
max_bitrate: options.maximumBitrate,
|
|
6661
6663
|
target_size: [
|
|
6662
6664
|
Math.round(options.targetSize.width),
|
|
6663
6665
|
Math.round(options.targetSize.height)
|
|
@@ -6732,6 +6734,114 @@ var RoomEvent = class _RoomEvent {
|
|
|
6732
6734
|
}
|
|
6733
6735
|
};
|
|
6734
6736
|
|
|
6737
|
+
// src/Stream/Encoding/EncodingBitratePolicy.ts
|
|
6738
|
+
var _EncodingBitratePolicy = class _EncodingBitratePolicy {
|
|
6739
|
+
/**
|
|
6740
|
+
* 最低码率固定为 100 kbps,最高码率按尺寸和帧率计算并应用预算系数。
|
|
6741
|
+
*/
|
|
6742
|
+
static resolve(width, height, fps) {
|
|
6743
|
+
new RealtimeVideoFormat({ width, height, fps }).validate();
|
|
6744
|
+
const maximum = Math.round(
|
|
6745
|
+
this.resolveMaximumBitrate(width * height, fps) * this.bitrateScale
|
|
6746
|
+
);
|
|
6747
|
+
if (!Number.isSafeInteger(maximum)) {
|
|
6748
|
+
throw new XmaxError(
|
|
6749
|
+
"INVALID_CONFIGURATION" /* invalidConfiguration */,
|
|
6750
|
+
"Realtime video format exceeds the supported bitrate range"
|
|
6751
|
+
);
|
|
6752
|
+
}
|
|
6753
|
+
return { minimum: 100, maximum: Math.max(100, maximum) };
|
|
6754
|
+
}
|
|
6755
|
+
/**
|
|
6756
|
+
* 按像素面积和帧率插值计算最高码率参考值。
|
|
6757
|
+
*/
|
|
6758
|
+
static resolveMaximumBitrate(pixels, fps) {
|
|
6759
|
+
const bitrate15 = _EncodingBitratePolicy.interpolate(
|
|
6760
|
+
pixels,
|
|
6761
|
+
_EncodingBitratePolicy.referenceBitratesAt15FPS
|
|
6762
|
+
);
|
|
6763
|
+
const first30 = _EncodingBitratePolicy.referenceBitratesAt30FPS[0];
|
|
6764
|
+
let bitrate30;
|
|
6765
|
+
if (pixels < first30.value) {
|
|
6766
|
+
const reference15 = _EncodingBitratePolicy.interpolate(
|
|
6767
|
+
first30.value,
|
|
6768
|
+
_EncodingBitratePolicy.referenceBitratesAt15FPS
|
|
6769
|
+
);
|
|
6770
|
+
bitrate30 = bitrate15 * (first30.bitrate / reference15);
|
|
6771
|
+
} else {
|
|
6772
|
+
bitrate30 = _EncodingBitratePolicy.interpolate(
|
|
6773
|
+
pixels,
|
|
6774
|
+
_EncodingBitratePolicy.referenceBitratesAt30FPS
|
|
6775
|
+
);
|
|
6776
|
+
}
|
|
6777
|
+
const bitrate10 = bitrate15 * (400 / 500);
|
|
6778
|
+
const maximum60 = bitrate30 * (6500 / 3150);
|
|
6779
|
+
return _EncodingBitratePolicy.interpolate(fps, [
|
|
6780
|
+
{ value: 10, bitrate: bitrate10 * 2 },
|
|
6781
|
+
{ value: 15, bitrate: bitrate15 * 2 },
|
|
6782
|
+
{ value: 30, bitrate: bitrate30 * 2 },
|
|
6783
|
+
{ value: 60, bitrate: maximum60 }
|
|
6784
|
+
]);
|
|
6785
|
+
}
|
|
6786
|
+
/**
|
|
6787
|
+
* 在相邻参考点间线性插值,表外按最近端点的比例外推。
|
|
6788
|
+
*/
|
|
6789
|
+
static interpolate(value, points) {
|
|
6790
|
+
const first = points[0];
|
|
6791
|
+
if (value <= first.value) {
|
|
6792
|
+
return first.bitrate * (value / first.value);
|
|
6793
|
+
}
|
|
6794
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
6795
|
+
const upper = points[index];
|
|
6796
|
+
if (value <= upper.value) {
|
|
6797
|
+
const lower = points[index - 1];
|
|
6798
|
+
const ratio = (value - lower.value) / (upper.value - lower.value);
|
|
6799
|
+
return lower.bitrate + (upper.bitrate - lower.bitrate) * ratio;
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
const last = points[points.length - 1];
|
|
6803
|
+
return last.bitrate * (value / last.value);
|
|
6804
|
+
}
|
|
6805
|
+
};
|
|
6806
|
+
/**
|
|
6807
|
+
* 最高码率相对参考值的预算系数。
|
|
6808
|
+
*/
|
|
6809
|
+
__publicField(_EncodingBitratePolicy, "bitrateScale", 0.6);
|
|
6810
|
+
/**
|
|
6811
|
+
* 流畅优先码率参考,按像素面积升序排列,单位为 kbps。
|
|
6812
|
+
*/
|
|
6813
|
+
__publicField(_EncodingBitratePolicy, "referenceBitratesAt15FPS", [
|
|
6814
|
+
{ value: 120 * 120, bitrate: 50 },
|
|
6815
|
+
{ value: 160 * 120, bitrate: 65 },
|
|
6816
|
+
{ value: 180 * 180, bitrate: 100 },
|
|
6817
|
+
{ value: 240 * 180, bitrate: 120 },
|
|
6818
|
+
{ value: 320 * 180, bitrate: 140 },
|
|
6819
|
+
{ value: 320 * 240, bitrate: 200 },
|
|
6820
|
+
{ value: 424 * 240, bitrate: 220 },
|
|
6821
|
+
{ value: 360 * 360, bitrate: 260 },
|
|
6822
|
+
{ value: 480 * 360, bitrate: 320 },
|
|
6823
|
+
{ value: 640 * 360, bitrate: 400 },
|
|
6824
|
+
{ value: 640 * 480, bitrate: 500 },
|
|
6825
|
+
{ value: 848 * 480, bitrate: 610 },
|
|
6826
|
+
{ value: 960 * 720, bitrate: 910 },
|
|
6827
|
+
{ value: 1280 * 720, bitrate: 1130 },
|
|
6828
|
+
{ value: 1920 * 1080, bitrate: 2080 }
|
|
6829
|
+
]);
|
|
6830
|
+
/**
|
|
6831
|
+
* 30 fps 的像素面积与码率参考点,单位为 kbps。
|
|
6832
|
+
*/
|
|
6833
|
+
__publicField(_EncodingBitratePolicy, "referenceBitratesAt30FPS", [
|
|
6834
|
+
{ value: 360 * 360, bitrate: 400 },
|
|
6835
|
+
{ value: 480 * 360, bitrate: 490 },
|
|
6836
|
+
{ value: 640 * 360, bitrate: 600 },
|
|
6837
|
+
{ value: 640 * 480, bitrate: 750 },
|
|
6838
|
+
{ value: 848 * 480, bitrate: 930 },
|
|
6839
|
+
{ value: 960 * 720, bitrate: 1380 },
|
|
6840
|
+
{ value: 1280 * 720, bitrate: 1710 },
|
|
6841
|
+
{ value: 1920 * 1080, bitrate: 3150 }
|
|
6842
|
+
]);
|
|
6843
|
+
var EncodingBitratePolicy = _EncodingBitratePolicy;
|
|
6844
|
+
|
|
6735
6845
|
// src/Stream/Room/RoomHeartbeat.ts
|
|
6736
6846
|
var RoomHeartbeat = class {
|
|
6737
6847
|
/**
|
|
@@ -7022,7 +7132,7 @@ var RoomController = class {
|
|
|
7022
7132
|
*
|
|
7023
7133
|
* @throws 已有房间未离开、操作失效或进房失败时抛出错误。
|
|
7024
7134
|
*/
|
|
7025
|
-
async join(connection, ensureActive2) {
|
|
7135
|
+
async join(connection, ensureActive2, measureRtcJoin) {
|
|
7026
7136
|
try {
|
|
7027
7137
|
ensureActive2();
|
|
7028
7138
|
} catch (error) {
|
|
@@ -7041,11 +7151,14 @@ var RoomController = class {
|
|
|
7041
7151
|
if (!this.customCodec) this.codec = new RoomMessageCodec(
|
|
7042
7152
|
connection.provider === "vertc" /* vertc */ ? null : connection.provider === "agora" /* agora */ ? "__agora_chunk__" : "__trtc_chunk__"
|
|
7043
7153
|
);
|
|
7044
|
-
|
|
7154
|
+
const configuration = toRoomJoinConfiguration(connection);
|
|
7155
|
+
const completeRtcJoin = measureRtcJoin?.();
|
|
7156
|
+
await this.rtcManager.joinRoom(configuration);
|
|
7045
7157
|
ensureActive2();
|
|
7046
7158
|
if (this.state.kind !== "joining" || this.state.operationID !== operationID || this.leaveOperation) {
|
|
7047
7159
|
throw new XmaxError("CANCELLED" /* cancelled */, "RTC room join was cancelled");
|
|
7048
7160
|
}
|
|
7161
|
+
completeRtcJoin?.();
|
|
7049
7162
|
this.state = { kind: "joined", userID: connection.userID };
|
|
7050
7163
|
this.heartbeat.start(connection.userID);
|
|
7051
7164
|
} catch (error) {
|
|
@@ -7097,7 +7210,8 @@ var RoomController = class {
|
|
|
7097
7210
|
targetSize: options.targetSize,
|
|
7098
7211
|
mirror: options.mirror ?? false,
|
|
7099
7212
|
faceRestoration: options.faceRestoration ?? false,
|
|
7100
|
-
extraParams: options.extraParams
|
|
7213
|
+
extraParams: options.extraParams,
|
|
7214
|
+
...this.downlinkBitrates(options.targetSize ?? options.videoFormat)
|
|
7101
7215
|
})
|
|
7102
7216
|
);
|
|
7103
7217
|
}
|
|
@@ -7136,10 +7250,23 @@ var RoomController = class {
|
|
|
7136
7250
|
RoomEvent.changeTargetSize({
|
|
7137
7251
|
userID: this.requireUserID(),
|
|
7138
7252
|
taskID: options.taskID,
|
|
7139
|
-
targetSize: options.targetSize
|
|
7253
|
+
targetSize: options.targetSize,
|
|
7254
|
+
...this.downlinkBitrates(options.targetSize)
|
|
7140
7255
|
})
|
|
7141
7256
|
);
|
|
7142
7257
|
}
|
|
7258
|
+
/**
|
|
7259
|
+
* 按回传尺寸和 24 fps 估算下行码率,最高值限制在 100~10000 kbps。
|
|
7260
|
+
* 估算不设置服务端帧率,也不沿用显式上传码率。
|
|
7261
|
+
*/
|
|
7262
|
+
downlinkBitrates(size) {
|
|
7263
|
+
const { maximum } = EncodingBitratePolicy.resolve(
|
|
7264
|
+
Math.round(size.width),
|
|
7265
|
+
Math.round(size.height),
|
|
7266
|
+
24
|
|
7267
|
+
);
|
|
7268
|
+
return { minimumBitrate: 100, maximumBitrate: Math.min(1e4, maximum) };
|
|
7269
|
+
}
|
|
7143
7270
|
/**
|
|
7144
7271
|
* 尝试发送生成停止信令;未进房或任务标识为空时忽略。
|
|
7145
7272
|
*/
|
|
@@ -7241,7 +7368,7 @@ var ENCODER_PREFERENCE_MAP = {
|
|
|
7241
7368
|
["maintainFramerate" /* maintainFramerate */]: "maintainFramerate" /* maintainFramerate */,
|
|
7242
7369
|
["maintainQuality" /* maintainQuality */]: "maintainQuality" /* maintainQuality */
|
|
7243
7370
|
};
|
|
7244
|
-
var
|
|
7371
|
+
var EncodingController = class {
|
|
7245
7372
|
/**
|
|
7246
7373
|
* 创建编码控制器。
|
|
7247
7374
|
*
|
|
@@ -7266,21 +7393,13 @@ var _EncodingController = class _EncodingController {
|
|
|
7266
7393
|
minimum = videoFormat.minimumBitrate;
|
|
7267
7394
|
maximum = videoFormat.maximumBitrate;
|
|
7268
7395
|
} else {
|
|
7269
|
-
const
|
|
7270
|
-
videoFormat.width
|
|
7396
|
+
const defaults = EncodingBitratePolicy.resolve(
|
|
7397
|
+
videoFormat.width,
|
|
7398
|
+
videoFormat.height,
|
|
7271
7399
|
videoFormat.fps
|
|
7272
7400
|
);
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
throw new XmaxError(
|
|
7276
|
-
"INVALID_CONFIGURATION" /* invalidConfiguration */,
|
|
7277
|
-
"Realtime video format exceeds the supported bitrate range"
|
|
7278
|
-
);
|
|
7279
|
-
}
|
|
7280
|
-
const defaultMinimum = Math.max(1, Math.round(bitrates.minimum));
|
|
7281
|
-
const defaultMaximum = Math.max(defaultMinimum + 1, maximumBitrate);
|
|
7282
|
-
minimum = videoFormat.minimumBitrate ?? defaultMinimum;
|
|
7283
|
-
maximum = videoFormat.maximumBitrate ?? defaultMaximum;
|
|
7401
|
+
minimum = videoFormat.minimumBitrate ?? defaults.minimum;
|
|
7402
|
+
maximum = videoFormat.maximumBitrate ?? defaults.maximum;
|
|
7284
7403
|
}
|
|
7285
7404
|
if (minimum > maximum) {
|
|
7286
7405
|
throw new XmaxError(
|
|
@@ -7321,97 +7440,7 @@ var _EncodingController = class _EncodingController {
|
|
|
7321
7440
|
}
|
|
7322
7441
|
return format;
|
|
7323
7442
|
}
|
|
7324
|
-
/**
|
|
7325
|
-
* 按像素面积与帧率计算码率区间(kbps):流畅优先为最低,画质优先为最高。
|
|
7326
|
-
*/
|
|
7327
|
-
static resolveBitrates(pixels, fps) {
|
|
7328
|
-
const bitrate15 = _EncodingController.interpolate(
|
|
7329
|
-
pixels,
|
|
7330
|
-
_EncodingController.referenceBitratesAt15FPS
|
|
7331
|
-
);
|
|
7332
|
-
const first30 = _EncodingController.referenceBitratesAt30FPS[0];
|
|
7333
|
-
let bitrate30;
|
|
7334
|
-
if (pixels < first30.value) {
|
|
7335
|
-
const reference15 = _EncodingController.interpolate(
|
|
7336
|
-
first30.value,
|
|
7337
|
-
_EncodingController.referenceBitratesAt15FPS
|
|
7338
|
-
);
|
|
7339
|
-
bitrate30 = bitrate15 * (first30.bitrate / reference15);
|
|
7340
|
-
} else {
|
|
7341
|
-
bitrate30 = _EncodingController.interpolate(
|
|
7342
|
-
pixels,
|
|
7343
|
-
_EncodingController.referenceBitratesAt30FPS
|
|
7344
|
-
);
|
|
7345
|
-
}
|
|
7346
|
-
const bitrate10 = bitrate15 * (400 / 500);
|
|
7347
|
-
const minimum60 = bitrate30 * (4780 / 3150);
|
|
7348
|
-
const maximum60 = bitrate30 * (6500 / 3150);
|
|
7349
|
-
return {
|
|
7350
|
-
minimum: _EncodingController.interpolate(fps, [
|
|
7351
|
-
{ value: 10, bitrate: bitrate10 },
|
|
7352
|
-
{ value: 15, bitrate: bitrate15 },
|
|
7353
|
-
{ value: 30, bitrate: bitrate30 },
|
|
7354
|
-
{ value: 60, bitrate: minimum60 }
|
|
7355
|
-
]),
|
|
7356
|
-
maximum: _EncodingController.interpolate(fps, [
|
|
7357
|
-
{ value: 10, bitrate: bitrate10 * 2 },
|
|
7358
|
-
{ value: 15, bitrate: bitrate15 * 2 },
|
|
7359
|
-
{ value: 30, bitrate: bitrate30 * 2 },
|
|
7360
|
-
{ value: 60, bitrate: maximum60 }
|
|
7361
|
-
])
|
|
7362
|
-
};
|
|
7363
|
-
}
|
|
7364
|
-
/**
|
|
7365
|
-
* 在相邻参考点间线性插值,表外按最近端点的比例外推。
|
|
7366
|
-
*/
|
|
7367
|
-
static interpolate(value, points) {
|
|
7368
|
-
const first = points[0];
|
|
7369
|
-
if (value <= first.value) {
|
|
7370
|
-
return first.bitrate * (value / first.value);
|
|
7371
|
-
}
|
|
7372
|
-
for (let index = 1; index < points.length; index += 1) {
|
|
7373
|
-
const upper = points[index];
|
|
7374
|
-
if (value <= upper.value) {
|
|
7375
|
-
const lower = points[index - 1];
|
|
7376
|
-
const ratio = (value - lower.value) / (upper.value - lower.value);
|
|
7377
|
-
return lower.bitrate + (upper.bitrate - lower.bitrate) * ratio;
|
|
7378
|
-
}
|
|
7379
|
-
}
|
|
7380
|
-
const last = points[points.length - 1];
|
|
7381
|
-
return last.bitrate * (value / last.value);
|
|
7382
|
-
}
|
|
7383
7443
|
};
|
|
7384
|
-
/**
|
|
7385
|
-
* 流畅优先码率参考,按像素面积升序排列,单位为 kbps。
|
|
7386
|
-
*/
|
|
7387
|
-
__publicField(_EncodingController, "referenceBitratesAt15FPS", [
|
|
7388
|
-
{ value: 120 * 120, bitrate: 50 },
|
|
7389
|
-
{ value: 160 * 120, bitrate: 65 },
|
|
7390
|
-
{ value: 180 * 180, bitrate: 100 },
|
|
7391
|
-
{ value: 240 * 180, bitrate: 120 },
|
|
7392
|
-
{ value: 320 * 180, bitrate: 140 },
|
|
7393
|
-
{ value: 320 * 240, bitrate: 200 },
|
|
7394
|
-
{ value: 424 * 240, bitrate: 220 },
|
|
7395
|
-
{ value: 360 * 360, bitrate: 260 },
|
|
7396
|
-
{ value: 480 * 360, bitrate: 320 },
|
|
7397
|
-
{ value: 640 * 360, bitrate: 400 },
|
|
7398
|
-
{ value: 640 * 480, bitrate: 500 },
|
|
7399
|
-
{ value: 848 * 480, bitrate: 610 },
|
|
7400
|
-
{ value: 960 * 720, bitrate: 910 },
|
|
7401
|
-
{ value: 1280 * 720, bitrate: 1130 },
|
|
7402
|
-
{ value: 1920 * 1080, bitrate: 2080 }
|
|
7403
|
-
]);
|
|
7404
|
-
__publicField(_EncodingController, "referenceBitratesAt30FPS", [
|
|
7405
|
-
{ value: 360 * 360, bitrate: 400 },
|
|
7406
|
-
{ value: 480 * 360, bitrate: 490 },
|
|
7407
|
-
{ value: 640 * 360, bitrate: 600 },
|
|
7408
|
-
{ value: 640 * 480, bitrate: 750 },
|
|
7409
|
-
{ value: 848 * 480, bitrate: 930 },
|
|
7410
|
-
{ value: 960 * 720, bitrate: 1380 },
|
|
7411
|
-
{ value: 1280 * 720, bitrate: 1710 },
|
|
7412
|
-
{ value: 1920 * 1080, bitrate: 3150 }
|
|
7413
|
-
]);
|
|
7414
|
-
var EncodingController = _EncodingController;
|
|
7415
7444
|
|
|
7416
7445
|
// src/Stream/StreamController.ts
|
|
7417
7446
|
function makeInitialState() {
|
|
@@ -7580,9 +7609,9 @@ var StreamController = class {
|
|
|
7580
7609
|
*
|
|
7581
7610
|
* @throws 连接已取消,或 RTC 进房、房间配置与本地流发布失败时抛出错误。
|
|
7582
7611
|
*/
|
|
7583
|
-
async connect(connection, includeLocalAudio, ensureActive2, beforePublish, publishLocalMedia = true) {
|
|
7612
|
+
async connect(connection, includeLocalAudio, ensureActive2, beforePublish, publishLocalMedia = true, measureRtcJoin) {
|
|
7584
7613
|
this.configureRoom({ roomID: connection.roomID, botID: connection.botID });
|
|
7585
|
-
await this.roomController.join(connection, ensureActive2);
|
|
7614
|
+
await this.roomController.join(connection, ensureActive2, measureRtcJoin);
|
|
7586
7615
|
ensureActive2();
|
|
7587
7616
|
XmaxLogger.stream.info(
|
|
7588
7617
|
() => `RTC \u623F\u95F4\u5DF2\u914D\u7F6E (RTC Room Configured)
|
|
@@ -8180,12 +8209,13 @@ var StreamController = class {
|
|
|
8180
8209
|
|
|
8181
8210
|
// src/Stream/Quality/QualityTiers.ts
|
|
8182
8211
|
var INITIAL_QUALITY_LEVEL = 2;
|
|
8212
|
+
var MINIMUM_QUALITY_SHORT_SIDE = 240;
|
|
8183
8213
|
var QUALITY_TIERS = {
|
|
8184
|
-
L1: { width: 1024, height: 1920, frameRate: 30 },
|
|
8185
|
-
L2: { width:
|
|
8186
|
-
L3: { width:
|
|
8187
|
-
L4: { width: 512, height: 960, frameRate: 20 },
|
|
8188
|
-
L5: { width:
|
|
8214
|
+
L1: { scale: 1, width: 1024, height: 1920, frameRate: 30 },
|
|
8215
|
+
L2: { scale: 0.8, width: 818, height: 1536, frameRate: 24 },
|
|
8216
|
+
L3: { scale: 0.65, width: 664, height: 1248, frameRate: 24 },
|
|
8217
|
+
L4: { scale: 0.5, width: 512, height: 960, frameRate: 20 },
|
|
8218
|
+
L5: { scale: 0.4, width: 408, height: 768, frameRate: 16 }
|
|
8189
8219
|
};
|
|
8190
8220
|
|
|
8191
8221
|
// src/Stream/Quality/QualityPolicy.ts
|
|
@@ -8201,9 +8231,13 @@ var DEFAULT_POLICY = {
|
|
|
8201
8231
|
// src/Stream/Quality/QualityController.ts
|
|
8202
8232
|
var ORDERED_QUALITY_TIERS = Object.values(QUALITY_TIERS);
|
|
8203
8233
|
var QualityController = class {
|
|
8204
|
-
|
|
8234
|
+
/**
|
|
8235
|
+
* 创建调档状态机;候选档位不满足编码约束时保持当前档位,不跨档尝试。
|
|
8236
|
+
*/
|
|
8237
|
+
constructor(apply, now = () => performance.now(), policy = {}, canApply = () => true) {
|
|
8205
8238
|
__publicField(this, "apply", apply);
|
|
8206
8239
|
__publicField(this, "now", now);
|
|
8240
|
+
__publicField(this, "canApply", canApply);
|
|
8207
8241
|
__publicField(this, "policy");
|
|
8208
8242
|
__publicField(this, "active", false);
|
|
8209
8243
|
__publicField(this, "index", 0);
|
|
@@ -8293,10 +8327,13 @@ var QualityController = class {
|
|
|
8293
8327
|
this.good = this.poor = 0;
|
|
8294
8328
|
}
|
|
8295
8329
|
async change(index, reason) {
|
|
8330
|
+
this.resetCounts();
|
|
8331
|
+
if (!this.canApply(index + 1)) {
|
|
8332
|
+
return;
|
|
8333
|
+
}
|
|
8296
8334
|
const lifecycle = this.lifecycle;
|
|
8297
8335
|
const change = { from: this.index + 1, to: index + 1, ...ORDERED_QUALITY_TIERS[index], reason };
|
|
8298
8336
|
this.switching = true;
|
|
8299
|
-
this.resetCounts();
|
|
8300
8337
|
try {
|
|
8301
8338
|
await this.apply(change, lifecycle.signal);
|
|
8302
8339
|
if (lifecycle.signal.aborted) return;
|
|
@@ -8321,12 +8358,20 @@ var QualityController = class {
|
|
|
8321
8358
|
// src/Stream/Quality/QualityVideoFormat.ts
|
|
8322
8359
|
function qualityVideoSize(baseline, level) {
|
|
8323
8360
|
const tier = Object.values(QUALITY_TIERS)[level - 1];
|
|
8324
|
-
if (!Number.isInteger(level) || !tier)
|
|
8361
|
+
if (!Number.isInteger(level) || !tier) {
|
|
8362
|
+
throw new Error("Unsupported quality level");
|
|
8363
|
+
}
|
|
8325
8364
|
baseline.validate();
|
|
8326
|
-
const
|
|
8327
|
-
const
|
|
8365
|
+
const shortSide = Math.min(baseline.width, baseline.height);
|
|
8366
|
+
const minimum = Math.min(shortSide, MINIMUM_QUALITY_SHORT_SIDE);
|
|
8367
|
+
const scale = Math.max(tier.scale, minimum / shortSide);
|
|
8368
|
+
const even = (value) => Math.max(2, Math.floor(value / 2 + 1e-9) * 2);
|
|
8328
8369
|
return { width: even(baseline.width * scale), height: even(baseline.height * scale) };
|
|
8329
8370
|
}
|
|
8371
|
+
function supportsQualityEncoding(format) {
|
|
8372
|
+
const defaults = EncodingBitratePolicy.resolve(format.width, format.height, format.fps);
|
|
8373
|
+
return (format.minimumBitrate ?? defaults.minimum) <= (format.maximumBitrate ?? defaults.maximum);
|
|
8374
|
+
}
|
|
8330
8375
|
function qualityVideoFormat(baseline, level) {
|
|
8331
8376
|
const size = qualityVideoSize(baseline, level);
|
|
8332
8377
|
const tier = Object.values(QUALITY_TIERS)[level - 1];
|
|
@@ -8338,35 +8383,59 @@ function qualityVideoFormat(baseline, level) {
|
|
|
8338
8383
|
}
|
|
8339
8384
|
|
|
8340
8385
|
// src/Stream/Quality/QualityHistory.ts
|
|
8341
|
-
var
|
|
8342
|
-
|
|
8386
|
+
var _QualityHistory = class _QualityHistory {
|
|
8387
|
+
/**
|
|
8388
|
+
* 创建按模型与 RTC 提供方隔离的档位存储。
|
|
8389
|
+
*/
|
|
8390
|
+
constructor(model, provider, now = Date.now) {
|
|
8343
8391
|
__publicField(this, "model", model);
|
|
8344
8392
|
__publicField(this, "provider", provider);
|
|
8393
|
+
__publicField(this, "now", now);
|
|
8345
8394
|
}
|
|
8346
|
-
/**
|
|
8395
|
+
/**
|
|
8396
|
+
* 无记录、过期、损坏、时钟回退或存储不可用时使用默认 L2。
|
|
8397
|
+
*/
|
|
8347
8398
|
read(direction) {
|
|
8348
8399
|
try {
|
|
8349
|
-
const
|
|
8350
|
-
|
|
8400
|
+
const entry = JSON.parse(globalThis.localStorage.getItem(this.key(direction)) ?? "null");
|
|
8401
|
+
const now = this.now();
|
|
8402
|
+
if (entry && this.isValid(entry.level) && Number.isFinite(entry.savedAt) && entry.savedAt > 0 && now >= entry.savedAt && now - entry.savedAt < _QualityHistory.expirationMs) {
|
|
8403
|
+
return entry.level;
|
|
8404
|
+
}
|
|
8351
8405
|
} catch {
|
|
8352
8406
|
}
|
|
8353
8407
|
return INITIAL_QUALITY_LEVEL;
|
|
8354
8408
|
}
|
|
8355
|
-
/**
|
|
8409
|
+
/**
|
|
8410
|
+
* 应用成功后保存档位并刷新一小时有效期;下行成功仅表示信令已发送。
|
|
8411
|
+
*/
|
|
8356
8412
|
write(direction, level) {
|
|
8357
|
-
if (!this.isValid(level))
|
|
8413
|
+
if (!this.isValid(level)) {
|
|
8414
|
+
return;
|
|
8415
|
+
}
|
|
8358
8416
|
try {
|
|
8359
|
-
globalThis.localStorage.setItem(this.key(direction),
|
|
8417
|
+
globalThis.localStorage.setItem(this.key(direction), JSON.stringify({ level, savedAt: this.now() }));
|
|
8360
8418
|
} catch {
|
|
8361
8419
|
}
|
|
8362
8420
|
}
|
|
8421
|
+
/**
|
|
8422
|
+
* 生成当前方向的存储键;无时间戳的旧记录按无效记录处理。
|
|
8423
|
+
*/
|
|
8363
8424
|
key(direction) {
|
|
8364
8425
|
return `xmax:quality:v1:${this.provider}:${encodeURIComponent(this.model)}:${direction}`;
|
|
8365
8426
|
}
|
|
8427
|
+
/**
|
|
8428
|
+
* 校验档位编号。
|
|
8429
|
+
*/
|
|
8366
8430
|
isValid(level) {
|
|
8367
8431
|
return Number.isInteger(level) && level >= 1 && level <= Object.keys(QUALITY_TIERS).length;
|
|
8368
8432
|
}
|
|
8369
8433
|
};
|
|
8434
|
+
/**
|
|
8435
|
+
* 历史档位有效期,单位为毫秒。
|
|
8436
|
+
*/
|
|
8437
|
+
__publicField(_QualityHistory, "expirationMs", 60 * 60 * 1e3);
|
|
8438
|
+
var QualityHistory = _QualityHistory;
|
|
8370
8439
|
|
|
8371
8440
|
// src/Core/Realtime/RealtimeCoordinator.ts
|
|
8372
8441
|
function scopeAffects(scope, kind) {
|
|
@@ -8504,7 +8573,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
8504
8573
|
await this.errorHandler.report(error);
|
|
8505
8574
|
throw error;
|
|
8506
8575
|
}
|
|
8507
|
-
const operationKind = kind === "generation" /* generation */ && this.state.connectionState === "Generating" /* generating */ ? "configuration" /* configuration */ : kind;
|
|
8576
|
+
const operationKind = kind === "generation" /* generation */ && (this.state.connectionState === "Generating" /* generating */ || this.state.connectionState === "Starting" /* starting */ && !!this.state.taskID) ? "configuration" /* configuration */ : kind;
|
|
8508
8577
|
const lease = new OperationLease(failureScope);
|
|
8509
8578
|
const resultPromise = (async () => body(lease))();
|
|
8510
8579
|
const operation = {
|
|
@@ -8544,6 +8613,19 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
|
|
|
8544
8613
|
new RealtimeState({ connectionState: "Ready" /* ready */ })
|
|
8545
8614
|
);
|
|
8546
8615
|
}
|
|
8616
|
+
/**
|
|
8617
|
+
* 当前生成任务和远端展示均就绪后进入 generating;迟到通知不恢复已结束任务。
|
|
8618
|
+
*/
|
|
8619
|
+
async remotePreviewDidBecomeReady(sessionID, taskID, isCurrent) {
|
|
8620
|
+
if (this.termination || this.state.connectionState !== "Starting" /* starting */ || this.state.sessionID !== sessionID || this.state.taskID !== taskID || !isCurrent()) {
|
|
8621
|
+
return;
|
|
8622
|
+
}
|
|
8623
|
+
await this.setState(new RealtimeState({
|
|
8624
|
+
connectionState: "Generating" /* generating */,
|
|
8625
|
+
sessionID,
|
|
8626
|
+
taskID
|
|
8627
|
+
}));
|
|
8628
|
+
}
|
|
8547
8629
|
/**
|
|
8548
8630
|
* 断开实时连接;尚未提交连接状态的活跃操作也会被取消。
|
|
8549
8631
|
*/
|
|
@@ -8836,11 +8918,16 @@ var RealtimeLaunchTimer = class {
|
|
|
8836
8918
|
return this.measure("permissionMs", performance.now());
|
|
8837
8919
|
}
|
|
8838
8920
|
/**
|
|
8839
|
-
*
|
|
8840
|
-
|
|
8921
|
+
* 开始创建业务会话的计时。
|
|
8922
|
+
*/
|
|
8923
|
+
startSession() {
|
|
8924
|
+
return this.measure("sessionMs", performance.now());
|
|
8925
|
+
}
|
|
8926
|
+
/**
|
|
8927
|
+
* 开始 RTC 适配器进房调用的计时。
|
|
8841
8928
|
*/
|
|
8842
|
-
|
|
8843
|
-
return this.measure("
|
|
8929
|
+
startRtcJoin() {
|
|
8930
|
+
return this.measure("rtcJoinMs", performance.now());
|
|
8844
8931
|
}
|
|
8845
8932
|
/**
|
|
8846
8933
|
* 开始发布本地流计时,返回发布完成时调用的记录函数。
|
|
@@ -8849,11 +8936,17 @@ var RealtimeLaunchTimer = class {
|
|
|
8849
8936
|
return this.measure("publishMs", performance.now());
|
|
8850
8937
|
}
|
|
8851
8938
|
/**
|
|
8852
|
-
*
|
|
8939
|
+
* 开始等待远端首帧提交合成器,不包含后续渲染预热。
|
|
8853
8940
|
*/
|
|
8854
8941
|
startFirstFrame() {
|
|
8855
8942
|
return this.measure("firstFrameMs", performance.now());
|
|
8856
8943
|
}
|
|
8944
|
+
/**
|
|
8945
|
+
* 首帧就绪后开始渲染预热;结束时记录预热及实际总耗时,不含渐入动画。
|
|
8946
|
+
*/
|
|
8947
|
+
startRenderWarmup() {
|
|
8948
|
+
return this.measure("renderWarmupMs", performance.now());
|
|
8949
|
+
}
|
|
8857
8950
|
/**
|
|
8858
8951
|
* 冻结本轮结果,令尚未完成的回调失效;下一轮相机启动才重置。
|
|
8859
8952
|
*/
|
|
@@ -8874,7 +8967,7 @@ var RealtimeLaunchTimer = class {
|
|
|
8874
8967
|
this.snapshot = Object.freeze({
|
|
8875
8968
|
...this.snapshot,
|
|
8876
8969
|
[key]: now - startedAt,
|
|
8877
|
-
...key === "
|
|
8970
|
+
...key === "renderWarmupMs" ? { totalMs: now - this.startedAt } : {}
|
|
8878
8971
|
});
|
|
8879
8972
|
this.notify();
|
|
8880
8973
|
};
|
|
@@ -8925,8 +9018,8 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
8925
9018
|
}
|
|
8926
9019
|
/**
|
|
8927
9020
|
* 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
|
|
8928
|
-
*
|
|
8929
|
-
*
|
|
9021
|
+
* 会话创建、RTC 进房和本地流发布分别计时;可选 beforePublish 检查阻塞发布,
|
|
9022
|
+
* 其等待只反映在实际完整启动耗时中。
|
|
8930
9023
|
* @param options.localTrack 当前本地媒体轨道。
|
|
8931
9024
|
* @param options.remoteVideoFormat 原始生成及远端画面规格,缺省使用本地轨道规格。
|
|
8932
9025
|
* @param options.localVideoFormat 发布前实际应用的上行编码格式;与原始生成及远端格式分开。
|
|
@@ -8943,10 +9036,11 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
8943
9036
|
const sessionService = this.requireSessionService();
|
|
8944
9037
|
const streamController = this.dependencies.streamController;
|
|
8945
9038
|
options.ensureCurrent();
|
|
8946
|
-
const
|
|
9039
|
+
const completeSession = this.dependencies.timing.startSession();
|
|
8947
9040
|
const session = await sessionService.createSession(options.model);
|
|
8948
9041
|
this.activeSession = session;
|
|
8949
9042
|
options.ensureCurrent();
|
|
9043
|
+
completeSession();
|
|
8950
9044
|
const connection = session.connection;
|
|
8951
9045
|
if (!connection) {
|
|
8952
9046
|
throw new XmaxError(
|
|
@@ -8979,11 +9073,10 @@ var XmaxRealtimeConnectionManager = class {
|
|
|
8979
9073
|
}, async () => {
|
|
8980
9074
|
options.ensureCurrent();
|
|
8981
9075
|
await options.beforePublish?.();
|
|
8982
|
-
|
|
9076
|
+
options.ensureCurrent();
|
|
8983
9077
|
completePublish = this.dependencies.timing.startPublish();
|
|
8984
|
-
}, publishLocalMedia);
|
|
9078
|
+
}, publishLocalMedia, () => this.dependencies.timing.startRtcJoin());
|
|
8985
9079
|
options.ensureCurrent();
|
|
8986
|
-
completeConnection();
|
|
8987
9080
|
completePublish?.();
|
|
8988
9081
|
options.onPublished();
|
|
8989
9082
|
sessionService.startHeartbeat(session.id, {
|
|
@@ -9426,6 +9519,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
9426
9519
|
*/
|
|
9427
9520
|
__publicField(this, "launchTimer", new RealtimeLaunchTimer());
|
|
9428
9521
|
__publicField(this, "remoteFrameDisplayHandler");
|
|
9522
|
+
__publicField(this, "remoteRenderWarmupCompletedHandler");
|
|
9429
9523
|
/**
|
|
9430
9524
|
* 媒体权限弹窗观察
|
|
9431
9525
|
*/
|
|
@@ -9502,6 +9596,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
9502
9596
|
});
|
|
9503
9597
|
this.renderController = new RenderController({
|
|
9504
9598
|
onFrameDisplayed: () => this.remoteFrameDisplayHandler?.(),
|
|
9599
|
+
onRenderWarmupCompleted: () => {
|
|
9600
|
+
this.remoteRenderWarmupCompletedHandler?.();
|
|
9601
|
+
void this.completeGenerationPresentation().catch((error) => this.errorHandler.report(error));
|
|
9602
|
+
},
|
|
9505
9603
|
onAttached: () => this.applyRemoteInterpolation(),
|
|
9506
9604
|
onDetached: () => {
|
|
9507
9605
|
this.interpolationRevision++;
|
|
@@ -9530,6 +9628,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
9530
9628
|
await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
|
|
9531
9629
|
await this.applyUplinkQuality(change.to, token, signal);
|
|
9532
9630
|
});
|
|
9631
|
+
}, void 0, {}, (level) => {
|
|
9632
|
+
const track = this.currentUplinkTrack;
|
|
9633
|
+
const state2 = track && this.qualityStates.get(track);
|
|
9634
|
+
return !!state2 && supportsQualityEncoding(this.uplinkQualityFormat(state2.baseline, level));
|
|
9533
9635
|
});
|
|
9534
9636
|
this.downlinkQualityController = new QualityController(async (change, signal) => {
|
|
9535
9637
|
await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
|
|
@@ -9600,6 +9702,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
9600
9702
|
}
|
|
9601
9703
|
const level = Math.max(1, Math.min(Object.keys(QUALITY_TIERS).length, state2.level + (direction === "upgrade" ? -1 : 1)));
|
|
9602
9704
|
if (level === state2.level) return;
|
|
9705
|
+
if (!supportsQualityEncoding(this.uplinkQualityFormat(state2.baseline, level))) {
|
|
9706
|
+
throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Quality level conflicts with explicit bitrate constraints");
|
|
9707
|
+
}
|
|
9603
9708
|
this.adaptiveQualityRevision++;
|
|
9604
9709
|
controller.resetSamples();
|
|
9605
9710
|
await this.applyUplinkQuality(level, token);
|
|
@@ -10061,7 +10166,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10061
10166
|
const track = this.currentUplinkTrack;
|
|
10062
10167
|
const previous = track?.videoFormat;
|
|
10063
10168
|
const state2 = this.currentState.connectionState;
|
|
10064
|
-
if (!track || !previous || state2 !== "Connected" /* connected */ && state2 !== "Generating" /* generating */) {
|
|
10169
|
+
if (!track || !previous || state2 !== "Connected" /* connected */ && state2 !== "Starting" /* starting */ && state2 !== "Generating" /* generating */) {
|
|
10065
10170
|
throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Frame rate updates require a connected local camera, external or file stream");
|
|
10066
10171
|
}
|
|
10067
10172
|
if (!Number.isFinite(fps) || fps <= 0) {
|
|
@@ -10182,7 +10287,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10182
10287
|
void 0,
|
|
10183
10288
|
async (token) => {
|
|
10184
10289
|
const connectionState = this.coordinator.currentState.connectionState;
|
|
10185
|
-
if (connectionState === "Connecting" /* connecting */ || connectionState === "Connected" /* connected */ || connectionState === "Generating" /* generating */) {
|
|
10290
|
+
if (connectionState === "Connecting" /* connecting */ || connectionState === "Connected" /* connected */ || connectionState === "Starting" /* starting */ || connectionState === "Generating" /* generating */) {
|
|
10186
10291
|
throw new XmaxError(
|
|
10187
10292
|
"INVALID_CONFIGURATION" /* invalidConfiguration */,
|
|
10188
10293
|
"A realtime connection is already active"
|
|
@@ -10197,6 +10302,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10197
10302
|
*
|
|
10198
10303
|
* 尚未连接时先建立实时连接;已在生成时默认更新条件,restart 为 true 时重新开始生成。
|
|
10199
10304
|
* 首次生成必须提供条件上下文,之后缺省时复用最近一次缓存的上下文。
|
|
10305
|
+
* 返回后请将远端流挂载到 SDK 视频视图;首帧及渲染预热完成后状态才进入 generating。
|
|
10200
10306
|
*
|
|
10201
10307
|
* @param options.localStream 由当前 Manager 创建的摄像头、外部视频轨、本地文件或网络视频源。
|
|
10202
10308
|
* @param options.context 本次生成使用的条件上下文;缺省时复用缓存。
|
|
@@ -10204,7 +10310,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10204
10310
|
* @param options.faceRestoration 是否启用原始人脸回贴,默认 false;正在生成时通过 change_condition 更新。
|
|
10205
10311
|
* @param options.extraParams 合并到本次 start 或 change_condition 的 params;不缓存,自动恢复时不携带,不允许覆盖 SDK 字段。
|
|
10206
10312
|
* @param options.record 是否录制远端原始音视频约 20 秒,默认 false;仅 start 有效,条件更新时忽略,重启录制暂不支持。
|
|
10207
|
-
* @returns
|
|
10313
|
+
* @returns 可挂载的远端媒体流,不等待渲染预热;展示就绪以 generating 状态为准。
|
|
10208
10314
|
* @throws extraParams 包含非 JSON 数据、循环引用或 SDK 保留字段时,在连接或更新前抛出 invalidConfiguration。
|
|
10209
10315
|
* @throws 本地流不属于当前 Manager、缺少可用的条件上下文、信令发送
|
|
10210
10316
|
* 或生成确认失败时抛出错误;失败时自动停止生成任务并释放连接资源。
|
|
@@ -10230,8 +10336,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10230
10336
|
);
|
|
10231
10337
|
}
|
|
10232
10338
|
const currentState = this.coordinator.currentState;
|
|
10233
|
-
const
|
|
10234
|
-
if (
|
|
10339
|
+
const hasGeneration = currentState.connectionState === "Generating" /* generating */ || currentState.connectionState === "Starting" /* starting */ && !!currentState.taskID;
|
|
10340
|
+
if (hasGeneration && options.restart !== true) {
|
|
10235
10341
|
await this.generationManager.update(currentState.taskID, {
|
|
10236
10342
|
videoFormat,
|
|
10237
10343
|
context: options.context,
|
|
@@ -10248,7 +10354,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10248
10354
|
throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Create a network video stream before using a reference video");
|
|
10249
10355
|
}
|
|
10250
10356
|
const context = referenceVideo ? new RealtimeContext({ ...supplied, referenceVideo }) : supplied;
|
|
10251
|
-
if (currentState.connectionState !== "Connected" /* connected */ && !
|
|
10357
|
+
if (currentState.connectionState !== "Connected" /* connected */ && !hasGeneration) {
|
|
10252
10358
|
await this.performConnect(options.localStream, token);
|
|
10253
10359
|
}
|
|
10254
10360
|
return this.performStartGeneration(
|
|
@@ -10300,7 +10406,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10300
10406
|
if (this.qualityStates.has(track)) return void 0;
|
|
10301
10407
|
const baseline = track.videoFormat;
|
|
10302
10408
|
if (!baseline) return void 0;
|
|
10303
|
-
|
|
10409
|
+
let level = this.qualityHistory.read("uplink");
|
|
10410
|
+
while (level > 1 && !supportsQualityEncoding(this.uplinkQualityFormat(baseline, level))) {
|
|
10411
|
+
level--;
|
|
10412
|
+
}
|
|
10413
|
+
return { baseline, level };
|
|
10304
10414
|
}
|
|
10305
10415
|
/**
|
|
10306
10416
|
* 建立实时连接:创建会话、进房发布、启动心跳并准备远端渲染轨道。
|
|
@@ -10386,6 +10496,15 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10386
10496
|
*/
|
|
10387
10497
|
async performStartGeneration(token, videoFormat, context, restart, faceRestoration, extraParams, record2 = false) {
|
|
10388
10498
|
token.setFailureScope(0 /* connection */);
|
|
10499
|
+
const sessionID = this.connectionManager.currentSessionID;
|
|
10500
|
+
if (!sessionID) {
|
|
10501
|
+
throw new XmaxError("SESSION_ERROR" /* sessionError */, "Realtime session is unavailable");
|
|
10502
|
+
}
|
|
10503
|
+
await this.coordinator.commit(
|
|
10504
|
+
new RealtimeState({ connectionState: "Starting" /* starting */, sessionID }),
|
|
10505
|
+
token
|
|
10506
|
+
);
|
|
10507
|
+
token.ensureCurrent();
|
|
10389
10508
|
if (restart) {
|
|
10390
10509
|
void this.recordingController.stop("restarted" /* restarted */);
|
|
10391
10510
|
}
|
|
@@ -10393,9 +10512,14 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10393
10512
|
const targetSize = this.downlinkQualityController ? qualityVideoSize(videoFormat, initialLevel) : void 0;
|
|
10394
10513
|
await this.prepareFrameInterpolation(targetSize ?? videoFormat, token);
|
|
10395
10514
|
const completeFirstFrame = this.launchTimer.startFirstFrame();
|
|
10515
|
+
this.remoteRenderWarmupCompletedHandler = void 0;
|
|
10396
10516
|
this.remoteFrameDisplayHandler = () => {
|
|
10397
|
-
if (!token.signal.aborted)
|
|
10517
|
+
if (!token.signal.aborted) {
|
|
10518
|
+
completeFirstFrame();
|
|
10519
|
+
this.remoteRenderWarmupCompletedHandler ?? (this.remoteRenderWarmupCompletedHandler = this.launchTimer.startRenderWarmup());
|
|
10520
|
+
}
|
|
10398
10521
|
};
|
|
10522
|
+
this.renderController.prepareForGeneration();
|
|
10399
10523
|
const taskID = await this.generationManager.start({
|
|
10400
10524
|
videoFormat,
|
|
10401
10525
|
signal: token.signal,
|
|
@@ -10426,10 +10550,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10426
10550
|
} : void 0,
|
|
10427
10551
|
extraParams
|
|
10428
10552
|
});
|
|
10429
|
-
|
|
10430
|
-
if (!sessionID) {
|
|
10431
|
-
throw new XmaxError("SESSION_ERROR" /* sessionError */, "Realtime session is unavailable");
|
|
10432
|
-
}
|
|
10553
|
+
token.ensureCurrent();
|
|
10433
10554
|
this.downlinkQualityController?.stop();
|
|
10434
10555
|
this.downlinkQualityState = {
|
|
10435
10556
|
taskID,
|
|
@@ -10438,14 +10559,17 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10438
10559
|
size: targetSize ?? { width: videoFormat.width, height: videoFormat.height }
|
|
10439
10560
|
};
|
|
10440
10561
|
await this.coordinator.commit(
|
|
10441
|
-
new RealtimeState({ connectionState: "
|
|
10562
|
+
new RealtimeState({ connectionState: "Starting" /* starting */, sessionID, taskID }),
|
|
10442
10563
|
token
|
|
10443
10564
|
);
|
|
10565
|
+
token.ensureCurrent();
|
|
10566
|
+
await this.completeGenerationPresentation();
|
|
10567
|
+
token.ensureCurrent();
|
|
10444
10568
|
this.qualityHistory?.write("downlink", initialLevel);
|
|
10445
10569
|
const onFinish = this.mediaController.onNetworkVideoFinish;
|
|
10446
10570
|
if (this.mediaController.source === "networkVideo") {
|
|
10447
10571
|
this.streamController.activateNetworkVideoCompletion(() => {
|
|
10448
|
-
if (this.currentState.taskID === taskID && this.currentState.connectionState === "Generating" /* generating */) {
|
|
10572
|
+
if (this.currentState.taskID === taskID && (this.currentState.connectionState === "Starting" /* starting */ || this.currentState.connectionState === "Generating" /* generating */)) {
|
|
10449
10573
|
void this.recordingController.stop("generationEnded" /* generationEnded */);
|
|
10450
10574
|
return onFinish?.();
|
|
10451
10575
|
}
|
|
@@ -10453,6 +10577,20 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10453
10577
|
}
|
|
10454
10578
|
return this.connectionManager.makeRemoteStream();
|
|
10455
10579
|
}
|
|
10580
|
+
/**
|
|
10581
|
+
* 任务启动成功且当前视图完成预热后提交展示就绪状态,不阻塞远端流返回。
|
|
10582
|
+
*/
|
|
10583
|
+
async completeGenerationPresentation() {
|
|
10584
|
+
const { sessionID, taskID } = this.currentState;
|
|
10585
|
+
if (!sessionID || !taskID) {
|
|
10586
|
+
return;
|
|
10587
|
+
}
|
|
10588
|
+
await this.coordinator.remotePreviewDidBecomeReady(
|
|
10589
|
+
sessionID,
|
|
10590
|
+
taskID,
|
|
10591
|
+
() => this.renderController.isReady
|
|
10592
|
+
);
|
|
10593
|
+
}
|
|
10456
10594
|
/**
|
|
10457
10595
|
* 心跳失败或会话失效:结束当前连接生命周期并通过最终状态给出原因。
|
|
10458
10596
|
*/
|
|
@@ -10471,6 +10609,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
|
|
|
10471
10609
|
this.mediaController.pauseLocalVideo();
|
|
10472
10610
|
this.launchTimer.cancel();
|
|
10473
10611
|
this.remoteFrameDisplayHandler = void 0;
|
|
10612
|
+
this.remoteRenderWarmupCompletedHandler = void 0;
|
|
10474
10613
|
this.clearVideoStatistics();
|
|
10475
10614
|
this.interpolationSize = void 0;
|
|
10476
10615
|
this.applyRemoteInterpolation();
|
|
@@ -11013,9 +11152,9 @@ var XmaxClient = class {
|
|
|
11013
11152
|
/**
|
|
11014
11153
|
* 创建媒体处理与能力查询 Service。
|
|
11015
11154
|
*
|
|
11016
|
-
* @param model 媒体输入规则使用的模型,默认为 `x2.
|
|
11155
|
+
* @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
|
|
11017
11156
|
*/
|
|
11018
|
-
createMediaService(model = "x2.
|
|
11157
|
+
createMediaService(model = "x2.1.1-preview-cn" /* x2_1_1_preview_cn */) {
|
|
11019
11158
|
return new MediaService(model);
|
|
11020
11159
|
}
|
|
11021
11160
|
/**
|
|
@@ -11033,7 +11172,7 @@ var RealtimeConfiguration = class {
|
|
|
11033
11172
|
/**
|
|
11034
11173
|
* 创建实时业务配置。
|
|
11035
11174
|
*
|
|
11036
|
-
* @param init.model 实时生成业务使用的模型;未知名称原样透传,默认配置同 x2.1-preview。
|
|
11175
|
+
* @param init.model 实时生成业务使用的模型;未知名称原样透传,默认配置同 x2.1.1-preview-cn。
|
|
11037
11176
|
* @param init.provider RTC 提供方,默认按模型定义选择;必须受模型支持,创建后不可切换。
|
|
11038
11177
|
* @param init.isFrameInterpolationEnabled 是否默认开启远端生成画面的插帧,默认 false。
|
|
11039
11178
|
*/
|
|
@@ -11206,6 +11345,75 @@ var RenderStatisticsController = class {
|
|
|
11206
11345
|
}
|
|
11207
11346
|
};
|
|
11208
11347
|
|
|
11348
|
+
// src/Render/Video/RenderWarmupController.ts
|
|
11349
|
+
var RenderWarmupController = class {
|
|
11350
|
+
/**
|
|
11351
|
+
* 创建视图预热控制器。
|
|
11352
|
+
*/
|
|
11353
|
+
constructor(element) {
|
|
11354
|
+
__publicField(this, "element", element);
|
|
11355
|
+
/**
|
|
11356
|
+
* 预热配置与生命周期
|
|
11357
|
+
*/
|
|
11358
|
+
__publicField(this, "durationMs");
|
|
11359
|
+
__publicField(this, "timer");
|
|
11360
|
+
__publicField(this, "started", false);
|
|
11361
|
+
__publicField(this, "completed", false);
|
|
11362
|
+
}
|
|
11363
|
+
/**
|
|
11364
|
+
* 是否仍在等待首帧或预热完成;期间不允许对不可见画面交互。
|
|
11365
|
+
*/
|
|
11366
|
+
get isPending() {
|
|
11367
|
+
return this.durationMs !== void 0 && !this.completed;
|
|
11368
|
+
}
|
|
11369
|
+
/**
|
|
11370
|
+
* 为新媒体隐藏画面;undefined 表示直接显示,不启用预热。
|
|
11371
|
+
*/
|
|
11372
|
+
prepare(durationMs) {
|
|
11373
|
+
this.reset();
|
|
11374
|
+
this.durationMs = durationMs;
|
|
11375
|
+
if (durationMs === void 0) {
|
|
11376
|
+
return;
|
|
11377
|
+
}
|
|
11378
|
+
this.element.style.transition = "none";
|
|
11379
|
+
this.element.style.opacity = "0";
|
|
11380
|
+
this.element.style.pointerEvents = "none";
|
|
11381
|
+
}
|
|
11382
|
+
/**
|
|
11383
|
+
* 首帧就绪后开始一次性预热,结束时通知并渐入画面。
|
|
11384
|
+
*/
|
|
11385
|
+
start(onCompleted) {
|
|
11386
|
+
if (this.durationMs === void 0 || this.started) {
|
|
11387
|
+
return;
|
|
11388
|
+
}
|
|
11389
|
+
this.started = true;
|
|
11390
|
+
this.timer = setTimeout(() => {
|
|
11391
|
+
this.timer = void 0;
|
|
11392
|
+
this.completed = true;
|
|
11393
|
+
const reduceMotion = globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
11394
|
+
this.element.style.transition = reduceMotion ? "none" : "opacity 300ms ease-in-out";
|
|
11395
|
+
this.element.style.opacity = "1";
|
|
11396
|
+
this.element.style.pointerEvents = "";
|
|
11397
|
+
onCompleted();
|
|
11398
|
+
}, this.durationMs);
|
|
11399
|
+
}
|
|
11400
|
+
/**
|
|
11401
|
+
* 换流或卸载时取消预热和渐入,防止旧任务显示新画面。
|
|
11402
|
+
*/
|
|
11403
|
+
reset() {
|
|
11404
|
+
clearTimeout(this.timer);
|
|
11405
|
+
this.timer = void 0;
|
|
11406
|
+
this.started = false;
|
|
11407
|
+
this.completed = false;
|
|
11408
|
+
if (this.durationMs !== void 0) {
|
|
11409
|
+
this.element.style.transition = "";
|
|
11410
|
+
this.element.style.opacity = "";
|
|
11411
|
+
this.element.style.pointerEvents = "";
|
|
11412
|
+
}
|
|
11413
|
+
this.durationMs = void 0;
|
|
11414
|
+
}
|
|
11415
|
+
};
|
|
11416
|
+
|
|
11209
11417
|
// ../../node_modules/.pnpm/framegen@1.4.0/node_modules/framegen/rt.js
|
|
11210
11418
|
var WG = 8;
|
|
11211
11419
|
var texBgSeq = 0;
|
|
@@ -14120,6 +14328,10 @@ var XmaxVideoView = class {
|
|
|
14120
14328
|
* 呈现统计组件
|
|
14121
14329
|
*/
|
|
14122
14330
|
__publicField(this, "statisticsController");
|
|
14331
|
+
/**
|
|
14332
|
+
* 远端画面预热与渐入组件
|
|
14333
|
+
*/
|
|
14334
|
+
__publicField(this, "warmupController");
|
|
14123
14335
|
/**
|
|
14124
14336
|
* DOM 挂载状态
|
|
14125
14337
|
*/
|
|
@@ -14136,6 +14348,7 @@ var XmaxVideoView = class {
|
|
|
14136
14348
|
this.element.style.position = "relative";
|
|
14137
14349
|
this.element.style.overflow = "hidden";
|
|
14138
14350
|
this.element.style.backgroundColor = "black";
|
|
14351
|
+
this.warmupController = new RenderWarmupController(this.element);
|
|
14139
14352
|
this.videoElement = document.createElement("video");
|
|
14140
14353
|
this.statisticsController = new RenderStatisticsController(this.videoElement);
|
|
14141
14354
|
this.videoElement.autoplay = true;
|
|
@@ -14159,7 +14372,7 @@ var XmaxVideoView = class {
|
|
|
14159
14372
|
*/
|
|
14160
14373
|
set isInteractionEnabled(enabled) {
|
|
14161
14374
|
this.interactionEnabled = enabled;
|
|
14162
|
-
this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, enabled);
|
|
14375
|
+
this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, enabled && !this.warmupController.isPending);
|
|
14163
14376
|
}
|
|
14164
14377
|
/**
|
|
14165
14378
|
* 自定义轨迹视觉效果;undefined 表示使用 SDK 默认效果。
|
|
@@ -14260,6 +14473,7 @@ var XmaxVideoView = class {
|
|
|
14260
14473
|
this.startInterpolation();
|
|
14261
14474
|
this.startRenderStatistics();
|
|
14262
14475
|
if ((this.videoElement.srcObject || this.videoElement.getAttribute("src")) && !this.hasNotifiedFrameDisplay) {
|
|
14476
|
+
this.prepareRenderWarmup();
|
|
14263
14477
|
this.armFrameDisplayNotification();
|
|
14264
14478
|
}
|
|
14265
14479
|
}
|
|
@@ -14272,6 +14486,8 @@ var XmaxVideoView = class {
|
|
|
14272
14486
|
this.statisticsController.stop();
|
|
14273
14487
|
this.stopInterpolation();
|
|
14274
14488
|
this.cancelFrameDisplayNotification();
|
|
14489
|
+
this.warmupController.reset();
|
|
14490
|
+
this.hasNotifiedFrameDisplay = false;
|
|
14275
14491
|
if (this.videoElement.getAttribute("src")) {
|
|
14276
14492
|
this.videoElement.pause();
|
|
14277
14493
|
}
|
|
@@ -14281,6 +14497,7 @@ var XmaxVideoView = class {
|
|
|
14281
14497
|
* 设置网络视频预览;静音播放一次,不捕获或发布媒体轨道。 @internal
|
|
14282
14498
|
*/
|
|
14283
14499
|
setVideoURL(url) {
|
|
14500
|
+
this.warmupController.reset();
|
|
14284
14501
|
this.statisticsController.stop();
|
|
14285
14502
|
this.stopInterpolation();
|
|
14286
14503
|
this.cancelFrameDisplayNotification();
|
|
@@ -14309,13 +14526,27 @@ var XmaxVideoView = class {
|
|
|
14309
14526
|
this.cancelFrameDisplayNotification();
|
|
14310
14527
|
this.hasNotifiedFrameDisplay = false;
|
|
14311
14528
|
this.videoElement.srcObject = stream;
|
|
14529
|
+
this.warmupController.reset();
|
|
14312
14530
|
if (stream) {
|
|
14313
14531
|
this.videoElement.autoplay = true;
|
|
14532
|
+
this.prepareRenderWarmup();
|
|
14314
14533
|
this.armFrameDisplayNotification();
|
|
14315
14534
|
this.startInterpolation();
|
|
14316
14535
|
this.startRenderStatistics();
|
|
14317
14536
|
}
|
|
14318
14537
|
}
|
|
14538
|
+
/**
|
|
14539
|
+
* 新生成任务重新等待首帧和预热,不替换正在播放的媒体流。 @internal
|
|
14540
|
+
*/
|
|
14541
|
+
prepareForGeneration() {
|
|
14542
|
+
this.cancelFrameDisplayNotification();
|
|
14543
|
+
this.warmupController.reset();
|
|
14544
|
+
this.hasNotifiedFrameDisplay = false;
|
|
14545
|
+
this.prepareRenderWarmup();
|
|
14546
|
+
if (!this.detached && this.videoElement.srcObject) {
|
|
14547
|
+
this.armFrameDisplayNotification();
|
|
14548
|
+
}
|
|
14549
|
+
}
|
|
14319
14550
|
/**
|
|
14320
14551
|
* 由远端绑定注入配置;本地预览不走此管线。 @internal
|
|
14321
14552
|
*/
|
|
@@ -14390,6 +14621,7 @@ var XmaxVideoView = class {
|
|
|
14390
14621
|
* 解绑当前轨道并清空渲染内容。
|
|
14391
14622
|
*/
|
|
14392
14623
|
detachCurrentTrack() {
|
|
14624
|
+
this.warmupController.reset();
|
|
14393
14625
|
this.disposeTrajectory();
|
|
14394
14626
|
this.statisticsController.stop();
|
|
14395
14627
|
const track = this.currentTrack;
|
|
@@ -14417,8 +14649,15 @@ var XmaxVideoView = class {
|
|
|
14417
14649
|
}
|
|
14418
14650
|
this.hasNotifiedFrameDisplay = true;
|
|
14419
14651
|
this.cancelFrameDisplayNotification();
|
|
14652
|
+
const notifiedSequence = this.frameNotificationSequence;
|
|
14420
14653
|
binding?.frameDisplayHandler?.();
|
|
14421
14654
|
this.frameDisplayHandler?.();
|
|
14655
|
+
if (notifiedSequence === this.frameNotificationSequence) {
|
|
14656
|
+
this.warmupController.start(() => {
|
|
14657
|
+
this.applyContentMode();
|
|
14658
|
+
binding?.renderWarmupCompletedHandler?.();
|
|
14659
|
+
});
|
|
14660
|
+
}
|
|
14422
14661
|
};
|
|
14423
14662
|
if (typeof this.videoElement.requestVideoFrameCallback === "function") {
|
|
14424
14663
|
this.videoFrameCallbackID = this.videoElement.requestVideoFrameCallback(notify);
|
|
@@ -14446,11 +14685,19 @@ var XmaxVideoView = class {
|
|
|
14446
14685
|
this.onPlaying = void 0;
|
|
14447
14686
|
}
|
|
14448
14687
|
}
|
|
14688
|
+
/**
|
|
14689
|
+
* 仅远端渲染绑定启用预热,本地预览和普通媒体直接显示。
|
|
14690
|
+
*/
|
|
14691
|
+
prepareRenderWarmup() {
|
|
14692
|
+
const binding = this.currentTrack ? VideoRenderRegistry.binding(this.currentTrack) : void 0;
|
|
14693
|
+
this.warmupController.prepare(binding?.renderWarmupMs);
|
|
14694
|
+
this.applyContentMode();
|
|
14695
|
+
}
|
|
14449
14696
|
/**
|
|
14450
14697
|
* 应用显示模式与镜像样式。
|
|
14451
14698
|
*/
|
|
14452
14699
|
applyContentMode() {
|
|
14453
|
-
this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, this.interactionEnabled);
|
|
14700
|
+
this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, this.interactionEnabled && !this.warmupController.isPending);
|
|
14454
14701
|
this.videoElement.style.objectFit = OBJECT_FIT[this.contentMode];
|
|
14455
14702
|
this.videoElement.style.transform = this.mirrored ? "scaleX(-1)" : "";
|
|
14456
14703
|
if (this.interpolationCanvas) {
|
|
@@ -14477,7 +14724,7 @@ var XmaxVideoView = class {
|
|
|
14477
14724
|
}
|
|
14478
14725
|
const overlay = new TrajectoryOverlayView(this.customTrajectoryRenderer ?? new DefaultTrajectoryEffectRenderer());
|
|
14479
14726
|
this.trajectoryOverlay = overlay;
|
|
14480
|
-
overlay.configure(this.contentMode, this.mirrored, this.interactionEnabled);
|
|
14727
|
+
overlay.configure(this.contentMode, this.mirrored, this.interactionEnabled && !this.warmupController.isPending);
|
|
14481
14728
|
this.element.appendChild(overlay.element);
|
|
14482
14729
|
binding.attach(overlay);
|
|
14483
14730
|
}
|
|
@@ -14578,7 +14825,7 @@ var XmaxRealtimeVideoView = class {
|
|
|
14578
14825
|
}
|
|
14579
14826
|
/**
|
|
14580
14827
|
* 当前显示的远端生成视频轨道。
|
|
14581
|
-
*
|
|
14828
|
+
* SDK 远端轨道等待首帧及预热后渐入;置空时立即恢复本地预览。
|
|
14582
14829
|
*/
|
|
14583
14830
|
get remoteTrack() {
|
|
14584
14831
|
return this.currentRemoteTrack;
|
|
@@ -14639,18 +14886,23 @@ var XmaxRealtimeVideoView = class {
|
|
|
14639
14886
|
this.element.remove();
|
|
14640
14887
|
}
|
|
14641
14888
|
/**
|
|
14642
|
-
*
|
|
14889
|
+
* 更新远端轨道绑定;SDK 轨道共用单轨视图的预热,普通轨道等待首帧后渐入。
|
|
14643
14890
|
*/
|
|
14644
14891
|
updateRemoteTrack() {
|
|
14645
14892
|
const remoteTrack = this.currentRemoteTrack;
|
|
14646
14893
|
this.remoteVideoView.frameDisplayHandler = void 0;
|
|
14894
|
+
this.remoteVideoView.track = void 0;
|
|
14647
14895
|
this.remoteVideoView.element.style.transition = "";
|
|
14648
14896
|
this.remoteVideoView.element.style.opacity = "0";
|
|
14649
14897
|
this.remoteVideoView.element.style.visibility = "hidden";
|
|
14650
|
-
this.remoteVideoView.track = void 0;
|
|
14651
14898
|
if (!remoteTrack) {
|
|
14652
14899
|
return;
|
|
14653
14900
|
}
|
|
14901
|
+
if (VideoRenderRegistry.binding(remoteTrack)?.renderWarmupMs !== void 0) {
|
|
14902
|
+
this.remoteVideoView.element.style.visibility = "visible";
|
|
14903
|
+
this.remoteVideoView.track = remoteTrack;
|
|
14904
|
+
return;
|
|
14905
|
+
}
|
|
14654
14906
|
this.remoteVideoView.frameDisplayHandler = () => {
|
|
14655
14907
|
if (this.currentRemoteTrack !== remoteTrack) {
|
|
14656
14908
|
return;
|
|
@@ -14713,4 +14965,4 @@ export {
|
|
|
14713
14965
|
XmaxVideoView,
|
|
14714
14966
|
XmaxRealtimeVideoView
|
|
14715
14967
|
};
|
|
14716
|
-
//# sourceMappingURL=chunk-
|
|
14968
|
+
//# sourceMappingURL=chunk-LOYJFYQT.js.map
|