@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.
@@ -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.9";
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;未指定时由 SDK 计算,0 表示不设最低码率。
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" /* 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
- if (modelDefaults(model) === "x2.0" /* x2_0 */) {
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" /* x2_1_preview */],
703
- ...Object.values(RtcProvider).filter((provider) => !MODEL_RTC_PROVIDERS["x2.1-preview" /* x2_1_preview */].includes(provider))
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.0-trtc`。
692
+ * @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
726
693
  */
727
- constructor(model = "x2.0-trtc" /* x2_0_trtc */) {
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
- await this.rtcManager.joinRoom(toRoomJoinConfiguration(connection));
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 _EncodingController = class _EncodingController {
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 bitrates = _EncodingController.resolveBitrates(
7270
- videoFormat.width * videoFormat.height,
7396
+ const defaults = EncodingBitratePolicy.resolve(
7397
+ videoFormat.width,
7398
+ videoFormat.height,
7271
7399
  videoFormat.fps
7272
7400
  );
7273
- const maximumBitrate = Math.round(bitrates.maximum);
7274
- if (!Number.isFinite(maximumBitrate)) {
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: 768, height: 1440, frameRate: 24 },
8186
- L3: { width: 640, height: 1200, frameRate: 24 },
8187
- L4: { width: 512, height: 960, frameRate: 20 },
8188
- L5: { width: 384, height: 720, frameRate: 16 }
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
- constructor(apply, now = () => performance.now(), policy = {}) {
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) throw new Error("Unsupported quality level");
8361
+ if (!Number.isInteger(level) || !tier) {
8362
+ throw new Error("Unsupported quality level");
8363
+ }
8325
8364
  baseline.validate();
8326
- const scale = tier.width / QUALITY_TIERS.L1.width;
8327
- const even = (value) => Math.max(2, Math.floor(value / 2) * 2);
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 QualityHistory = class {
8342
- constructor(model, provider) {
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
- /** 无记录、损坏、SSR 或浏览器禁止存储时使用默认 L2。 */
8395
+ /**
8396
+ * 无记录、过期、损坏、时钟回退或存储不可用时使用默认 L2。
8397
+ */
8347
8398
  read(direction) {
8348
8399
  try {
8349
- const level = Number(globalThis.localStorage.getItem(this.key(direction)));
8350
- if (this.isValid(level)) return level;
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)) return;
8413
+ if (!this.isValid(level)) {
8414
+ return;
8415
+ }
8358
8416
  try {
8359
- globalThis.localStorage.setItem(this.key(direction), String(level));
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
- startConnection() {
8843
- return this.measure("connectionMs", performance.now());
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 === "firstFrameMs" ? { totalMs: now - this.startedAt } : {}
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
- * 进房结束即记录连接耗时;可选 beforePublish 检查阻塞发布,计入连接耗时;
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 completeConnection = this.dependencies.timing.startConnection();
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
- completeConnection();
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 isGenerating = currentState.connectionState === "Generating" /* generating */;
10234
- if (isGenerating && options.restart !== true) {
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 */ && !isGenerating) {
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
- return { baseline, level: this.qualityHistory.read("uplink") };
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) completeFirstFrame();
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
- const sessionID = this.connectionManager.currentSessionID;
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: "Generating" /* generating */, sessionID, taskID }),
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.0-trtc`。
11155
+ * @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
11017
11156
  */
11018
- createMediaService(model = "x2.0-trtc" /* x2_0_trtc */) {
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-ILZL4W2G.js.map
14968
+ //# sourceMappingURL=chunk-LOYJFYQT.js.map