@xmaxai/web-sdk 1.0.8 → 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.
@@ -1,6 +1,6 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
+ import {
2
+ __publicField
3
+ } from "./chunk-V6TY7KAL.js";
4
4
 
5
5
  // src/Foundation/Runtime/XmaxEnvironment.ts
6
6
  var XmaxEnvironment = /* @__PURE__ */ ((XmaxEnvironment3) => {
@@ -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.8";
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,8 @@ 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";
601
+ RealtimeModel2["x2_1_1_preview_cn"] = "x2.1.1-preview-cn";
602
+ RealtimeModel2["x2_1_1_preview_us"] = "x2.1.1-preview-us";
605
603
  return RealtimeModel2;
606
604
  })(RealtimeModel || {});
607
605
  function isKnownModel(model) {
@@ -611,38 +609,29 @@ function isKnownModel(model) {
611
609
  return Object.values(RealtimeModel).includes(model);
612
610
  }
613
611
  function modelDefaults(model) {
614
- 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 */;
615
613
  }
616
614
  var MODEL_DISPLAY_NAMES = {
617
- ["x2.0" /* x2_0 */]: "X2.0",
618
- ["x2.0-trtc" /* x2_0_trtc */]: "X2.1-preview-trtc",
619
- ["x2.1-preview" /* x2_1_preview */]: "X2.1-preview-agora",
620
- ["x2.1-preview-1014" /* x2_1_preview_1014 */]: "X2.1-preview-1014"
615
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "X2.1.1-preview-cn",
616
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "X2.1.1-preview-us"
621
617
  };
622
618
  var MODEL_BASE_URLS = {
623
- ["x2.1-preview" /* x2_1_preview */]: "https://cloud.xmax.22duck.cn/open/api/v1",
624
- ["x2.1-preview-1014" /* x2_1_preview_1014 */]: "https://dev.xmaxai.com/open/api/v1"
619
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "https://cloud.xmax.22duck.cn/open/api/v1",
620
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "https://api.xmax.ai/open/api/v1"
625
621
  };
626
622
  var RESOLUTION_BUCKETS = {
627
- ["x2.0" /* x2_0 */]: [],
628
- ["x2.0-trtc" /* x2_0_trtc */]: [
623
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: [
629
624
  { width: 1024, height: 1920 },
630
625
  { width: 1920, height: 1024 }
631
626
  ],
632
- ["x2.1-preview" /* x2_1_preview */]: [
633
- { width: 1024, height: 1920 },
634
- { width: 1920, height: 1024 }
635
- ],
636
- ["x2.1-preview-1014" /* x2_1_preview_1014 */]: [
627
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: [
637
628
  { width: 1024, height: 1920 },
638
629
  { width: 1920, height: 1024 }
639
630
  ]
640
631
  };
641
632
  var MAXIMUM_INPUT_PIXELS = {
642
- ["x2.0" /* x2_0 */]: 128e4,
643
- ["x2.0-trtc" /* x2_0_trtc */]: 21e5,
644
- ["x2.1-preview" /* x2_1_preview */]: 21e5,
645
- ["x2.1-preview-1014" /* x2_1_preview_1014 */]: 21e5
633
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: 21e5,
634
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: 21e5
646
635
  };
647
636
  function modelDisplayName(model) {
648
637
  return isKnownModel(model) ? MODEL_DISPLAY_NAMES[model] : model;
@@ -666,23 +655,19 @@ function defaultFrameRate(_model) {
666
655
  return 30;
667
656
  }
668
657
  function defaultCameraVideoFormat(model) {
669
- if (modelDefaults(model) === "x2.0" /* x2_0 */) {
670
- return new RealtimeVideoFormat({ width: 832, height: 1472, fps: 30 });
671
- }
658
+ modelDefaults(model);
672
659
  return new RealtimeVideoFormat({ width: 1024, height: 1920, fps: 30 });
673
660
  }
674
661
  var MODEL_RTC_PROVIDERS = {
675
- ["x2.0" /* x2_0 */]: Object.freeze(["vertc" /* vertc */]),
676
- ["x2.0-trtc" /* x2_0_trtc */]: Object.freeze(["trtc" /* trtc */]),
677
- ["x2.1-preview" /* x2_1_preview */]: Object.freeze(["agora" /* agora */]),
678
- ["x2.1-preview-1014" /* x2_1_preview_1014 */]: Object.freeze(["agora" /* agora */])
662
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: Object.freeze(["agora" /* agora */]),
663
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: Object.freeze(["agora" /* agora */])
679
664
  };
680
665
  function supportedRtcProviders(model) {
681
666
  return isKnownModel(model) ? MODEL_RTC_PROVIDERS[model] : CUSTOM_MODEL_RTC_PROVIDERS;
682
667
  }
683
668
  var CUSTOM_MODEL_RTC_PROVIDERS = Object.freeze([
684
- ...MODEL_RTC_PROVIDERS["x2.1-preview" /* x2_1_preview */],
685
- ...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))
686
671
  ]);
687
672
 
688
673
  // src/Foundation/Media/Video/FrameInterpolationSupport.ts
@@ -704,9 +689,9 @@ var MediaService = class {
704
689
  /**
705
690
  * 创建媒体服务。
706
691
  *
707
- * @param model 媒体输入规则使用的模型,默认为 `x2.0-trtc`。
692
+ * @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
708
693
  */
709
- constructor(model = "x2.0-trtc" /* x2_0_trtc */) {
694
+ constructor(model = "x2.1.1-preview-cn" /* x2_1_1_preview_cn */) {
710
695
  /**
711
696
  * 模型约束
712
697
  */
@@ -2133,6 +2118,12 @@ var _AgoraRtcManager = class _AgoraRtcManager {
2133
2118
  if (volume > 0 && this.requestedAudio.has(String(user.uid))) track.play();
2134
2119
  else track.stop();
2135
2120
  }
2121
+ /**
2122
+ * 获取原始远端音轨,不依赖声网音频播放器是否处于播放状态。
2123
+ */
2124
+ getRemoteAudioTrack(userID) {
2125
+ return this.client?.remoteUsers.find((user) => String(user.uid) === userID)?.audioTrack?.getMediaStreamTrack();
2126
+ }
2136
2127
  /**
2137
2128
  * 按房间顺序发送消息,限制每秒包数与总字节;失败返回给原业务调用。
2138
2129
  */
@@ -3031,6 +3022,15 @@ var TrtcRtcManager = class {
3031
3022
  get supportsRemoteAudioVolumeControl() {
3032
3023
  return canControlTrtcRemoteAudioVolume();
3033
3024
  }
3025
+ /**
3026
+ * 获取原始远端音轨;不支持独立播放音量的平台暂不开放录制。
3027
+ */
3028
+ getRemoteAudioTrack(userID) {
3029
+ if (!canControlTrtcRemoteAudioVolume()) {
3030
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "TRTC recording cannot preserve remote audio while muted on this platform");
3031
+ }
3032
+ return this.requireEngine().getAudioTrack(userID) ?? void 0;
3033
+ }
3034
3034
  /**
3035
3035
  * 设置指定远端用户的音频播放音量。
3036
3036
  *
@@ -3656,6 +3656,12 @@ var VeRtcManager = class {
3656
3656
  this.audioPlayers.set(userID, player);
3657
3657
  this.applyAudioVolume(userID);
3658
3658
  }
3659
+ /**
3660
+ * 获取原始远端音轨,独立于 audio 元素的静音和播放音量。
3661
+ */
3662
+ getRemoteAudioTrack(userID) {
3663
+ return this.requireEngine().getRemoteStreamTrack(userID, this.sdk.StreamIndex.STREAM_INDEX_MAIN, "audio");
3664
+ }
3659
3665
  setRemoteAudioVolume(volume, userID) {
3660
3666
  this.audioVolumes.set(userID, Number.isFinite(volume) ? Math.max(0, Math.min(100, Math.round(volume))) : 0);
3661
3667
  this.applyAudioVolume(userID);
@@ -3919,6 +3925,50 @@ function createRtcManager(provider, environment) {
3919
3925
  }
3920
3926
  }
3921
3927
 
3928
+ // src/Service/Realtime/RealtimeExtraParams.ts
3929
+ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
3930
+ "model",
3931
+ "face_replace",
3932
+ "size",
3933
+ "target_size",
3934
+ "prompt",
3935
+ "ref_image_path",
3936
+ "mirror",
3937
+ "ref_video_path",
3938
+ "sample_method",
3939
+ "min_bitrate",
3940
+ "max_bitrate"
3941
+ ]);
3942
+ function snapshotExtraParams(params) {
3943
+ if (params === void 0) return void 0;
3944
+ const invalid2 = (message) => {
3945
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, message);
3946
+ };
3947
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
3948
+ return invalid2("extraParams must be a JSON object");
3949
+ }
3950
+ for (const key of Object.keys(params)) {
3951
+ if (RESERVED_PARAMS.has(key)) return invalid2(`extraParams conflicts with SDK parameter: ${key}`);
3952
+ }
3953
+ const ancestors = /* @__PURE__ */ new Set();
3954
+ const validate = (value) => {
3955
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
3956
+ if (typeof value === "number" && Number.isFinite(value)) return;
3957
+ if (typeof value !== "object" || !value) return invalid2("extraParams must contain only JSON values");
3958
+ if (ancestors.has(value)) return invalid2("extraParams must not contain circular references");
3959
+ const prototype = Object.getPrototypeOf(value);
3960
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
3961
+ return invalid2("extraParams must contain only JSON objects and arrays");
3962
+ }
3963
+ if (Object.getOwnPropertySymbols(value).length) return invalid2("extraParams must not contain symbol keys");
3964
+ ancestors.add(value);
3965
+ for (const child of Array.isArray(value) ? value : Object.values(value)) validate(child);
3966
+ ancestors.delete(value);
3967
+ };
3968
+ validate(params);
3969
+ return JSON.parse(JSON.stringify(params));
3970
+ }
3971
+
3922
3972
  // src/Foundation/Media/Camera/CameraFrameValidator.ts
3923
3973
  var CAMERA_WARMUP_MS = 200;
3924
3974
  function waitForValidCameraFrame(track, signal) {
@@ -4413,121 +4463,26 @@ var _CameraController = class _CameraController {
4413
4463
  __publicField(_CameraController, "localVideoTrackID", "video0");
4414
4464
  var CameraController = _CameraController;
4415
4465
 
4416
- // src/Service/Realtime/RealtimeReferenceVideo.ts
4417
- var RealtimeVideoSampleMethod = /* @__PURE__ */ ((RealtimeVideoSampleMethod2) => {
4418
- RealtimeVideoSampleMethod2["time"] = "time";
4419
- RealtimeVideoSampleMethod2["fps"] = "fps";
4420
- return RealtimeVideoSampleMethod2;
4421
- })(RealtimeVideoSampleMethod || {});
4422
-
4423
- // src/Media/Video/NetworkVideoController.ts
4424
- var NetworkVideoController = class {
4425
- constructor() {
4426
- /**
4427
- * 当前视频源与完成通知
4428
- */
4429
- __publicField(this, "currentTrack");
4430
- __publicField(this, "reference");
4431
- __publicField(this, "onFinish");
4432
- /**
4433
- * 预览进度与视图资源
4434
- */
4435
- __publicField(this, "position", 0);
4436
- __publicField(this, "ended", false);
4437
- __publicField(this, "bindings", /* @__PURE__ */ new Map());
4438
- }
4439
- /**
4440
- * 创建网络视频源,校验地址、采样方式和模型支持的尺寸;不请求视频下载。
4441
- */
4442
- create(options, mediaService) {
4443
- let url;
4444
- try {
4445
- url = new URL(options.url);
4446
- } catch {
4447
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an absolute HTTP or HTTPS URL");
4448
- }
4449
- if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password) {
4450
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an HTTP or HTTPS URL without embedded credentials");
4451
- }
4452
- const sampleMethod = options.sampleMethod ?? "time" /* time */;
4453
- if (!Object.values(RealtimeVideoSampleMethod).includes(sampleMethod)) {
4454
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Unsupported video sample method");
4455
- }
4456
- if (this.currentTrack) {
4457
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current network video stream before creating another one");
4458
- }
4459
- options.videoFormat.validate();
4460
- const size = mediaService.resolveModelInputSize(options.videoFormat);
4461
- const track = new RealtimeVideoTrack({
4462
- id: "network-video",
4463
- videoFormat: options.videoFormat.resized(size.width, size.height)
4464
- });
4465
- this.currentTrack = track;
4466
- this.reference = Object.freeze({ path: options.url.trim(), sampleMethod });
4467
- this.onFinish = options.onFinish;
4468
- VideoRenderRegistry.register(track, {
4469
- attachHandler: (view) => this.attach(view),
4470
- detachHandler: (view) => this.detach(view)
4471
- });
4472
- return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
4473
- }
4474
- /**
4475
- * 释放全部预览绑定及源信息,不影响其他媒体源。
4476
- */
4477
- stop() {
4478
- for (const view of this.bindings.keys()) {
4479
- this.detach(view);
4480
- }
4481
- if (this.currentTrack) {
4482
- VideoRenderRegistry.unregister(this.currentTrack);
4483
- }
4484
- this.currentTrack = void 0;
4485
- this.reference = void 0;
4486
- this.onFinish = void 0;
4487
- this.position = 0;
4488
- this.ended = false;
4489
- }
4490
- /**
4491
- * 绑定播放器,恢复预览位置;本地播放错误仅记录,不中断服务端生成。
4492
- */
4493
- attach(view) {
4494
- if (!this.reference || !view.videoElement || !view.setVideoURL) {
4495
- throw new XmaxError("MEDIA_ERROR" /* mediaError */, "This render target does not support network video preview");
4496
- }
4497
- this.detach(view);
4498
- const video = view.videoElement;
4499
- video.autoplay = !this.ended;
4500
- const restore = () => {
4501
- if (this.position > 0) {
4502
- video.currentTime = Math.min(this.position, Math.max(0, video.duration - 1e-3) || this.position);
4503
- }
4504
- if (this.ended) {
4505
- video.pause();
4466
+ // src/Media/LocalMediaVideoFormat.ts
4467
+ function resolveLocalMediaVideoFormat(format, mediaService) {
4468
+ format.validate();
4469
+ const buckets = resolutionBuckets(mediaService.model);
4470
+ let target = buckets.find((size2) => size2.width === format.width && size2.height === format.height);
4471
+ if (!target && buckets.length > 0) {
4472
+ const ratio = format.width / format.height;
4473
+ let bestDistance = Number.POSITIVE_INFINITY;
4474
+ for (const bucket of buckets) {
4475
+ const distance = Math.abs(Math.log(bucket.width / bucket.height / ratio));
4476
+ const tied = Math.abs(distance - bestDistance) < 1e-9;
4477
+ if (distance < bestDistance - 1e-9 || tied && bucket.width >= bucket.height && target && target.width < target.height) {
4478
+ target = bucket;
4479
+ bestDistance = distance;
4506
4480
  }
4507
- };
4508
- const fail = () => {
4509
- XmaxLogger.render.warning(() => "\u7F51\u7EDC\u89C6\u9891\u9884\u89C8\u5931\u8D25 (Network Video Preview Failed)");
4510
- };
4511
- video.addEventListener("loadedmetadata", restore);
4512
- video.addEventListener("error", fail);
4513
- this.bindings.set(view, () => {
4514
- this.position = video.currentTime;
4515
- this.ended = video.ended || this.ended;
4516
- video.removeEventListener("loadedmetadata", restore);
4517
- video.removeEventListener("error", fail);
4518
- view.setVideoURL?.(null);
4519
- });
4520
- view.isMirrored = false;
4521
- view.setVideoURL(this.reference.path);
4522
- }
4523
- /**
4524
- * 保存预览进度并释放该视图的地址和事件监听。
4525
- */
4526
- detach(view) {
4527
- this.bindings.get(view)?.();
4528
- this.bindings.delete(view);
4481
+ }
4529
4482
  }
4530
- };
4483
+ const size = mediaService.resolveModelInputSize(target ?? format);
4484
+ return format.resized(size.width, size.height);
4485
+ }
4531
4486
 
4532
4487
  // src/Media/Video/LocalVideoController.ts
4533
4488
  var LocalVideoController = class {
@@ -4571,7 +4526,7 @@ var LocalVideoController = class {
4571
4526
  if (!(options.file instanceof Blob) || options.file.size === 0) {
4572
4527
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local video requires a non-empty File or Blob");
4573
4528
  }
4574
- const format = this.resolveVideoFormat(options.videoFormat, mediaService);
4529
+ const format = resolveLocalMediaVideoFormat(options.videoFormat, mediaService);
4575
4530
  if (signal.aborted) {
4576
4531
  throw this.cancelled();
4577
4532
  }
@@ -4798,29 +4753,6 @@ var LocalVideoController = class {
4798
4753
  context.fillRect(0, 0, canvas.width, canvas.height);
4799
4754
  context.drawImage(video, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);
4800
4755
  }
4801
- /**
4802
- * 仅为本地文件选择最接近宽高比的模型档位;比例距离相同时优先横屏。
4803
- * 无固定档位时保留公共尺寸解析规则,帧率和显式编码配置不变。
4804
- */
4805
- resolveVideoFormat(format, mediaService) {
4806
- format.validate();
4807
- const buckets = resolutionBuckets(mediaService.model);
4808
- let target = buckets.find((size2) => size2.width === format.width && size2.height === format.height);
4809
- if (!target && buckets.length > 0) {
4810
- const ratio = format.width / format.height;
4811
- let bestDistance = Number.POSITIVE_INFINITY;
4812
- for (const bucket of buckets) {
4813
- const distance = Math.abs(Math.log(bucket.width / bucket.height / ratio));
4814
- const tied = Math.abs(distance - bestDistance) < 1e-9;
4815
- if (distance < bestDistance - 1e-9 || tied && bucket.width >= bucket.height && target && target.width < target.height) {
4816
- target = bucket;
4817
- bestDistance = distance;
4818
- }
4819
- }
4820
- }
4821
- const size = mediaService.resolveModelInputSize(target ?? format);
4822
- return format.resized(size.width, size.height);
4823
- }
4824
4756
  /**
4825
4757
  * 解码或绘制失败时停止媒体推进,并报告一次运行期错误。
4826
4758
  */
@@ -4842,61 +4774,1299 @@ var LocalVideoController = class {
4842
4774
  }
4843
4775
  };
4844
4776
 
4845
- // src/Media/Video/ExternalVideoController.ts
4846
- var ExternalVideoController = class {
4847
- constructor(onError) {
4848
- __publicField(this, "onError", onError);
4849
- __publicField(this, "track");
4850
- __publicField(this, "onEnded", () => {
4851
- this.onError(new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended"));
4852
- });
4853
- }
4854
- get currentTrack() {
4855
- return this.track;
4777
+ // src/Service/Realtime/RealtimeReferenceVideo.ts
4778
+ var RealtimeVideoSampleMethod = /* @__PURE__ */ ((RealtimeVideoSampleMethod2) => {
4779
+ RealtimeVideoSampleMethod2["time"] = "time";
4780
+ RealtimeVideoSampleMethod2["fps"] = "fps";
4781
+ return RealtimeVideoSampleMethod2;
4782
+ })(RealtimeVideoSampleMethod || {});
4783
+
4784
+ // src/Media/Video/NetworkVideoController.ts
4785
+ var NetworkVideoController = class {
4786
+ constructor() {
4787
+ /**
4788
+ * 当前视频源与完成通知
4789
+ */
4790
+ __publicField(this, "currentTrack");
4791
+ __publicField(this, "reference");
4792
+ __publicField(this, "onFinish");
4793
+ /**
4794
+ * 预览进度与视图资源
4795
+ */
4796
+ __publicField(this, "position", 0);
4797
+ __publicField(this, "ended", false);
4798
+ __publicField(this, "bindings", /* @__PURE__ */ new Map());
4856
4799
  }
4800
+ /**
4801
+ * 创建网络视频源,校验地址、采样方式和模型支持的尺寸;不请求视频下载。
4802
+ */
4857
4803
  create(options, mediaService) {
4858
- if (this.track) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current external video first");
4859
- const { videoTrack, videoFormat } = options;
4860
- if (!videoTrack || videoTrack.kind !== "video" || videoTrack.readyState !== "live" || typeof videoTrack.clone !== "function") {
4861
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video requires a live video MediaStreamTrack");
4804
+ let url;
4805
+ try {
4806
+ url = new URL(options.url);
4807
+ } catch {
4808
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an absolute HTTP or HTTPS URL");
4862
4809
  }
4863
- videoFormat.validate();
4864
- const size = mediaService.resolveModelInputSize(videoFormat);
4865
- const format = videoFormat.resized(size.width, size.height);
4866
- const nativeTrack = videoTrack.clone();
4867
- if (nativeTrack.readyState !== "live") {
4868
- nativeTrack.stop();
4869
- throw new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended during setup");
4810
+ if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password) {
4811
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an HTTP or HTTPS URL without embedded credentials");
4870
4812
  }
4871
- const track = new RealtimeVideoTrack({ id: "external-video", videoFormat: format });
4872
- track.mediaStreamTrack = nativeTrack;
4873
- nativeTrack.addEventListener("ended", this.onEnded);
4874
- this.track = track;
4813
+ const sampleMethod = options.sampleMethod ?? "time" /* time */;
4814
+ if (!Object.values(RealtimeVideoSampleMethod).includes(sampleMethod)) {
4815
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Unsupported video sample method");
4816
+ }
4817
+ if (this.currentTrack) {
4818
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current network video stream before creating another one");
4819
+ }
4820
+ options.videoFormat.validate();
4821
+ const size = mediaService.resolveModelInputSize(options.videoFormat);
4822
+ const track = new RealtimeVideoTrack({
4823
+ id: "network-video",
4824
+ videoFormat: options.videoFormat.resized(size.width, size.height)
4825
+ });
4826
+ this.currentTrack = track;
4827
+ this.reference = Object.freeze({ path: options.url.trim(), sampleMethod });
4828
+ this.onFinish = options.onFinish;
4829
+ VideoRenderRegistry.register(track, {
4830
+ attachHandler: (view) => this.attach(view),
4831
+ detachHandler: (view) => this.detach(view)
4832
+ });
4875
4833
  return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
4876
4834
  }
4835
+ /**
4836
+ * 释放全部预览绑定及源信息,不影响其他媒体源。
4837
+ */
4877
4838
  stop() {
4878
- const nativeTrack = this.track?.mediaStreamTrack;
4879
- this.track = void 0;
4880
- nativeTrack?.removeEventListener("ended", this.onEnded);
4881
- nativeTrack?.stop();
4839
+ for (const view of this.bindings.keys()) {
4840
+ this.detach(view);
4841
+ }
4842
+ if (this.currentTrack) {
4843
+ VideoRenderRegistry.unregister(this.currentTrack);
4844
+ }
4845
+ this.currentTrack = void 0;
4846
+ this.reference = void 0;
4847
+ this.onFinish = void 0;
4848
+ this.position = 0;
4849
+ this.ended = false;
4882
4850
  }
4883
- };
4884
-
4885
- // src/Service/Realtime/RealtimeContext.ts
4886
- var RealtimeContext = class {
4887
4851
  /**
4888
- * 创建实时生成条件并规范化文本和参考路径。
4852
+ * 绑定播放器,恢复预览位置;本地播放错误仅记录,不中断服务端生成。
4889
4853
  */
4890
- constructor(init) {
4891
- /**
4892
- * 服务端直接读取的网络视频。
4893
- */
4894
- __publicField(this, "referenceVideo");
4895
- /**
4896
- * 生成条件
4897
- */
4898
- /**
4899
- * 实时生成使用的文本条件。
4854
+ attach(view) {
4855
+ if (!this.reference || !view.videoElement || !view.setVideoURL) {
4856
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "This render target does not support network video preview");
4857
+ }
4858
+ this.detach(view);
4859
+ const video = view.videoElement;
4860
+ video.autoplay = !this.ended;
4861
+ const restore = () => {
4862
+ if (this.position > 0) {
4863
+ video.currentTime = Math.min(this.position, Math.max(0, video.duration - 1e-3) || this.position);
4864
+ }
4865
+ if (this.ended) {
4866
+ video.pause();
4867
+ }
4868
+ };
4869
+ const fail = () => {
4870
+ XmaxLogger.render.warning(() => "\u7F51\u7EDC\u89C6\u9891\u9884\u89C8\u5931\u8D25 (Network Video Preview Failed)");
4871
+ };
4872
+ video.addEventListener("loadedmetadata", restore);
4873
+ video.addEventListener("error", fail);
4874
+ this.bindings.set(view, () => {
4875
+ this.position = video.currentTime;
4876
+ this.ended = video.ended || this.ended;
4877
+ video.removeEventListener("loadedmetadata", restore);
4878
+ video.removeEventListener("error", fail);
4879
+ view.setVideoURL?.(null);
4880
+ });
4881
+ view.isMirrored = false;
4882
+ view.setVideoURL(this.reference.path);
4883
+ }
4884
+ /**
4885
+ * 保存预览进度并释放该视图的地址和事件监听。
4886
+ */
4887
+ detach(view) {
4888
+ this.bindings.get(view)?.();
4889
+ this.bindings.delete(view);
4890
+ }
4891
+ };
4892
+
4893
+ // src/Media/Video/ExternalVideoController.ts
4894
+ var ExternalVideoController = class {
4895
+ constructor(onError) {
4896
+ __publicField(this, "onError", onError);
4897
+ __publicField(this, "track");
4898
+ __publicField(this, "onEnded", () => {
4899
+ this.onError(new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended"));
4900
+ });
4901
+ }
4902
+ get currentTrack() {
4903
+ return this.track;
4904
+ }
4905
+ create(options, mediaService) {
4906
+ if (this.track) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current external video first");
4907
+ const { videoTrack, videoFormat } = options;
4908
+ if (!videoTrack || videoTrack.kind !== "video" || videoTrack.readyState !== "live" || typeof videoTrack.clone !== "function") {
4909
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video requires a live video MediaStreamTrack");
4910
+ }
4911
+ videoFormat.validate();
4912
+ const size = mediaService.resolveModelInputSize(videoFormat);
4913
+ const format = videoFormat.resized(size.width, size.height);
4914
+ const nativeTrack = videoTrack.clone();
4915
+ if (nativeTrack.readyState !== "live") {
4916
+ nativeTrack.stop();
4917
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended during setup");
4918
+ }
4919
+ const track = new RealtimeVideoTrack({ id: "external-video", videoFormat: format });
4920
+ track.mediaStreamTrack = nativeTrack;
4921
+ nativeTrack.addEventListener("ended", this.onEnded);
4922
+ this.track = track;
4923
+ return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
4924
+ }
4925
+ stop() {
4926
+ const nativeTrack = this.track?.mediaStreamTrack;
4927
+ this.track = void 0;
4928
+ nativeTrack?.removeEventListener("ended", this.onEnded);
4929
+ nativeTrack?.stop();
4930
+ }
4931
+ };
4932
+
4933
+ // src/Media/Image/LocalImageController.ts
4934
+ var LocalImageController = class {
4935
+ /**
4936
+ * 注入运行期错误通知,不创建浏览器资源。
4937
+ */
4938
+ constructor(onError) {
4939
+ __publicField(this, "onError", onError);
4940
+ /**
4941
+ * 当前图片源,包含尚未完成的解码操作。
4942
+ */
4943
+ __publicField(this, "source");
4944
+ }
4945
+ /**
4946
+ * 是否持有正在准备或可用的图片源。
4947
+ */
4948
+ get isActive() {
4949
+ return this.source !== void 0;
4950
+ }
4951
+ /**
4952
+ * 已准备的本地图片视频轨。
4953
+ */
4954
+ get currentTrack() {
4955
+ return this.source?.track;
4956
+ }
4957
+ /**
4958
+ * 解码图片、准备静止画面并持续输出帧;取消或失败时释放本次资源。
4959
+ */
4960
+ async create(options, mediaService, signal) {
4961
+ if (this.source) {
4962
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local image before creating another one");
4963
+ }
4964
+ if (!(options.file instanceof Blob) || options.file.size === 0) {
4965
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local image requires a non-empty File or Blob");
4966
+ }
4967
+ const format = resolveLocalMediaVideoFormat(options.videoFormat, mediaService);
4968
+ if (signal.aborted) {
4969
+ throw this.cancelled();
4970
+ }
4971
+ if (typeof document === "undefined") {
4972
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Local image requires a browser");
4973
+ }
4974
+ const image = document.createElement("img");
4975
+ const canvas = document.createElement("canvas");
4976
+ const context = canvas.getContext("2d");
4977
+ if (!context || typeof canvas.captureStream !== "function") {
4978
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Canvas video capture is unavailable");
4979
+ }
4980
+ canvas.width = format.width;
4981
+ canvas.height = format.height;
4982
+ const source = {
4983
+ image,
4984
+ canvas,
4985
+ url: URL.createObjectURL(options.file),
4986
+ abort: new AbortController(),
4987
+ targets: /* @__PURE__ */ new Set()
4988
+ };
4989
+ this.source = source;
4990
+ try {
4991
+ await this.load(source, signal);
4992
+ if (signal.aborted || this.source !== source) {
4993
+ throw this.cancelled();
4994
+ }
4995
+ if (!image.naturalWidth || !image.naturalHeight) {
4996
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "The image has no valid dimensions");
4997
+ }
4998
+ const scale = Math.min(canvas.width / image.naturalWidth, canvas.height / image.naturalHeight);
4999
+ const width = image.naturalWidth * scale;
5000
+ const height = image.naturalHeight * scale;
5001
+ context.fillStyle = "black";
5002
+ context.fillRect(0, 0, canvas.width, canvas.height);
5003
+ context.drawImage(image, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);
5004
+ source.stream = canvas.captureStream(format.fps);
5005
+ const nativeTrack = source.stream.getVideoTracks()[0];
5006
+ if (!nativeTrack || nativeTrack.readyState !== "live") {
5007
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Canvas did not provide a live video track");
5008
+ }
5009
+ const track = new RealtimeVideoTrack({ id: "local-image", videoFormat: format });
5010
+ track.mediaStreamTrack = nativeTrack;
5011
+ source.track = track;
5012
+ const refresh = () => {
5013
+ context.drawImage(canvas, 0, 0);
5014
+ if (typeof nativeTrack.requestFrame === "function") {
5015
+ nativeTrack.requestFrame();
5016
+ }
5017
+ };
5018
+ refresh();
5019
+ source.timer = setInterval(() => {
5020
+ if (this.source !== source) {
5021
+ return;
5022
+ }
5023
+ try {
5024
+ refresh();
5025
+ } catch {
5026
+ this.handleError(source);
5027
+ }
5028
+ }, 1e3 / format.fps);
5029
+ source.onEnded = () => this.handleError(source);
5030
+ nativeTrack.addEventListener("ended", source.onEnded);
5031
+ VideoRenderRegistry.register(track, {
5032
+ attachHandler: (view) => {
5033
+ if (this.source !== source) {
5034
+ return;
5035
+ }
5036
+ view.isMirrored = false;
5037
+ view.setMediaStream(source.stream);
5038
+ source.targets.add(view);
5039
+ },
5040
+ detachHandler: (view) => {
5041
+ source.targets.delete(view);
5042
+ view.setMediaStream(null);
5043
+ }
5044
+ });
5045
+ return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
5046
+ } catch (error) {
5047
+ if (this.source === source) {
5048
+ this.stop();
5049
+ }
5050
+ throw XmaxError.from(error);
5051
+ }
5052
+ }
5053
+ /**
5054
+ * 取消解码、清空预览并释放视频轨、定时器和对象 URL。
5055
+ */
5056
+ stop() {
5057
+ const source = this.source;
5058
+ this.source = void 0;
5059
+ if (!source) {
5060
+ return;
5061
+ }
5062
+ source.abort.abort();
5063
+ clearInterval(source.timer);
5064
+ if (source.track) {
5065
+ VideoRenderRegistry.unregister(source.track);
5066
+ if (source.onEnded) {
5067
+ source.track.mediaStreamTrack?.removeEventListener("ended", source.onEnded);
5068
+ }
5069
+ source.track.mediaStreamTrack = void 0;
5070
+ }
5071
+ for (const view of source.targets) {
5072
+ try {
5073
+ view.setMediaStream(null);
5074
+ } catch {
5075
+ }
5076
+ }
5077
+ source.targets.clear();
5078
+ source.stream?.getTracks().forEach((track) => track.stop());
5079
+ source.image.removeAttribute("src");
5080
+ URL.revokeObjectURL(source.url);
5081
+ source.canvas.width = 0;
5082
+ source.canvas.height = 0;
5083
+ }
5084
+ /**
5085
+ * 等待浏览器完成图片解码,支持主动停止、操作取消和超时。
5086
+ */
5087
+ load(source, signal) {
5088
+ return new Promise((resolve, reject) => {
5089
+ const finish = (error) => {
5090
+ clearTimeout(timer);
5091
+ source.image.removeEventListener("load", loaded);
5092
+ source.image.removeEventListener("error", failed);
5093
+ signal.removeEventListener("abort", aborted);
5094
+ source.abort.signal.removeEventListener("abort", aborted);
5095
+ if (error) {
5096
+ reject(error);
5097
+ } else {
5098
+ resolve();
5099
+ }
5100
+ };
5101
+ const loaded = () => finish();
5102
+ const failed = () => finish(new XmaxError("MEDIA_ERROR" /* mediaError */, "The browser cannot decode this image file"));
5103
+ const aborted = () => finish(this.cancelled());
5104
+ const timer = setTimeout(() => finish(new XmaxError("TIMEOUT" /* timeout */, "Local image preparation timed out")), 1e4);
5105
+ source.image.addEventListener("load", loaded, { once: true });
5106
+ source.image.addEventListener("error", failed, { once: true });
5107
+ signal.addEventListener("abort", aborted, { once: true });
5108
+ source.abort.signal.addEventListener("abort", aborted, { once: true });
5109
+ if (signal.aborted || source.abort.signal.aborted) {
5110
+ aborted();
5111
+ } else {
5112
+ source.image.src = source.url;
5113
+ }
5114
+ });
5115
+ }
5116
+ /**
5117
+ * 输出失效时停止刷新并仅报告一次,资源由业务生命周期统一释放。
5118
+ */
5119
+ handleError(source) {
5120
+ if (this.source !== source || source.timer === void 0) {
5121
+ return;
5122
+ }
5123
+ clearInterval(source.timer);
5124
+ source.timer = void 0;
5125
+ this.onError(new XmaxError("MEDIA_ERROR" /* mediaError */, "Local image video output failed"));
5126
+ }
5127
+ /**
5128
+ * 构造统一的图片操作取消错误。
5129
+ */
5130
+ cancelled() {
5131
+ return new XmaxError("CANCELLED" /* cancelled */, "Local image operation was cancelled");
5132
+ }
5133
+ };
5134
+
5135
+ // src/Foundation/Media/Video/VideoContentMode.ts
5136
+ var VideoContentMode = /* @__PURE__ */ ((VideoContentMode2) => {
5137
+ VideoContentMode2["fit"] = "fit";
5138
+ VideoContentMode2["fill"] = "fill";
5139
+ return VideoContentMode2;
5140
+ })(VideoContentMode || {});
5141
+
5142
+ // src/Service/Realtime/RealtimePoint.ts
5143
+ var RealtimePoint = class {
5144
+ /**
5145
+ * 创建模型输入像素坐标点。
5146
+ *
5147
+ * @param init.x 模型输入像素横坐标。
5148
+ * @param init.y 模型输入像素纵坐标。
5149
+ */
5150
+ constructor(init) {
5151
+ /**
5152
+ * 坐标
5153
+ */
5154
+ /**
5155
+ * 模型输入像素横坐标。
5156
+ */
5157
+ __publicField(this, "x");
5158
+ /**
5159
+ * 模型输入像素纵坐标。
5160
+ */
5161
+ __publicField(this, "y");
5162
+ this.x = init.x;
5163
+ this.y = init.y;
5164
+ }
5165
+ };
5166
+
5167
+ // src/Media/Interaction/InteractionCoordinateMapper.ts
5168
+ var InteractionCoordinateMapper = class {
5169
+ /**
5170
+ * 计算居中显示的视频矩形;fill 允许矩形超出视口,fit 留出黑边。
5171
+ */
5172
+ static displayedFrame(viewport, video, mode) {
5173
+ if (![viewport.width, viewport.height, video.width, video.height].every((value) => Number.isFinite(value) && value > 0)) {
5174
+ return void 0;
5175
+ }
5176
+ const scale = (mode === "fill" /* fill */ ? Math.max : Math.min)(viewport.width / video.width, viewport.height / video.height);
5177
+ const width = video.width * scale;
5178
+ const height = video.height * scale;
5179
+ return { x: (viewport.width - width) / 2, y: (viewport.height - height) / 2, width, height };
5180
+ }
5181
+ /**
5182
+ * 忽略黑边和无效点,撤销显示镜像后取整并限制在模型输入范围内。
5183
+ */
5184
+ static map(point, frame, video) {
5185
+ const rect = this.displayedFrame(frame.viewportSize, video, frame.contentMode);
5186
+ if (!rect || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
5187
+ return void 0;
5188
+ }
5189
+ if (frame.contentMode === "fit" /* fit */ && (point.x < rect.x || point.x > rect.x + rect.width || point.y < rect.y || point.y > rect.y + rect.height)) {
5190
+ return void 0;
5191
+ }
5192
+ const x = (point.x - rect.x) / rect.width;
5193
+ const y = (point.y - rect.y) / rect.height;
5194
+ return new RealtimePoint({
5195
+ x: Math.min(video.width - 1, Math.max(0, Math.round((frame.mirrored ? 1 - x : x) * video.width))),
5196
+ y: Math.min(video.height - 1, Math.max(0, Math.round(y * video.height)))
5197
+ });
5198
+ }
5199
+ };
5200
+
5201
+ // src/Media/Interaction/InteractionController.ts
5202
+ var InteractionController = class {
5203
+ /**
5204
+ * 注入发送能力,不持有房间或具体 RTC。
5205
+ */
5206
+ constructor(listener) {
5207
+ __publicField(this, "listener", listener);
5208
+ /**
5209
+ * 活动任务与待发送采样;上下文对象身份隔离任务切换。
5210
+ */
5211
+ __publicField(this, "active");
5212
+ __publicField(this, "pending");
5213
+ __publicField(this, "draining");
5214
+ }
5215
+ /**
5216
+ * 激活或更新任务输入格式,清除旧采样。
5217
+ */
5218
+ startInteraction(taskID, videoFormat) {
5219
+ this.stopInteraction();
5220
+ if (taskID) {
5221
+ this.active = { taskID, videoFormat };
5222
+ }
5223
+ }
5224
+ /**
5225
+ * 立即使交互上下文失效;已提交给 RTC 的消息无法撤回。
5226
+ */
5227
+ stopInteraction() {
5228
+ this.active = void 0;
5229
+ this.pending = void 0;
5230
+ this.draining = void 0;
5231
+ }
5232
+ /**
5233
+ * 同步转换数据,发送忙时用最新采样覆盖旧采样,避免弱网积压。
5234
+ */
5235
+ submitInteraction(frame) {
5236
+ const context = this.active;
5237
+ if (!context) {
5238
+ return;
5239
+ }
5240
+ const points = frame.points.flatMap((point) => {
5241
+ const mapped = InteractionCoordinateMapper.map(point, frame, context.videoFormat);
5242
+ return mapped ? [mapped] : [];
5243
+ });
5244
+ if (!points.length) {
5245
+ return;
5246
+ }
5247
+ this.pending = { context, points };
5248
+ if (this.draining !== context) {
5249
+ void this.drain(context);
5250
+ }
5251
+ }
5252
+ /**
5253
+ * 顺序发送;失败只丢弃当前帧,不终止生成或阻止后续交互。
5254
+ */
5255
+ async drain(context) {
5256
+ this.draining = context;
5257
+ try {
5258
+ while (this.active === context && this.pending?.context === context) {
5259
+ const { points } = this.pending;
5260
+ this.pending = void 0;
5261
+ try {
5262
+ await this.listener(context.taskID, points);
5263
+ } catch {
5264
+ if (context === this.active) {
5265
+ XmaxLogger.interaction.warning(() => "\u53D1\u9001\u4EA4\u4E92\u8F68\u8FF9\u5931\u8D25\uFF0C\u5DF2\u4E22\u5F03\u5F53\u524D\u91C7\u6837\u5E27 (Interaction Sample Dropped)");
5266
+ }
5267
+ }
5268
+ }
5269
+ } finally {
5270
+ if (this.draining === context) {
5271
+ this.draining = void 0;
5272
+ }
5273
+ }
5274
+ }
5275
+ };
5276
+
5277
+ // src/Media/MediaController.ts
5278
+ var MediaController = class {
5279
+ /**
5280
+ * 注入基础能力与错误、交互输出,不自行创建跨层业务组件。
5281
+ */
5282
+ constructor(dependencies) {
5283
+ __publicField(this, "dependencies", dependencies);
5284
+ /**
5285
+ * 本地媒体与交互组件
5286
+ */
5287
+ __publicField(this, "camera");
5288
+ __publicField(this, "video");
5289
+ __publicField(this, "image");
5290
+ __publicField(this, "network", new NetworkVideoController());
5291
+ __publicField(this, "external");
5292
+ __publicField(this, "interaction");
5293
+ this.camera = dependencies.cameraController ?? new CameraController({
5294
+ rtcManager: dependencies.rtcManager,
5295
+ mediaService: dependencies.mediaService,
5296
+ errorListener: dependencies.onCameraError
5297
+ });
5298
+ this.video = new LocalVideoController(dependencies.onVideoError);
5299
+ this.image = new LocalImageController(dependencies.onImageError);
5300
+ this.external = new ExternalVideoController(dependencies.onExternalError);
5301
+ this.interaction = new InteractionController(dependencies.interactionListener);
5302
+ }
5303
+ /**
5304
+ * 当前可发布的轨道。
5305
+ */
5306
+ get uplinkTrack() {
5307
+ return this.external.currentTrack ?? this.video.currentTrack ?? this.image.currentTrack ?? this.camera.currentTrack;
5308
+ }
5309
+ /**
5310
+ * 当前本地源轨道,包含无上行的网络视频。
5311
+ */
5312
+ get currentTrack() {
5313
+ return this.uplinkTrack ?? this.network.currentTrack;
5314
+ }
5315
+ /**
5316
+ * 从实际资源推导来源,不维护另一份媒体状态。
5317
+ */
5318
+ get source() {
5319
+ if (this.external.currentTrack) {
5320
+ return "externalVideo";
5321
+ }
5322
+ if (this.video.isActive) {
5323
+ return "localVideo";
5324
+ }
5325
+ if (this.image.isActive) {
5326
+ return "localImage";
5327
+ }
5328
+ if (this.network.currentTrack) {
5329
+ return "networkVideo";
5330
+ }
5331
+ if (this.camera.currentTrack) {
5332
+ return "camera";
5333
+ }
5334
+ return void 0;
5335
+ }
5336
+ /**
5337
+ * 当前源需要发布的音频能力。
5338
+ */
5339
+ get hasAudio() {
5340
+ return this.video.isActive ? !!this.video.audioTrack : this.source === "camera" && this.camera.useMicrophone;
5341
+ }
5342
+ /**
5343
+ * 非相机源不等待相机预热。
5344
+ */
5345
+ get isFrameValidationEnabled() {
5346
+ return this.source === "camera" && this.camera.isFrameValidationEnabled;
5347
+ }
5348
+ /**
5349
+ * 网络视频的服务端读取参数。
5350
+ */
5351
+ get referenceVideo() {
5352
+ return this.network.reference;
5353
+ }
5354
+ /**
5355
+ * 网络视频完成回调。
5356
+ */
5357
+ get onNetworkVideoFinish() {
5358
+ return this.network.onFinish;
5359
+ }
5360
+ /**
5361
+ * 创建前拒绝覆盖活动源或尚未完成的文件准备。
5362
+ */
5363
+ ensureNoLocalSource() {
5364
+ if (this.currentTrack || this.video.isActive || this.image.isActive) {
5365
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local stream before creating another one");
5366
+ }
5367
+ }
5368
+ /**
5369
+ * 保持现有外部轨道能力校验与错误语义。
5370
+ */
5371
+ validateExternalMediaSupport(kind) {
5372
+ if (!this.dependencies.rtcManager.setExternalMediaTracks) {
5373
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, `The RTC provider does not support ${kind} video streams`);
5374
+ }
5375
+ }
5376
+ /**
5377
+ * 创建相机流,由相机组件管理 RTC 和预览。
5378
+ */
5379
+ createLocalCameraStream(options) {
5380
+ this.ensureNoLocalSource();
5381
+ return this.camera.createLocalCameraStream(options);
5382
+ }
5383
+ /**
5384
+ * 同步准备网络视频源;地址校验失败不初始化 RTC。
5385
+ */
5386
+ createNetworkVideoStream(options) {
5387
+ this.ensureNoLocalSource();
5388
+ return this.network.create(options, this.dependencies.mediaService);
5389
+ }
5390
+ /**
5391
+ * 先解锁用户触发的播放,再由 prepareSource 初始化 RTC。
5392
+ */
5393
+ createLocalVideoStream(options, signal) {
5394
+ this.ensureNoLocalSource();
5395
+ return this.video.create(options, this.dependencies.mediaService, signal);
5396
+ }
5397
+ /**
5398
+ * 解码本地图片并准备静止视频轨,不申请设备权限。
5399
+ */
5400
+ createLocalImageStream(options, signal) {
5401
+ this.ensureNoLocalSource();
5402
+ return this.image.create(options, this.dependencies.mediaService, signal);
5403
+ }
5404
+ /**
5405
+ * 接管外部视频轨副本,不停止调用方原始轨道。
5406
+ */
5407
+ createExternalVideoStream(options) {
5408
+ this.ensureNoLocalSource();
5409
+ return this.external.create(options, this.dependencies.mediaService);
5410
+ }
5411
+ /**
5412
+ * 初始化 RTC 并绑定非相机轨道;取消后不继续安装轨道。
5413
+ */
5414
+ async prepareSource(ensureCurrent) {
5415
+ const rtc = this.dependencies.rtcManager;
5416
+ await rtc.initialize();
5417
+ ensureCurrent();
5418
+ const track = this.external.currentTrack ?? this.video.currentTrack ?? this.image.currentTrack;
5419
+ if (track?.mediaStreamTrack) {
5420
+ await rtc.setExternalMediaTracks({ videoTrack: track.mediaStreamTrack, audioTrack: this.video.audioTrack });
5421
+ ensureCurrent();
5422
+ }
5423
+ }
5424
+ /**
5425
+ * 设置相机预览就绪通知。
5426
+ */
5427
+ setCameraPreviewReadyHandler(handler) {
5428
+ this.camera.setPreviewReadyHandler(handler);
5429
+ }
5430
+ /**
5431
+ * 等待相机预热。
5432
+ */
5433
+ waitForValidCameraFrame(signal) {
5434
+ return this.camera.waitForValidCameraFrame(signal);
5435
+ }
5436
+ /**
5437
+ * 切换相机;外部采集不由 SDK 控制。
5438
+ */
5439
+ switchCamera() {
5440
+ if (this.external.currentTrack) {
5441
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video capture is managed by the caller");
5442
+ }
5443
+ return this.camera.switchCamera();
5444
+ }
5445
+ /**
5446
+ * 停止相机资源。
5447
+ */
5448
+ stopLocalCameraStream() {
5449
+ return this.camera.stopLocalCameraStream();
5450
+ }
5451
+ /**
5452
+ * 生成开始后启动文件播放。
5453
+ */
5454
+ startLocalVideo(signal) {
5455
+ return this.video.start(signal);
5456
+ }
5457
+ /**
5458
+ * 断开时保留文件预览资源但暂停时间线。
5459
+ */
5460
+ pauseLocalVideo() {
5461
+ this.video.pause();
5462
+ }
5463
+ /**
5464
+ * 释放当前源,RTC 销毁失败也释放外部轨和解码器。
5465
+ */
5466
+ async stopLocalStream() {
5467
+ this.stopInteraction();
5468
+ const external = this.source !== void 0 && this.source !== "camera";
5469
+ this.network.stop();
5470
+ try {
5471
+ if (external) {
5472
+ await this.dependencies.rtcManager.destroy();
5473
+ } else {
5474
+ await this.camera.stopLocalCameraStream();
5475
+ }
5476
+ } finally {
5477
+ this.external.stop();
5478
+ if (this.image.isActive) {
5479
+ this.image.stop();
5480
+ }
5481
+ if (this.video.isActive) {
5482
+ await this.video.stop();
5483
+ }
5484
+ }
5485
+ }
5486
+ /**
5487
+ * 使用生成格式启用交互,不读取弱网调档后的上行规格。
5488
+ */
5489
+ startInteraction(taskID, format) {
5490
+ this.interaction.startInteraction(taskID, format);
5491
+ }
5492
+ /**
5493
+ * 结束任务时立即清除待发采样。
5494
+ */
5495
+ stopInteraction() {
5496
+ this.interaction.stopInteraction();
5497
+ }
5498
+ /**
5499
+ * 接收渲染层快照并交给坐标与发送队列处理。
5500
+ */
5501
+ submitInteraction(frame) {
5502
+ this.interaction.submitInteraction(frame);
5503
+ }
5504
+ };
5505
+
5506
+ // src/Service/Realtime/RealtimeRecordingEvent.ts
5507
+ var RealtimeRecordingEventType = /* @__PURE__ */ ((RealtimeRecordingEventType2) => {
5508
+ RealtimeRecordingEventType2["completed"] = "completed";
5509
+ RealtimeRecordingEventType2["interrupted"] = "interrupted";
5510
+ RealtimeRecordingEventType2["failed"] = "failed";
5511
+ return RealtimeRecordingEventType2;
5512
+ })(RealtimeRecordingEventType || {});
5513
+ var RealtimeRecordingFinishReason = /* @__PURE__ */ ((RealtimeRecordingFinishReason2) => {
5514
+ RealtimeRecordingFinishReason2["durationLimit"] = "durationLimit";
5515
+ RealtimeRecordingFinishReason2["generationEnded"] = "generationEnded";
5516
+ RealtimeRecordingFinishReason2["restarted"] = "restarted";
5517
+ RealtimeRecordingFinishReason2["mediaInterrupted"] = "mediaInterrupted";
5518
+ return RealtimeRecordingFinishReason2;
5519
+ })(RealtimeRecordingFinishReason || {});
5520
+
5521
+ // src/Media/Recording/RecordingController.ts
5522
+ var _RecordingController = class _RecordingController {
5523
+ constructor() {
5524
+ /**
5525
+ * 任务资源与结果监听
5526
+ */
5527
+ __publicField(this, "session");
5528
+ __publicField(this, "listener");
5529
+ }
5530
+ /**
5531
+ * 设置统一结果监听器;传入 undefined 清除,不停止正在进行的录制。
5532
+ */
5533
+ setListener(listener) {
5534
+ this.listener = listener;
5535
+ }
5536
+ /**
5537
+ * 开始准备当前任务的录制;准备和录制失败只通过独立事件通知。
5538
+ */
5539
+ start(taskID, readTracks) {
5540
+ if (this.session && !this.session.finished) {
5541
+ void this.stop("restarted" /* restarted */);
5542
+ }
5543
+ try {
5544
+ const tracks = readTracks();
5545
+ if (!tracks || tracks.videoTrack.readyState === "ended") {
5546
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Remote video is unavailable for recording");
5547
+ }
5548
+ if (typeof MediaRecorder === "undefined" || typeof AudioContext === "undefined") {
5549
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Audio/video recording is not supported by this browser");
5550
+ }
5551
+ let resolve;
5552
+ const completion = new Promise((done) => {
5553
+ resolve = done;
5554
+ });
5555
+ const session = {
5556
+ taskID,
5557
+ readTracks,
5558
+ videoTrack: tracks.videoTrack,
5559
+ chunks: [],
5560
+ bytes: 0,
5561
+ hasAudio: false,
5562
+ stopping: false,
5563
+ finished: false,
5564
+ reason: "durationLimit" /* durationLimit */,
5565
+ completion,
5566
+ resolve
5567
+ };
5568
+ this.session = session;
5569
+ session.timer = setTimeout(() => {
5570
+ this.fail(session, new XmaxError("TIMEOUT" /* timeout */, "Timed out preparing remote recording"));
5571
+ }, _RecordingController.preparationTimeoutMs);
5572
+ void this.prepare(session).catch((error) => this.fail(session, XmaxError.from(error)));
5573
+ } catch (error) {
5574
+ this.reportFailure(taskID, XmaxError.from(error));
5575
+ }
5576
+ }
5577
+ /**
5578
+ * 请求收尾并等待最终数据;重复停止共享完成结果,且绝不停止 RTC 原始轨道。
5579
+ */
5580
+ stop(reason) {
5581
+ const session = this.session;
5582
+ if (!session) {
5583
+ return Promise.resolve();
5584
+ }
5585
+ this.stopSession(session, reason);
5586
+ return session.completion;
5587
+ }
5588
+ /**
5589
+ * 不支持的录制请求使用同一错误出口,不改变生成生命周期。
5590
+ */
5591
+ reportFailure(taskID, error) {
5592
+ this.emit({ type: "failed" /* failed */, taskID, error });
5593
+ }
5594
+ /**
5595
+ * 使用固定音频输出轨处理晚到的音频;不在 MediaRecorder 运行中增删轨道。
5596
+ */
5597
+ async prepare(session) {
5598
+ const context = new AudioContext();
5599
+ session.audioContext = context;
5600
+ session.audioDestination = context.createMediaStreamDestination();
5601
+ const silence = context.createConstantSource();
5602
+ silence.offset.value = 0;
5603
+ silence.connect(session.audioDestination);
5604
+ silence.start();
5605
+ session.silenceSource = silence;
5606
+ await context.resume();
5607
+ if (session.stopping || session.finished) {
5608
+ return;
5609
+ }
5610
+ if (context.state !== "running") {
5611
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Browser did not allow recording audio to start");
5612
+ }
5613
+ this.updateAudio(session);
5614
+ const video = document.createElement("video");
5615
+ session.video = video;
5616
+ video.muted = true;
5617
+ video.playsInline = true;
5618
+ video.srcObject = new MediaStream([session.videoTrack]);
5619
+ const ready = () => {
5620
+ if (!session.stopping && !session.finished && video.readyState >= 2) {
5621
+ this.beginRecording(session);
5622
+ }
5623
+ };
5624
+ if (typeof video.requestVideoFrameCallback === "function") {
5625
+ session.frameCallback = video.requestVideoFrameCallback(ready);
5626
+ } else {
5627
+ video.onloadeddata = ready;
5628
+ video.onplaying = ready;
5629
+ }
5630
+ video.onerror = () => this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Cannot read remote recording video"));
5631
+ try {
5632
+ await video.play();
5633
+ } catch (error) {
5634
+ if (!session.recorder && !session.stopping && !session.finished) {
5635
+ throw error;
5636
+ }
5637
+ }
5638
+ if (typeof video.requestVideoFrameCallback !== "function") {
5639
+ ready();
5640
+ }
5641
+ }
5642
+ /**
5643
+ * 首帧可用后启动单个音视频录制器,计时不包含连接和媒体准备阶段。
5644
+ */
5645
+ beginRecording(session) {
5646
+ if (session.recorder || session.stopping || session.finished) {
5647
+ return;
5648
+ }
5649
+ try {
5650
+ session.hasAudio = false;
5651
+ this.updateAudio(session);
5652
+ const stream = new MediaStream([session.videoTrack, ...session.audioDestination.stream.getAudioTracks()]);
5653
+ const mimeType = ["video/mp4;codecs=avc1,mp4a.40.2", "video/webm;codecs=vp8,opus", "video/mp4", "video/webm"].find((type) => MediaRecorder.isTypeSupported(type));
5654
+ if (!mimeType) {
5655
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "No supported audio/video recording format");
5656
+ }
5657
+ const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 6e6, audioBitsPerSecond: 128e3 });
5658
+ session.recorder = recorder;
5659
+ recorder.ondataavailable = (event) => {
5660
+ if (session.finished || session.error || !event.data.size) {
5661
+ return;
5662
+ }
5663
+ if (session.bytes + event.data.size > _RecordingController.maximumBytes) {
5664
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording exceeded the memory limit"));
5665
+ return;
5666
+ }
5667
+ session.chunks.push(event.data);
5668
+ session.bytes += event.data.size;
5669
+ };
5670
+ recorder.onerror = () => {
5671
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Remote recording encoder failed"));
5672
+ };
5673
+ recorder.onstop = () => {
5674
+ if (!session.stopping) {
5675
+ session.reason = "mediaInterrupted" /* mediaInterrupted */;
5676
+ }
5677
+ this.finish(session);
5678
+ };
5679
+ recorder.start(1e3);
5680
+ clearTimeout(session.timer);
5681
+ session.timer = setTimeout(() => this.stopSession(session, "durationLimit" /* durationLimit */), _RecordingController.durationMs);
5682
+ session.monitor = setInterval(() => {
5683
+ try {
5684
+ const tracks = session.readTracks();
5685
+ if (!tracks || tracks.videoTrack !== session.videoTrack || session.videoTrack.readyState === "ended") {
5686
+ this.stopSession(session, "mediaInterrupted" /* mediaInterrupted */);
5687
+ } else if (session.audioContext?.state !== "running") {
5688
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording audio context was suspended"));
5689
+ } else {
5690
+ this.updateAudio(session);
5691
+ }
5692
+ } catch (error) {
5693
+ this.fail(session, XmaxError.from(error));
5694
+ }
5695
+ }, 100);
5696
+ this.releaseVideoProbe(session);
5697
+ } catch (error) {
5698
+ this.fail(session, XmaxError.from(error));
5699
+ }
5700
+ }
5701
+ /**
5702
+ * 轮询仅在录制期间运行;接入原始音频,不连接扬声器,不受播放音量影响。
5703
+ */
5704
+ updateAudio(session) {
5705
+ const track = session.readTracks()?.audioTrack;
5706
+ const liveTrack = track?.readyState === "live" ? track : void 0;
5707
+ if (liveTrack !== session.audioTrack) {
5708
+ session.audioSource?.disconnect();
5709
+ session.audioSource = void 0;
5710
+ session.audioTrack = liveTrack;
5711
+ if (liveTrack) {
5712
+ session.audioSource = session.audioContext.createMediaStreamSource(new MediaStream([liveTrack]));
5713
+ session.audioSource.connect(session.audioDestination);
5714
+ }
5715
+ }
5716
+ if (liveTrack && !liveTrack.muted && liveTrack.enabled) {
5717
+ session.hasAudio = true;
5718
+ }
5719
+ }
5720
+ /**
5721
+ * 标记失败并尽力停止编码;任何错误都不能冒泡到实时生成。
5722
+ */
5723
+ fail(session, error) {
5724
+ if (session.finished) {
5725
+ return;
5726
+ }
5727
+ session.error ?? (session.error = error);
5728
+ this.stopSession(session, "mediaInterrupted" /* mediaInterrupted */);
5729
+ }
5730
+ /**
5731
+ * 保留最后一个 dataavailable,等待 stop 后再封装;异常路径有收尾超时保护。
5732
+ */
5733
+ stopSession(session, reason) {
5734
+ if (session.stopping || session.finished) {
5735
+ return;
5736
+ }
5737
+ session.stopping = true;
5738
+ session.reason = reason;
5739
+ clearTimeout(session.timer);
5740
+ clearInterval(session.monitor);
5741
+ this.releaseVideoProbe(session);
5742
+ if (!session.recorder || session.recorder.state === "inactive") {
5743
+ this.finish(session);
5744
+ return;
5745
+ }
5746
+ session.finalizationTimer = setTimeout(() => {
5747
+ session.error ?? (session.error = new XmaxError("TIMEOUT" /* timeout */, "Timed out finalizing remote recording"));
5748
+ this.finish(session);
5749
+ }, _RecordingController.finalizationTimeoutMs);
5750
+ try {
5751
+ session.recorder.stop();
5752
+ } catch (error) {
5753
+ session.error ?? (session.error = XmaxError.from(error));
5754
+ this.finish(session);
5755
+ }
5756
+ }
5757
+ /**
5758
+ * 一次性交付结果并释放所有自有资源,不保留文件副本。
5759
+ */
5760
+ finish(session) {
5761
+ if (session.finished) {
5762
+ return;
5763
+ }
5764
+ session.finished = true;
5765
+ clearTimeout(session.timer);
5766
+ clearTimeout(session.finalizationTimer);
5767
+ clearInterval(session.monitor);
5768
+ this.releaseVideoProbe(session);
5769
+ const blob = new Blob(session.chunks, { type: session.recorder?.mimeType ?? session.chunks[0]?.type });
5770
+ session.chunks = [];
5771
+ session.audioSource?.disconnect();
5772
+ session.silenceSource?.stop();
5773
+ session.silenceSource?.disconnect();
5774
+ session.audioDestination?.stream.getTracks().forEach((track) => track.stop());
5775
+ if (session.audioContext && session.audioContext.state !== "closed") {
5776
+ void session.audioContext.close().catch(() => {
5777
+ });
5778
+ }
5779
+ if (session.recorder) {
5780
+ session.recorder.ondataavailable = null;
5781
+ session.recorder.onerror = null;
5782
+ session.recorder.onstop = null;
5783
+ }
5784
+ if (this.session === session) {
5785
+ this.session = void 0;
5786
+ }
5787
+ if (session.error || !blob.size) {
5788
+ this.reportFailure(session.taskID, session.error ?? new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording ended without usable media"));
5789
+ } else {
5790
+ void this.deliverResult(session.taskID, blob, session.hasAudio, session.reason);
5791
+ }
5792
+ session.resolve();
5793
+ }
5794
+ /**
5795
+ * 按需加载后处理,成功后回调;处理失败仍交付原文件,不丢失已完成的录像。
5796
+ */
5797
+ async deliverResult(taskID, original, hasAudio, reason) {
5798
+ let blob = original;
5799
+ try {
5800
+ const { RecordingProcessor } = await import("./RecordingProcessor-ZSBJFF4J.js");
5801
+ blob = await new RecordingProcessor().process(original);
5802
+ } catch (error) {
5803
+ XmaxLogger.realtime.warning(() => `Recording gap removal failed; keeping original clip: ${XmaxError.from(error).message}`);
5804
+ }
5805
+ this.emit({
5806
+ type: reason === "durationLimit" /* durationLimit */ ? "completed" /* completed */ : "interrupted" /* interrupted */,
5807
+ taskID,
5808
+ result: { blob, hasAudio, reason }
5809
+ });
5810
+ }
5811
+ /**
5812
+ * 释放首帧探测元素;srcObject 原始轨道不调用 stop。
5813
+ */
5814
+ releaseVideoProbe(session) {
5815
+ const video = session.video;
5816
+ if (!video) {
5817
+ return;
5818
+ }
5819
+ if (session.frameCallback !== void 0) {
5820
+ video.cancelVideoFrameCallback(session.frameCallback);
5821
+ }
5822
+ video.onloadeddata = null;
5823
+ video.onplaying = null;
5824
+ video.onerror = null;
5825
+ video.pause();
5826
+ video.srcObject = null;
5827
+ session.video = void 0;
5828
+ }
5829
+ /**
5830
+ * 隔离接入方监听器异常,包括异步监听器的 Promise 拒绝。
5831
+ */
5832
+ emit(event) {
5833
+ try {
5834
+ void Promise.resolve(this.listener?.(event)).catch(() => {
5835
+ XmaxLogger.realtime.warning(() => "Recording listener rejected");
5836
+ });
5837
+ } catch {
5838
+ XmaxLogger.realtime.warning(() => "Recording listener threw an error");
5839
+ }
5840
+ }
5841
+ };
5842
+ /**
5843
+ * 录制资源上限
5844
+ */
5845
+ __publicField(_RecordingController, "durationMs", 2e4);
5846
+ __publicField(_RecordingController, "maximumBytes", 64 * 1024 * 1024);
5847
+ __publicField(_RecordingController, "preparationTimeoutMs", 1e4);
5848
+ __publicField(_RecordingController, "finalizationTimeoutMs", 5e3);
5849
+ var RecordingController = _RecordingController;
5850
+
5851
+ // src/Render/Trajectory/TrajectoryBinding.ts
5852
+ var TrajectoryBinding = class {
5853
+ /**
5854
+ * 注入采样回调及模型输入格式,不引用 Media Controller 实例。
5855
+ */
5856
+ constructor(listener, format) {
5857
+ __publicField(this, "listener", listener);
5858
+ __publicField(this, "format", format);
5859
+ /**
5860
+ * 绑定视图与可用性
5861
+ */
5862
+ __publicField(this, "overlay");
5863
+ __publicField(this, "enabled", false);
5864
+ __publicField(this, "valid", true);
5865
+ }
5866
+ /**
5867
+ * 一个远端绑定只由一个交互视图持有,迁移时取消旧视图的触点。
5868
+ */
5869
+ attach(overlay) {
5870
+ if (!this.valid) {
5871
+ return;
5872
+ }
5873
+ if (this.overlay !== overlay) {
5874
+ this.overlay?.setBinding(void 0);
5875
+ }
5876
+ this.overlay = overlay;
5877
+ overlay.setBinding(this, this.format, this.enabled);
5878
+ }
5879
+ /**
5880
+ * 只解除当前所有者,旧视图迟到的 detach 不影响新视图。
5881
+ */
5882
+ detach(overlay) {
5883
+ if (this.overlay === overlay) {
5884
+ this.overlay = void 0;
5885
+ overlay.setBinding(void 0);
5886
+ }
5887
+ }
5888
+ /**
5889
+ * 开始、更新或结束任务时同步交互能力,并取消旧手势。
5890
+ */
5891
+ update(enabled, format = this.format) {
5892
+ this.enabled = enabled;
5893
+ this.format = format;
5894
+ this.overlay?.setBinding(this, format, enabled);
5895
+ }
5896
+ /**
5897
+ * 转发当前视图采样;注销或停用后忽略。
5898
+ */
5899
+ submit(frame) {
5900
+ if (this.valid && this.enabled) {
5901
+ this.listener(frame);
5902
+ }
5903
+ }
5904
+ /**
5905
+ * 注销绑定并释放视图交互资源,不允许旧引用重新挂载。
5906
+ */
5907
+ invalidate() {
5908
+ this.valid = false;
5909
+ this.overlay?.setBinding(void 0);
5910
+ this.overlay = void 0;
5911
+ }
5912
+ };
5913
+
5914
+ // src/Render/Trajectory/TrajectoryRegistry.ts
5915
+ var TrajectoryRegistry = class {
5916
+ /**
5917
+ * 替换绑定前先使旧绑定失效。
5918
+ */
5919
+ static register(track, binding) {
5920
+ this.unregister(track);
5921
+ this.bindings.set(track, binding);
5922
+ }
5923
+ /**
5924
+ * 查询交互能力,本地轨道不注册该能力。
5925
+ */
5926
+ static binding(track) {
5927
+ return this.bindings.get(track);
5928
+ }
5929
+ /**
5930
+ * 主动清理视图资源,不能仅依赖 WeakMap 垃圾回收。
5931
+ */
5932
+ static unregister(track) {
5933
+ this.bindings.get(track)?.invalidate();
5934
+ this.bindings.delete(track);
5935
+ }
5936
+ };
5937
+ __publicField(TrajectoryRegistry, "bindings", /* @__PURE__ */ new WeakMap());
5938
+
5939
+ // src/Render/RenderController.ts
5940
+ var RenderController = class {
5941
+ /**
5942
+ * 注入显示生命周期通知,不持有 Core 对象。
5943
+ */
5944
+ constructor(callbacks = {}) {
5945
+ __publicField(this, "callbacks", callbacks);
5946
+ /**
5947
+ * 当前轨道与唯一渲染目标
5948
+ */
5949
+ __publicField(this, "track");
5950
+ __publicField(this, "target");
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?.();
5966
+ }
5967
+ /**
5968
+ * 注册新轨道并使旧绑定失效;本地轨道不经过该入口。
5969
+ */
5970
+ registerRemoteTrack(track, listener) {
5971
+ this.resetRemoteTrack();
5972
+ this.track = track;
5973
+ if (track.videoFormat) {
5974
+ TrajectoryRegistry.register(track, new TrajectoryBinding(listener, track.videoFormat));
5975
+ }
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
+ },
5985
+ frameDisplayHandler: () => {
5986
+ if (this.track === track) {
5987
+ this.callbacks.onFrameDisplayed?.();
5988
+ }
5989
+ },
5990
+ attachHandler: (view) => {
5991
+ if (this.track !== track) {
5992
+ return;
5993
+ }
5994
+ this.target?.setFrameInterpolation?.(void 0);
5995
+ this.ready = false;
5996
+ view.isMirrored = false;
5997
+ this.target = view;
5998
+ this.callbacks.onAttached?.();
5999
+ view.setMediaStream(track.mediaStreamTrack ? new MediaStream([track.mediaStreamTrack]) : null);
6000
+ },
6001
+ detachHandler: (view) => {
6002
+ if (this.target === view) {
6003
+ this.ready = false;
6004
+ this.callbacks.onDetached?.();
6005
+ this.target = void 0;
6006
+ }
6007
+ view.setFrameInterpolation?.(void 0);
6008
+ view.setMediaStream(null);
6009
+ }
6010
+ });
6011
+ }
6012
+ /**
6013
+ * 更新媒体画面;暂停显示时同时禁用交互。
6014
+ */
6015
+ setRemoteMedia(track) {
6016
+ this.ready = false;
6017
+ if (this.track) {
6018
+ TrajectoryRegistry.binding(this.track)?.update(!!track && this.interactionEnabled);
6019
+ }
6020
+ this.target?.setMediaStream(track ? new MediaStream([track]) : null);
6021
+ }
6022
+ /**
6023
+ * 转交插帧配置,不重建交互视图。
6024
+ */
6025
+ setFrameInterpolation(options) {
6026
+ this.target?.setFrameInterpolation?.(options);
6027
+ }
6028
+ /**
6029
+ * 更新交互绑定的输入格式和可用性。
6030
+ */
6031
+ setInteractionEnabled(enabled, format) {
6032
+ this.interactionEnabled = enabled;
6033
+ if (this.track) {
6034
+ TrajectoryRegistry.binding(this.track)?.update(enabled, format);
6035
+ }
6036
+ }
6037
+ /**
6038
+ * 先使能力失效再清空画面,迟到首帧和旧手势不能恢复已结束任务。
6039
+ */
6040
+ resetRemoteTrack() {
6041
+ this.ready = false;
6042
+ const track = this.track;
6043
+ this.track = void 0;
6044
+ this.interactionEnabled = false;
6045
+ if (track) {
6046
+ TrajectoryRegistry.unregister(track);
6047
+ VideoRenderRegistry.unregister(track);
6048
+ }
6049
+ this.target?.setFrameInterpolation?.(void 0);
6050
+ this.target?.setMediaStream(null);
6051
+ this.target = void 0;
6052
+ }
6053
+ };
6054
+
6055
+ // src/Service/Realtime/RealtimeContext.ts
6056
+ var RealtimeContext = class {
6057
+ /**
6058
+ * 创建实时生成条件并规范化文本和参考路径。
6059
+ */
6060
+ constructor(init) {
6061
+ /**
6062
+ * 服务端直接读取的网络视频。
6063
+ */
6064
+ __publicField(this, "referenceVideo");
6065
+ /**
6066
+ * 生成条件
6067
+ */
6068
+ /**
6069
+ * 实时生成使用的文本条件。
4900
6070
  */
4901
6071
  __publicField(this, "prompt");
4902
6072
  /**
@@ -5326,6 +6496,7 @@ var RealtimeConnectionState = /* @__PURE__ */ ((RealtimeConnectionState2) => {
5326
6496
  RealtimeConnectionState2["ready"] = "Ready";
5327
6497
  RealtimeConnectionState2["connecting"] = "Connecting";
5328
6498
  RealtimeConnectionState2["connected"] = "Connected";
6499
+ RealtimeConnectionState2["starting"] = "Starting";
5329
6500
  RealtimeConnectionState2["generating"] = "Generating";
5330
6501
  RealtimeConnectionState2["disconnecting"] = "Disconnecting";
5331
6502
  return RealtimeConnectionState2;
@@ -5425,12 +6596,26 @@ var RemoteStream = class {
5425
6596
  var RoomEvent = class _RoomEvent {
5426
6597
  /**
5427
6598
  * 生成开始信令。
6599
+ *
6600
+ * @param options.userID 业务用户标识。
6601
+ * @param options.taskID 当前生成任务标识。
6602
+ * @param options.videoFormat 模型生成使用的视频规格。
6603
+ * @param options.context 本次生成条件。
6604
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
6605
+ * @param options.minimumBitrate 服务端回传的最低码率,单位为 kbps。
6606
+ * @param options.maximumBitrate 服务端回传的最高码率,单位为 kbps。
6607
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
6608
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
6609
+ * @param options.extraParams 已校验的本次扩展参数,合并到 params。
6610
+ * @returns 序列化后的 start 房间消息。
5428
6611
  */
5429
6612
  static start(options) {
5430
6613
  return _RoomEvent.encode({
5431
6614
  event: "start",
5432
6615
  params: {
5433
6616
  ..._RoomEvent.generationParameters(options),
6617
+ min_bitrate: options.minimumBitrate,
6618
+ max_bitrate: options.maximumBitrate,
5434
6619
  mirror: options.mirror ?? false,
5435
6620
  ...options.context.referenceVideo && {
5436
6621
  ref_video_path: options.context.referenceVideo.path,
@@ -5448,6 +6633,15 @@ var RoomEvent = class _RoomEvent {
5448
6633
  }
5449
6634
  /**
5450
6635
  * 生成条件变更信令。
6636
+ *
6637
+ * @param options.userID 业务用户标识。
6638
+ * @param options.taskID 当前生成任务标识。
6639
+ * @param options.videoFormat 模型生成使用的视频规格。
6640
+ * @param options.context 本次生成条件。
6641
+ * @param options.targetSize 回传尺寸,缺省时不发送。
6642
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
6643
+ * @param options.extraParams 已校验的本次扩展参数,合并到 params。
6644
+ * @returns 序列化后的 change_condition 房间消息。
5451
6645
  */
5452
6646
  static changeCondition(options) {
5453
6647
  return _RoomEvent.encode({
@@ -5458,12 +6652,14 @@ var RoomEvent = class _RoomEvent {
5458
6652
  });
5459
6653
  }
5460
6654
  /**
5461
- * 回传尺寸调整信令。
6655
+ * 回传尺寸和码率范围调整信令,码率单位为 kbps。
5462
6656
  */
5463
6657
  static changeTargetSize(options) {
5464
6658
  return _RoomEvent.encode({
5465
6659
  event: "change_target_size",
5466
6660
  params: {
6661
+ min_bitrate: options.minimumBitrate,
6662
+ max_bitrate: options.maximumBitrate,
5467
6663
  target_size: [
5468
6664
  Math.round(options.targetSize.width),
5469
6665
  Math.round(options.targetSize.height)
@@ -5504,7 +6700,10 @@ var RoomEvent = class _RoomEvent {
5504
6700
  });
5505
6701
  }
5506
6702
  /**
5507
- * 生成条件参数:模型、生成尺寸、回传尺寸、文本与参考图路径。
6703
+ * 组装生成条件参数,包含人脸回贴配置及本次扩展参数。
6704
+ *
6705
+ * @param options 本次生成条件类信令的参数。
6706
+ * @returns start 与 change_condition 共用的 params 字段。
5508
6707
  */
5509
6708
  static generationParameters(options) {
5510
6709
  return {
@@ -5516,7 +6715,8 @@ var RoomEvent = class _RoomEvent {
5516
6715
  Math.round(options.targetSize.height)
5517
6716
  ] : void 0,
5518
6717
  prompt: options.context.prompt,
5519
- ref_image_path: options.context.referencePath
6718
+ ref_image_path: options.context.referencePath,
6719
+ ...options.extraParams
5520
6720
  };
5521
6721
  }
5522
6722
  /**
@@ -5534,6 +6734,114 @@ var RoomEvent = class _RoomEvent {
5534
6734
  }
5535
6735
  };
5536
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
+
5537
6845
  // src/Stream/Room/RoomHeartbeat.ts
5538
6846
  var RoomHeartbeat = class {
5539
6847
  /**
@@ -5824,7 +7132,7 @@ var RoomController = class {
5824
7132
  *
5825
7133
  * @throws 已有房间未离开、操作失效或进房失败时抛出错误。
5826
7134
  */
5827
- async join(connection, ensureActive2) {
7135
+ async join(connection, ensureActive2, measureRtcJoin) {
5828
7136
  try {
5829
7137
  ensureActive2();
5830
7138
  } catch (error) {
@@ -5843,11 +7151,14 @@ var RoomController = class {
5843
7151
  if (!this.customCodec) this.codec = new RoomMessageCodec(
5844
7152
  connection.provider === "vertc" /* vertc */ ? null : connection.provider === "agora" /* agora */ ? "__agora_chunk__" : "__trtc_chunk__"
5845
7153
  );
5846
- await this.rtcManager.joinRoom(toRoomJoinConfiguration(connection));
7154
+ const configuration = toRoomJoinConfiguration(connection);
7155
+ const completeRtcJoin = measureRtcJoin?.();
7156
+ await this.rtcManager.joinRoom(configuration);
5847
7157
  ensureActive2();
5848
7158
  if (this.state.kind !== "joining" || this.state.operationID !== operationID || this.leaveOperation) {
5849
7159
  throw new XmaxError("CANCELLED" /* cancelled */, "RTC room join was cancelled");
5850
7160
  }
7161
+ completeRtcJoin?.();
5851
7162
  this.state = { kind: "joined", userID: connection.userID };
5852
7163
  this.heartbeat.start(connection.userID);
5853
7164
  } catch (error) {
@@ -5880,46 +7191,51 @@ var RoomController = class {
5880
7191
  /**
5881
7192
  * 发送生成开始信令。
5882
7193
  *
5883
- * @param options.faceRestoration 是否回贴原始人脸,默认 false。
5884
- * @param options.mirror 是否由后端镜像输入画面,默认 false。
5885
7194
  * @param options.taskID 当前生成任务标识。
5886
7195
  * @param options.videoFormat 模型生成使用的视频规格。
5887
- * @param options.targetSize 服务端生成后的回传尺寸,缺省使用生成尺寸。
5888
7196
  * @param options.context 本次生成条件。
7197
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
7198
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
7199
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7200
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
5889
7201
  * @throws 房间未就绪或信令发送失败时抛出错误。
5890
7202
  */
5891
7203
  startGeneration(options) {
5892
7204
  return this.send(
5893
7205
  RoomEvent.start({
5894
- mirror: options.mirror ?? false,
5895
- faceRestoration: options.faceRestoration ?? false,
5896
7206
  userID: this.requireUserID(),
5897
7207
  taskID: options.taskID,
5898
7208
  videoFormat: options.videoFormat,
7209
+ context: options.context,
5899
7210
  targetSize: options.targetSize,
5900
- context: options.context
7211
+ mirror: options.mirror ?? false,
7212
+ faceRestoration: options.faceRestoration ?? false,
7213
+ extraParams: options.extraParams,
7214
+ ...this.downlinkBitrates(options.targetSize ?? options.videoFormat)
5901
7215
  })
5902
7216
  );
5903
7217
  }
5904
7218
  /**
5905
7219
  * 发送生成条件变更信令。
5906
7220
  *
5907
- * @param options.faceRestoration 是否回贴原始人脸,默认 false。
5908
7221
  * @param options.taskID 当前生成任务标识。
5909
7222
  * @param options.videoFormat 模型生成使用的视频规格。
5910
- * @param options.targetSize 服务端生成后的回传尺寸。
5911
7223
  * @param options.context 本次生成条件。
7224
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
7225
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7226
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
5912
7227
  * @throws 房间未就绪或信令发送失败时抛出错误。
5913
7228
  */
5914
7229
  changeGenerationCondition(options) {
5915
7230
  return this.send(
5916
7231
  RoomEvent.changeCondition({
5917
- faceRestoration: options.faceRestoration ?? false,
5918
7232
  userID: this.requireUserID(),
5919
7233
  taskID: options.taskID,
5920
7234
  videoFormat: options.videoFormat,
7235
+ context: options.context,
5921
7236
  targetSize: options.targetSize,
5922
- context: options.context
7237
+ faceRestoration: options.faceRestoration ?? false,
7238
+ extraParams: options.extraParams
5923
7239
  })
5924
7240
  );
5925
7241
  }
@@ -5934,10 +7250,23 @@ var RoomController = class {
5934
7250
  RoomEvent.changeTargetSize({
5935
7251
  userID: this.requireUserID(),
5936
7252
  taskID: options.taskID,
5937
- targetSize: options.targetSize
7253
+ targetSize: options.targetSize,
7254
+ ...this.downlinkBitrates(options.targetSize)
5938
7255
  })
5939
7256
  );
5940
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
+ }
5941
7270
  /**
5942
7271
  * 尝试发送生成停止信令;未进房或任务标识为空时忽略。
5943
7272
  */
@@ -6039,7 +7368,7 @@ var ENCODER_PREFERENCE_MAP = {
6039
7368
  ["maintainFramerate" /* maintainFramerate */]: "maintainFramerate" /* maintainFramerate */,
6040
7369
  ["maintainQuality" /* maintainQuality */]: "maintainQuality" /* maintainQuality */
6041
7370
  };
6042
- var _EncodingController = class _EncodingController {
7371
+ var EncodingController = class {
6043
7372
  /**
6044
7373
  * 创建编码控制器。
6045
7374
  *
@@ -6064,21 +7393,13 @@ var _EncodingController = class _EncodingController {
6064
7393
  minimum = videoFormat.minimumBitrate;
6065
7394
  maximum = videoFormat.maximumBitrate;
6066
7395
  } else {
6067
- const bitrates = _EncodingController.resolveBitrates(
6068
- videoFormat.width * videoFormat.height,
7396
+ const defaults = EncodingBitratePolicy.resolve(
7397
+ videoFormat.width,
7398
+ videoFormat.height,
6069
7399
  videoFormat.fps
6070
7400
  );
6071
- const maximumBitrate = Math.round(bitrates.maximum);
6072
- if (!Number.isFinite(maximumBitrate)) {
6073
- throw new XmaxError(
6074
- "INVALID_CONFIGURATION" /* invalidConfiguration */,
6075
- "Realtime video format exceeds the supported bitrate range"
6076
- );
6077
- }
6078
- const defaultMinimum = Math.max(1, Math.round(bitrates.minimum));
6079
- const defaultMaximum = Math.max(defaultMinimum + 1, maximumBitrate);
6080
- minimum = videoFormat.minimumBitrate ?? defaultMinimum;
6081
- maximum = videoFormat.maximumBitrate ?? defaultMaximum;
7401
+ minimum = videoFormat.minimumBitrate ?? defaults.minimum;
7402
+ maximum = videoFormat.maximumBitrate ?? defaults.maximum;
6082
7403
  }
6083
7404
  if (minimum > maximum) {
6084
7405
  throw new XmaxError(
@@ -6103,113 +7424,23 @@ var _EncodingController = class _EncodingController {
6103
7424
  if (!previous) {
6104
7425
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Apply an encoder configuration before updating frame rate");
6105
7426
  }
6106
- const format = new RealtimeVideoFormat({
6107
- width: previous.width,
6108
- height: previous.height,
6109
- fps,
6110
- minimumBitrate: previous.minimumBitrate,
6111
- maximumBitrate: previous.maximumBitrate,
6112
- encoderPreference: previous.encoderPreference === "maintainFramerate" /* maintainFramerate */ ? "maintainFramerate" /* maintainFramerate */ : "maintainQuality" /* maintainQuality */
6113
- });
6114
- format.validate();
6115
- if (fps !== previous.frameRate) {
6116
- const next = { ...previous, frameRate: fps };
6117
- await this.rtcManager.configureVideoEncoding(next);
6118
- this.lastAppliedConfiguration = next;
6119
- }
6120
- return format;
6121
- }
6122
- /**
6123
- * 按像素面积与帧率计算码率区间(kbps):流畅优先为最低,画质优先为最高。
6124
- */
6125
- static resolveBitrates(pixels, fps) {
6126
- const bitrate15 = _EncodingController.interpolate(
6127
- pixels,
6128
- _EncodingController.referenceBitratesAt15FPS
6129
- );
6130
- const first30 = _EncodingController.referenceBitratesAt30FPS[0];
6131
- let bitrate30;
6132
- if (pixels < first30.value) {
6133
- const reference15 = _EncodingController.interpolate(
6134
- first30.value,
6135
- _EncodingController.referenceBitratesAt15FPS
6136
- );
6137
- bitrate30 = bitrate15 * (first30.bitrate / reference15);
6138
- } else {
6139
- bitrate30 = _EncodingController.interpolate(
6140
- pixels,
6141
- _EncodingController.referenceBitratesAt30FPS
6142
- );
6143
- }
6144
- const bitrate10 = bitrate15 * (400 / 500);
6145
- const minimum60 = bitrate30 * (4780 / 3150);
6146
- const maximum60 = bitrate30 * (6500 / 3150);
6147
- return {
6148
- minimum: _EncodingController.interpolate(fps, [
6149
- { value: 10, bitrate: bitrate10 },
6150
- { value: 15, bitrate: bitrate15 },
6151
- { value: 30, bitrate: bitrate30 },
6152
- { value: 60, bitrate: minimum60 }
6153
- ]),
6154
- maximum: _EncodingController.interpolate(fps, [
6155
- { value: 10, bitrate: bitrate10 * 2 },
6156
- { value: 15, bitrate: bitrate15 * 2 },
6157
- { value: 30, bitrate: bitrate30 * 2 },
6158
- { value: 60, bitrate: maximum60 }
6159
- ])
6160
- };
6161
- }
6162
- /**
6163
- * 在相邻参考点间线性插值,表外按最近端点的比例外推。
6164
- */
6165
- static interpolate(value, points) {
6166
- const first = points[0];
6167
- if (value <= first.value) {
6168
- return first.bitrate * (value / first.value);
6169
- }
6170
- for (let index = 1; index < points.length; index += 1) {
6171
- const upper = points[index];
6172
- if (value <= upper.value) {
6173
- const lower = points[index - 1];
6174
- const ratio = (value - lower.value) / (upper.value - lower.value);
6175
- return lower.bitrate + (upper.bitrate - lower.bitrate) * ratio;
6176
- }
6177
- }
6178
- const last = points[points.length - 1];
6179
- return last.bitrate * (value / last.value);
7427
+ const format = new RealtimeVideoFormat({
7428
+ width: previous.width,
7429
+ height: previous.height,
7430
+ fps,
7431
+ minimumBitrate: previous.minimumBitrate,
7432
+ maximumBitrate: previous.maximumBitrate,
7433
+ encoderPreference: previous.encoderPreference === "maintainFramerate" /* maintainFramerate */ ? "maintainFramerate" /* maintainFramerate */ : "maintainQuality" /* maintainQuality */
7434
+ });
7435
+ format.validate();
7436
+ if (fps !== previous.frameRate) {
7437
+ const next = { ...previous, frameRate: fps };
7438
+ await this.rtcManager.configureVideoEncoding(next);
7439
+ this.lastAppliedConfiguration = next;
7440
+ }
7441
+ return format;
6180
7442
  }
6181
7443
  };
6182
- /**
6183
- * 流畅优先码率参考,按像素面积升序排列,单位为 kbps。
6184
- */
6185
- __publicField(_EncodingController, "referenceBitratesAt15FPS", [
6186
- { value: 120 * 120, bitrate: 50 },
6187
- { value: 160 * 120, bitrate: 65 },
6188
- { value: 180 * 180, bitrate: 100 },
6189
- { value: 240 * 180, bitrate: 120 },
6190
- { value: 320 * 180, bitrate: 140 },
6191
- { value: 320 * 240, bitrate: 200 },
6192
- { value: 424 * 240, bitrate: 220 },
6193
- { value: 360 * 360, bitrate: 260 },
6194
- { value: 480 * 360, bitrate: 320 },
6195
- { value: 640 * 360, bitrate: 400 },
6196
- { value: 640 * 480, bitrate: 500 },
6197
- { value: 848 * 480, bitrate: 610 },
6198
- { value: 960 * 720, bitrate: 910 },
6199
- { value: 1280 * 720, bitrate: 1130 },
6200
- { value: 1920 * 1080, bitrate: 2080 }
6201
- ]);
6202
- __publicField(_EncodingController, "referenceBitratesAt30FPS", [
6203
- { value: 360 * 360, bitrate: 400 },
6204
- { value: 480 * 360, bitrate: 490 },
6205
- { value: 640 * 360, bitrate: 600 },
6206
- { value: 640 * 480, bitrate: 750 },
6207
- { value: 848 * 480, bitrate: 930 },
6208
- { value: 960 * 720, bitrate: 1380 },
6209
- { value: 1280 * 720, bitrate: 1710 },
6210
- { value: 1920 * 1080, bitrate: 3150 }
6211
- ]);
6212
- var EncodingController = _EncodingController;
6213
7444
 
6214
7445
  // src/Stream/StreamController.ts
6215
7446
  function makeInitialState() {
@@ -6316,6 +7547,17 @@ var StreamController = class {
6316
7547
  get hasGenerationTask() {
6317
7548
  return this.state.generationTaskID !== void 0;
6318
7549
  }
7550
+ /**
7551
+ * 按活动结果流身份读取原始音视频,避免录制房间内其他用户的媒体。
7552
+ */
7553
+ getRemoteRecordingTracks() {
7554
+ const userID = this.state.activeRemoteStream?.userID;
7555
+ const videoTrack = userID && this.state.remoteVideoTracks.get(userID);
7556
+ if (!userID || !videoTrack) {
7557
+ return void 0;
7558
+ }
7559
+ return { videoTrack, audioTrack: this.rtcManager.getRemoteAudioTrack(userID) };
7560
+ }
6319
7561
  /**
6320
7562
  * 设置本地主视频流统计监听器,不改变 RTC 事件监听权。
6321
7563
  */
@@ -6367,9 +7609,9 @@ var StreamController = class {
6367
7609
  *
6368
7610
  * @throws 连接已取消,或 RTC 进房、房间配置与本地流发布失败时抛出错误。
6369
7611
  */
6370
- async connect(connection, includeLocalAudio, ensureActive2, beforePublish, publishLocalMedia = true) {
7612
+ async connect(connection, includeLocalAudio, ensureActive2, beforePublish, publishLocalMedia = true, measureRtcJoin) {
6371
7613
  this.configureRoom({ roomID: connection.roomID, botID: connection.botID });
6372
- await this.roomController.join(connection, ensureActive2);
7614
+ await this.roomController.join(connection, ensureActive2, measureRtcJoin);
6373
7615
  ensureActive2();
6374
7616
  XmaxLogger.stream.info(
6375
7617
  () => `RTC \u623F\u95F4\u5DF2\u914D\u7F6E (RTC Room Configured)
@@ -6397,8 +7639,17 @@ var StreamController = class {
6397
7639
  /**
6398
7640
  * 建立生成任务并发送开始信令。
6399
7641
  *
7642
+ * @param options.taskID 当前生成任务标识。
7643
+ * @param options.videoFormat 模型生成使用的视频规格。
7644
+ * @param options.context 本次生成条件。
7645
+ * @param options.restart 是否保留连接并替换当前生成任务,默认 false;不预先发送 stop。
7646
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
7647
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
7648
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7649
+ * @param options.onStartSent start 发送成功后的回调,不等待生成确认。
7650
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
6400
7651
  * @returns 等待远端结果流确认的 Promise。
6401
- * @throws 任务标识无效、RTC 房间未就绪或已有生成任务时同步抛错;
7652
+ * @throws 任务标识无效、RTC 房间未就绪、旧任务仍在启动或未指定 restart 且已有生成任务时同步抛错;
6402
7653
  * 发送失败或确认超时通过返回的 Promise 拒绝。
6403
7654
  */
6404
7655
  beginGeneration(options) {
@@ -6412,7 +7663,7 @@ var StreamController = class {
6412
7663
  if (!this.state.roomID) {
6413
7664
  throw new XmaxError("RTC_ERROR" /* rtcError */, "RTC room is not configured");
6414
7665
  }
6415
- if (this.state.generationTaskID) {
7666
+ if (this.state.generationTaskID && (!options.restart || this.state.generationWaiter)) {
6416
7667
  throw new XmaxError(
6417
7668
  "RTC_ERROR" /* rtcError */,
6418
7669
  "Realtime generation is already active"
@@ -6447,11 +7698,12 @@ var StreamController = class {
6447
7698
  try {
6448
7699
  const sending = this.roomController.startGeneration({
6449
7700
  taskID,
6450
- mirror: options.mirror ?? false,
6451
- faceRestoration: options.faceRestoration ?? false,
6452
7701
  videoFormat: options.videoFormat,
7702
+ context: options.context,
6453
7703
  targetSize: options.targetSize,
6454
- context: options.context
7704
+ mirror: options.mirror ?? false,
7705
+ faceRestoration: options.faceRestoration ?? false,
7706
+ extraParams: options.extraParams
6455
7707
  });
6456
7708
  const state2 = this.state;
6457
7709
  const sent = Promise.resolve(sending).then(async () => {
@@ -6503,16 +7755,26 @@ var StreamController = class {
6503
7755
  /**
6504
7756
  * 发送生成条件变更信令。
6505
7757
  *
7758
+ * @param options.taskID 当前生成任务标识。
7759
+ * @param options.videoFormat 模型生成使用的视频规格。
7760
+ * @param options.context 本次生成条件。
7761
+ * @param options.restart 本次条件更新不使用该字段。
7762
+ * @param options.targetSize 回传尺寸,缺省时不发送。
7763
+ * @param options.mirror 本次条件更新不使用该字段。
7764
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7765
+ * @param options.onStartSent 本次条件更新不调用该回调。
7766
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
6506
7767
  * @throws RTC 房间未就绪或条件变更信令发送失败时抛出错误。
6507
7768
  */
6508
7769
  async updateGeneration(options) {
6509
7770
  try {
6510
7771
  await this.roomController.changeGenerationCondition({
6511
7772
  taskID: options.taskID,
6512
- faceRestoration: options.faceRestoration ?? false,
6513
7773
  videoFormat: options.videoFormat,
7774
+ context: options.context,
6514
7775
  targetSize: options.targetSize,
6515
- context: options.context
7776
+ faceRestoration: options.faceRestoration ?? false,
7777
+ extraParams: options.extraParams
6516
7778
  });
6517
7779
  } catch (error) {
6518
7780
  throw XmaxError.from(error);
@@ -6947,12 +8209,13 @@ var StreamController = class {
6947
8209
 
6948
8210
  // src/Stream/Quality/QualityTiers.ts
6949
8211
  var INITIAL_QUALITY_LEVEL = 2;
8212
+ var MINIMUM_QUALITY_SHORT_SIDE = 240;
6950
8213
  var QUALITY_TIERS = {
6951
- L1: { width: 1024, height: 1920, frameRate: 30 },
6952
- L2: { width: 768, height: 1440, frameRate: 24 },
6953
- L3: { width: 640, height: 1200, frameRate: 24 },
6954
- L4: { width: 512, height: 960, frameRate: 20 },
6955
- 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 }
6956
8219
  };
6957
8220
 
6958
8221
  // src/Stream/Quality/QualityPolicy.ts
@@ -6968,9 +8231,13 @@ var DEFAULT_POLICY = {
6968
8231
  // src/Stream/Quality/QualityController.ts
6969
8232
  var ORDERED_QUALITY_TIERS = Object.values(QUALITY_TIERS);
6970
8233
  var QualityController = class {
6971
- constructor(apply, now = () => performance.now(), policy = {}) {
8234
+ /**
8235
+ * 创建调档状态机;候选档位不满足编码约束时保持当前档位,不跨档尝试。
8236
+ */
8237
+ constructor(apply, now = () => performance.now(), policy = {}, canApply = () => true) {
6972
8238
  __publicField(this, "apply", apply);
6973
8239
  __publicField(this, "now", now);
8240
+ __publicField(this, "canApply", canApply);
6974
8241
  __publicField(this, "policy");
6975
8242
  __publicField(this, "active", false);
6976
8243
  __publicField(this, "index", 0);
@@ -7060,10 +8327,13 @@ var QualityController = class {
7060
8327
  this.good = this.poor = 0;
7061
8328
  }
7062
8329
  async change(index, reason) {
8330
+ this.resetCounts();
8331
+ if (!this.canApply(index + 1)) {
8332
+ return;
8333
+ }
7063
8334
  const lifecycle = this.lifecycle;
7064
8335
  const change = { from: this.index + 1, to: index + 1, ...ORDERED_QUALITY_TIERS[index], reason };
7065
8336
  this.switching = true;
7066
- this.resetCounts();
7067
8337
  try {
7068
8338
  await this.apply(change, lifecycle.signal);
7069
8339
  if (lifecycle.signal.aborted) return;
@@ -7088,12 +8358,20 @@ var QualityController = class {
7088
8358
  // src/Stream/Quality/QualityVideoFormat.ts
7089
8359
  function qualityVideoSize(baseline, level) {
7090
8360
  const tier = Object.values(QUALITY_TIERS)[level - 1];
7091
- 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
+ }
7092
8364
  baseline.validate();
7093
- const scale = tier.width / QUALITY_TIERS.L1.width;
7094
- 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);
7095
8369
  return { width: even(baseline.width * scale), height: even(baseline.height * scale) };
7096
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
+ }
7097
8375
  function qualityVideoFormat(baseline, level) {
7098
8376
  const size = qualityVideoSize(baseline, level);
7099
8377
  const tier = Object.values(QUALITY_TIERS)[level - 1];
@@ -7105,34 +8383,59 @@ function qualityVideoFormat(baseline, level) {
7105
8383
  }
7106
8384
 
7107
8385
  // src/Stream/Quality/QualityHistory.ts
7108
- var QualityHistory = class {
7109
- constructor(model) {
8386
+ var _QualityHistory = class _QualityHistory {
8387
+ /**
8388
+ * 创建按模型与 RTC 提供方隔离的档位存储。
8389
+ */
8390
+ constructor(model, provider, now = Date.now) {
7110
8391
  __publicField(this, "model", model);
8392
+ __publicField(this, "provider", provider);
8393
+ __publicField(this, "now", now);
7111
8394
  }
7112
- /** 无记录、损坏、SSR 或浏览器禁止存储时使用默认 L2。 */
8395
+ /**
8396
+ * 无记录、过期、损坏、时钟回退或存储不可用时使用默认 L2。
8397
+ */
7113
8398
  read(direction) {
7114
8399
  try {
7115
- const level = Number(globalThis.localStorage.getItem(this.key(direction)));
7116
- 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
+ }
7117
8405
  } catch {
7118
8406
  }
7119
8407
  return INITIAL_QUALITY_LEVEL;
7120
8408
  }
7121
- /** 仅在应用成功后调用;下行成功表示信令已发送,不代表服务端已确认生效。 */
8409
+ /**
8410
+ * 应用成功后保存档位并刷新一小时有效期;下行成功仅表示信令已发送。
8411
+ */
7122
8412
  write(direction, level) {
7123
- if (!this.isValid(level)) return;
8413
+ if (!this.isValid(level)) {
8414
+ return;
8415
+ }
7124
8416
  try {
7125
- globalThis.localStorage.setItem(this.key(direction), String(level));
8417
+ globalThis.localStorage.setItem(this.key(direction), JSON.stringify({ level, savedAt: this.now() }));
7126
8418
  } catch {
7127
8419
  }
7128
8420
  }
8421
+ /**
8422
+ * 生成当前方向的存储键;无时间戳的旧记录按无效记录处理。
8423
+ */
7129
8424
  key(direction) {
7130
- return `xmax:quality:v1:agora:${encodeURIComponent(this.model)}:${direction}`;
8425
+ return `xmax:quality:v1:${this.provider}:${encodeURIComponent(this.model)}:${direction}`;
7131
8426
  }
8427
+ /**
8428
+ * 校验档位编号。
8429
+ */
7132
8430
  isValid(level) {
7133
8431
  return Number.isInteger(level) && level >= 1 && level <= Object.keys(QUALITY_TIERS).length;
7134
8432
  }
7135
8433
  };
8434
+ /**
8435
+ * 历史档位有效期,单位为毫秒。
8436
+ */
8437
+ __publicField(_QualityHistory, "expirationMs", 60 * 60 * 1e3);
8438
+ var QualityHistory = _QualityHistory;
7136
8439
 
7137
8440
  // src/Core/Realtime/RealtimeCoordinator.ts
7138
8441
  function scopeAffects(scope, kind) {
@@ -7270,7 +8573,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
7270
8573
  await this.errorHandler.report(error);
7271
8574
  throw error;
7272
8575
  }
7273
- 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;
7274
8577
  const lease = new OperationLease(failureScope);
7275
8578
  const resultPromise = (async () => body(lease))();
7276
8579
  const operation = {
@@ -7310,6 +8613,19 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
7310
8613
  new RealtimeState({ connectionState: "Ready" /* ready */ })
7311
8614
  );
7312
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
+ }
7313
8629
  /**
7314
8630
  * 断开实时连接;尚未提交连接状态的活跃操作也会被取消。
7315
8631
  */
@@ -7602,11 +8918,16 @@ var RealtimeLaunchTimer = class {
7602
8918
  return this.measure("permissionMs", performance.now());
7603
8919
  }
7604
8920
  /**
7605
- * 开始会话与进房计时,返回连接阶段完成时调用的记录函数。
7606
- * 相机预热等待计入连接耗时。
8921
+ * 开始创建业务会话的计时。
8922
+ */
8923
+ startSession() {
8924
+ return this.measure("sessionMs", performance.now());
8925
+ }
8926
+ /**
8927
+ * 开始 RTC 适配器进房调用的计时。
7607
8928
  */
7608
- startConnection() {
7609
- return this.measure("connectionMs", performance.now());
8929
+ startRtcJoin() {
8930
+ return this.measure("rtcJoinMs", performance.now());
7610
8931
  }
7611
8932
  /**
7612
8933
  * 开始发布本地流计时,返回发布完成时调用的记录函数。
@@ -7615,11 +8936,17 @@ var RealtimeLaunchTimer = class {
7615
8936
  return this.measure("publishMs", performance.now());
7616
8937
  }
7617
8938
  /**
7618
- * 开始等待远端显示首帧;完成回调同时记录首帧耗时和实际总耗时。
8939
+ * 开始等待远端首帧提交合成器,不包含后续渲染预热。
7619
8940
  */
7620
8941
  startFirstFrame() {
7621
8942
  return this.measure("firstFrameMs", performance.now());
7622
8943
  }
8944
+ /**
8945
+ * 首帧就绪后开始渲染预热;结束时记录预热及实际总耗时,不含渐入动画。
8946
+ */
8947
+ startRenderWarmup() {
8948
+ return this.measure("renderWarmupMs", performance.now());
8949
+ }
7623
8950
  /**
7624
8951
  * 冻结本轮结果,令尚未完成的回调失效;下一轮相机启动才重置。
7625
8952
  */
@@ -7640,7 +8967,7 @@ var RealtimeLaunchTimer = class {
7640
8967
  this.snapshot = Object.freeze({
7641
8968
  ...this.snapshot,
7642
8969
  [key]: now - startedAt,
7643
- ...key === "firstFrameMs" ? { totalMs: now - this.startedAt } : {}
8970
+ ...key === "renderWarmupMs" ? { totalMs: now - this.startedAt } : {}
7644
8971
  });
7645
8972
  this.notify();
7646
8973
  };
@@ -7676,7 +9003,6 @@ var XmaxRealtimeConnectionManager = class {
7676
9003
  * 远端轨道与渲染绑定
7677
9004
  */
7678
9005
  __publicField(this, "activeRemoteTrack");
7679
- __publicField(this, "remoteTarget");
7680
9006
  }
7681
9007
  /**
7682
9008
  * 当前活动会话标识;尚未连接或清理完成后为空。
@@ -7692,8 +9018,8 @@ var XmaxRealtimeConnectionManager = class {
7692
9018
  }
7693
9019
  /**
7694
9020
  * 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
7695
- * 进房结束即记录连接耗时;可选 beforePublish 检查阻塞发布,计入连接耗时;
7696
- * 发布本地流单独计时。
9021
+ * 会话创建、RTC 进房和本地流发布分别计时;可选 beforePublish 检查阻塞发布,
9022
+ * 其等待只反映在实际完整启动耗时中。
7697
9023
  * @param options.localTrack 当前本地媒体轨道。
7698
9024
  * @param options.remoteVideoFormat 原始生成及远端画面规格,缺省使用本地轨道规格。
7699
9025
  * @param options.localVideoFormat 发布前实际应用的上行编码格式;与原始生成及远端格式分开。
@@ -7710,10 +9036,11 @@ var XmaxRealtimeConnectionManager = class {
7710
9036
  const sessionService = this.requireSessionService();
7711
9037
  const streamController = this.dependencies.streamController;
7712
9038
  options.ensureCurrent();
7713
- const completeConnection = this.dependencies.timing.startConnection();
9039
+ const completeSession = this.dependencies.timing.startSession();
7714
9040
  const session = await sessionService.createSession(options.model);
7715
9041
  this.activeSession = session;
7716
9042
  options.ensureCurrent();
9043
+ completeSession();
7717
9044
  const connection = session.connection;
7718
9045
  if (!connection) {
7719
9046
  throw new XmaxError(
@@ -7746,11 +9073,10 @@ var XmaxRealtimeConnectionManager = class {
7746
9073
  }, async () => {
7747
9074
  options.ensureCurrent();
7748
9075
  await options.beforePublish?.();
7749
- completeConnection();
9076
+ options.ensureCurrent();
7750
9077
  completePublish = this.dependencies.timing.startPublish();
7751
- }, publishLocalMedia);
9078
+ }, publishLocalMedia, () => this.dependencies.timing.startRtcJoin());
7752
9079
  options.ensureCurrent();
7753
- completeConnection();
7754
9080
  completePublish?.();
7755
9081
  options.onPublished();
7756
9082
  sessionService.startHeartbeat(session.id, {
@@ -7766,21 +9092,15 @@ var XmaxRealtimeConnectionManager = class {
7766
9092
  videoFormat: options.remoteVideoFormat ?? options.localTrack.videoFormat
7767
9093
  });
7768
9094
  this.activeRemoteTrack = remoteTrack;
7769
- this.registerRemoteBinding(remoteTrack);
9095
+ this.dependencies.renderController.registerRemoteTrack(remoteTrack, this.dependencies.onInteraction);
7770
9096
  streamController.setRemoteAudioVolume(this.dependencies.remoteAudioVolume());
7771
9097
  return new RealtimeMediaStream({ id: "stream1" /* remote */, videoTrack: remoteTrack });
7772
9098
  }
7773
- /**
7774
- * 更新已绑定远端视图的插帧配置;传 undefined 停用插帧。
7775
- */
7776
- setFrameInterpolation(options) {
7777
- this.remoteTarget?.setFrameInterpolation?.(options);
7778
- }
7779
9099
  /**
7780
9100
  * 清空远端视图的媒体流,保留轨道与视图绑定以供统一清理。
7781
9101
  */
7782
9102
  clearRemoteMedia() {
7783
- this.remoteTarget?.setMediaStream(null);
9103
+ this.dependencies.renderController.setRemoteMedia(null);
7784
9104
  }
7785
9105
  /**
7786
9106
  * 停止会话心跳;未配置会话服务时不执行操作。
@@ -7860,7 +9180,7 @@ var XmaxRealtimeConnectionManager = class {
7860
9180
  if (track) {
7861
9181
  track.mediaStreamTrack = void 0;
7862
9182
  }
7863
- this.remoteTarget?.setMediaStream(null);
9183
+ this.dependencies.renderController.setRemoteMedia(null);
7864
9184
  return;
7865
9185
  }
7866
9186
  if (!track) {
@@ -7870,35 +9190,7 @@ var XmaxRealtimeConnectionManager = class {
7870
9190
  );
7871
9191
  }
7872
9192
  track.mediaStreamTrack = binding.videoTrack;
7873
- this.remoteTarget?.setMediaStream(new MediaStream([binding.videoTrack]));
7874
- }
7875
- /**
7876
- * 为远端轨道注册渲染绑定:attach 时挂流占位,媒体轨到达后送入画面。
7877
- */
7878
- registerRemoteBinding(track) {
7879
- VideoRenderRegistry.register(track, {
7880
- frameDisplayHandler: () => {
7881
- if (this.activeRemoteTrack === track) {
7882
- this.dependencies.onFrameDisplayed();
7883
- }
7884
- },
7885
- attachHandler: (view) => {
7886
- this.remoteTarget?.setFrameInterpolation?.(void 0);
7887
- view.isMirrored = false;
7888
- this.remoteTarget = view;
7889
- this.dependencies.onRenderAttached();
7890
- const mediaTrack = track.mediaStreamTrack;
7891
- view.setMediaStream(mediaTrack ? new MediaStream([mediaTrack]) : null);
7892
- },
7893
- detachHandler: (view) => {
7894
- if (this.remoteTarget === view) {
7895
- this.dependencies.onRenderDetached();
7896
- this.remoteTarget = void 0;
7897
- }
7898
- view.setFrameInterpolation?.(void 0);
7899
- view.setMediaStream(null);
7900
- }
7901
- });
9193
+ this.dependencies.renderController.setRemoteMedia(binding.videoTrack);
7902
9194
  }
7903
9195
  /**
7904
9196
  * 构造当前远端生成结果媒体流。
@@ -7971,12 +9263,11 @@ var XmaxRealtimeConnectionManager = class {
7971
9263
  }
7972
9264
  const track = this.activeRemoteTrack;
7973
9265
  if (track) {
7974
- VideoRenderRegistry.unregister(track);
9266
+ this.dependencies.renderController.resetRemoteTrack();
7975
9267
  track.mediaStreamTrack = void 0;
7976
9268
  }
7977
9269
  this.activeSession = void 0;
7978
9270
  this.activeRemoteTrack = void 0;
7979
- this.remoteTarget = void 0;
7980
9271
  if (sessionID && this.dependencies.sessionService) {
7981
9272
  try {
7982
9273
  await this.dependencies.sessionService.closeSession(sessionID);
@@ -8006,8 +9297,10 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8006
9297
  /**
8007
9298
  * 注入房间传输组件和任务标识生成器,不创建会话或启动生成。
8008
9299
  */
8009
- constructor(streamController, taskIDGenerator = _XmaxRealtimeGenerationManager.createTaskID) {
9300
+ constructor(streamController, interactionController, renderController, taskIDGenerator = _XmaxRealtimeGenerationManager.createTaskID) {
8010
9301
  __publicField(this, "streamController", streamController);
9302
+ __publicField(this, "interactionController", interactionController);
9303
+ __publicField(this, "renderController", renderController);
8011
9304
  __publicField(this, "taskIDGenerator", taskIDGenerator);
8012
9305
  /**
8013
9306
  * 当前生成条件与任务标识
@@ -8033,37 +9326,50 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8033
9326
  * 发送生成指令并等待确认、远端就绪和音频激活,成功后返回任务标识。
8034
9327
  * 条件上下文在生成确认后缓存;启动失败时尽力停止本次任务并保留原始错误。
8035
9328
  * @param options.videoFormat 模型生成使用的视频规格。
8036
- * @param options.targetSize 服务端生成后的回传尺寸,缺省使用生成尺寸。
8037
- * @param options.context 生成条件,缺省复用最近缓存的上下文。
8038
- * @param options.faceRestoration 是否回贴原始人脸,默认 false。
8039
- * @param options.mirror 是否由后端水平翻转输入,默认 false;仅用于 start 信令。
8040
9329
  * @param options.signal 取消本次生成启动的信号。
8041
9330
  * @param options.ensureCurrent 校验当前操作仍有效。
8042
9331
  * @param options.waitUntilRemoteReady 等待远端生成流就绪。
9332
+ * @param options.context 生成条件,缺省复用最近缓存的上下文。
9333
+ * @param options.restart 是否替换当前任务并重新发送 start,默认 false;不发送 stop。
9334
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
9335
+ * @param options.mirror 是否由后端水平翻转输入,默认 false;仅用于 start 信令。
9336
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
8043
9337
  * @param options.onStartSent start 信令发送成功后的可选异步回调。
9338
+ * @param options.onRemoteReady 远端视频就绪后通知任务标识,不等待录制等附加功能。
9339
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
8044
9340
  */
8045
9341
  async start(options) {
8046
9342
  const context = this.validateContext(options.context);
8047
9343
  options.ensureCurrent();
8048
9344
  const taskID = this.taskIDGenerator();
9345
+ if (options.restart) {
9346
+ this.interactionController.stopInteraction();
9347
+ this.renderController.setInteractionEnabled(false);
9348
+ }
8049
9349
  this.activeTaskID = taskID;
8050
9350
  try {
8051
9351
  const confirmation = this.streamController.beginGeneration({
8052
9352
  taskID,
8053
9353
  videoFormat: options.videoFormat,
8054
9354
  context,
9355
+ restart: options.restart,
8055
9356
  ...options.targetSize && { targetSize: options.targetSize },
8056
9357
  mirror: options.mirror ?? false,
8057
9358
  faceRestoration: options.faceRestoration ?? false,
8058
- ...options.onStartSent && { onStartSent: options.onStartSent }
9359
+ ...options.onStartSent && { onStartSent: options.onStartSent },
9360
+ extraParams: options.extraParams
8059
9361
  });
8060
9362
  await this.awaitConfirmation(confirmation, options.signal);
8061
9363
  options.ensureCurrent();
8062
9364
  this.currentContext = context;
8063
9365
  await options.waitUntilRemoteReady();
8064
9366
  options.ensureCurrent();
9367
+ options.onRemoteReady?.(taskID);
9368
+ options.ensureCurrent();
8065
9369
  await this.streamController.activateRemoteAudio();
8066
9370
  options.ensureCurrent();
9371
+ this.interactionController.startInteraction(taskID, options.videoFormat);
9372
+ this.renderController.setInteractionEnabled(true, options.videoFormat);
8067
9373
  return taskID;
8068
9374
  } catch (error) {
8069
9375
  await this.stop(taskID);
@@ -8074,9 +9380,10 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8074
9380
  * 更新活动任务的生成条件;信令发送成功后才覆盖缓存,无任务或上下文时抛错。
8075
9381
  * @param taskID 当前活动任务标识。
8076
9382
  * @param options.videoFormat 模型生成使用的视频规格。
8077
- * @param options.targetSize 服务端生成后的回传尺寸。
8078
9383
  * @param options.context 生成条件,缺省复用最近缓存的上下文。
9384
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
8079
9385
  * @param options.faceRestoration 是否回贴原始人脸,默认 false。
9386
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
8080
9387
  */
8081
9388
  async update(taskID, options) {
8082
9389
  const context = options.context ?? this.currentContext;
@@ -8087,11 +9394,14 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8087
9394
  taskID,
8088
9395
  videoFormat: options.videoFormat,
8089
9396
  context,
9397
+ ...options.targetSize && { targetSize: options.targetSize },
8090
9398
  faceRestoration: options.faceRestoration ?? false,
8091
- ...options.targetSize && { targetSize: options.targetSize }
9399
+ extraParams: options.extraParams
8092
9400
  });
8093
9401
  if (this.activeTaskID !== taskID) throw RealtimeCoordinator.cancelledError();
8094
9402
  this.currentContext = context;
9403
+ this.interactionController.startInteraction(taskID, options.videoFormat);
9404
+ this.renderController.setInteractionEnabled(true, options.videoFormat);
8095
9405
  }
8096
9406
  /**
8097
9407
  * 幂等停止指定活动任务;并发调用共享结果,停止失败仅记录日志。
@@ -8102,6 +9412,8 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8102
9412
  }
8103
9413
  if (!taskID || this.activeTaskID !== taskID) return;
8104
9414
  this.activeTaskID = void 0;
9415
+ this.interactionController.stopInteraction();
9416
+ this.renderController.setInteractionEnabled(false);
8105
9417
  const completion = (async () => {
8106
9418
  try {
8107
9419
  await this.streamController.stopGeneration(taskID);
@@ -8122,6 +9434,8 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8122
9434
  */
8123
9435
  async reset(taskID) {
8124
9436
  this.currentContext = void 0;
9437
+ this.interactionController.stopInteraction();
9438
+ this.renderController.setInteractionEnabled(false);
8125
9439
  await this.stop(this.activeTaskID ?? (taskID || void 0));
8126
9440
  }
8127
9441
  /**
@@ -8178,6 +9492,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8178
9492
  */
8179
9493
  __publicField(this, "options");
8180
9494
  __publicField(this, "mediaService");
9495
+ /**
9496
+ * 可选的任务录制;未开启时不分配浏览器录制资源。
9497
+ */
9498
+ __publicField(this, "recordingController", new RecordingController());
8181
9499
  /**
8182
9500
  * 本地流创建时的生成格式;上行编码调整不改变模型输入配置与远端插帧尺寸。
8183
9501
  */
@@ -8194,20 +9512,14 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8194
9512
  */
8195
9513
  __publicField(this, "coordinator");
8196
9514
  __publicField(this, "errorHandler");
8197
- __publicField(this, "cameraController");
8198
- __publicField(this, "networkVideoController", new NetworkVideoController());
8199
- __publicField(this, "localVideoController", new LocalVideoController((error) => {
8200
- void this.coordinator.terminateWithError(error, 0 /* connection */);
8201
- }));
8202
- __publicField(this, "externalVideoController", new ExternalVideoController((error) => {
8203
- void this.coordinator.terminateWithError(error, 1 /* all */);
8204
- }));
8205
- __publicField(this, "rtcManager");
9515
+ __publicField(this, "mediaController");
9516
+ __publicField(this, "renderController");
8206
9517
  /**
8207
9518
  * 启动计时与首帧通知
8208
9519
  */
8209
9520
  __publicField(this, "launchTimer", new RealtimeLaunchTimer());
8210
9521
  __publicField(this, "remoteFrameDisplayHandler");
9522
+ __publicField(this, "remoteRenderWarmupCompletedHandler");
8211
9523
  /**
8212
9524
  * 媒体权限弹窗观察
8213
9525
  */
@@ -8247,14 +9559,6 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8247
9559
  this.supportsInterpolation = dependencies?.supportsFrameInterpolation ?? (async (size) => await frameInterpolationAdapter(size) !== null);
8248
9560
  this.errorHandler = new RealtimeErrorHandler();
8249
9561
  const rtcManager = dependencies?.rtcManager ?? createRtcManager(options.provider, dependencies?.environment ?? "china" /* china */);
8250
- this.rtcManager = rtcManager;
8251
- this.cameraController = dependencies?.cameraController ?? new CameraController({
8252
- rtcManager,
8253
- mediaService: this.mediaService,
8254
- errorListener: (error) => {
8255
- void this.errorHandler.report(error);
8256
- }
8257
- });
8258
9562
  const sessionService = dependencies?.sessionService ?? (dependencies?.apiService ? new RealtimeSessionService({ apiService: dependencies.apiService, provider: options.provider }) : void 0);
8259
9563
  this.streamController = dependencies?.streamController ?? new StreamController({
8260
9564
  rtcManager,
@@ -8265,10 +9569,42 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8265
9569
  void this.errorHandler.report(error);
8266
9570
  },
8267
9571
  remoteStreamListener: (binding) => {
9572
+ if (!binding) {
9573
+ void this.recordingController.stop("mediaInterrupted" /* mediaInterrupted */);
9574
+ }
8268
9575
  this.clearRemoteVideoStatistics();
8269
9576
  this.connectionManager.handleRemoteStreamBinding(binding);
8270
9577
  }
8271
9578
  });
9579
+ this.mediaController = new MediaController({
9580
+ rtcManager,
9581
+ mediaService: this.mediaService,
9582
+ cameraController: dependencies?.cameraController,
9583
+ onCameraError: (error) => {
9584
+ void this.errorHandler.report(error);
9585
+ },
9586
+ onVideoError: (error) => {
9587
+ void this.coordinator.terminateWithError(error, 0 /* connection */);
9588
+ },
9589
+ onExternalError: (error) => {
9590
+ void this.coordinator.terminateWithError(error, 1 /* all */);
9591
+ },
9592
+ onImageError: (error) => {
9593
+ void this.coordinator.terminateWithError(error, 1 /* all */);
9594
+ },
9595
+ interactionListener: (taskID, points) => this.streamController.sendTracks(taskID, points)
9596
+ });
9597
+ this.renderController = new RenderController({
9598
+ onFrameDisplayed: () => this.remoteFrameDisplayHandler?.(),
9599
+ onRenderWarmupCompleted: () => {
9600
+ this.remoteRenderWarmupCompletedHandler?.();
9601
+ void this.completeGenerationPresentation().catch((error) => this.errorHandler.report(error));
9602
+ },
9603
+ onAttached: () => this.applyRemoteInterpolation(),
9604
+ onDetached: () => {
9605
+ this.interpolationRevision++;
9606
+ }
9607
+ });
8272
9608
  this.connectionManager = new XmaxRealtimeConnectionManager({
8273
9609
  provider: options.provider,
8274
9610
  sessionService,
@@ -8278,35 +9614,24 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8278
9614
  onHeartbeatFailure: (sessionID, error) => {
8279
9615
  void this.handleHeartbeatFailure(sessionID, error);
8280
9616
  },
8281
- onFrameDisplayed: () => this.remoteFrameDisplayHandler?.(),
8282
- onRenderAttached: () => this.applyRemoteInterpolation(),
8283
- onRenderDetached: () => {
8284
- this.interpolationRevision++;
8285
- }
9617
+ renderController: this.renderController,
9618
+ onInteraction: (frame) => this.mediaController.submitInteraction(frame)
8286
9619
  });
8287
- this.generationManager = new XmaxRealtimeGenerationManager(this.streamController);
9620
+ this.generationManager = new XmaxRealtimeGenerationManager(this.streamController, this.mediaController, this.renderController);
8288
9621
  this.coordinator = new RealtimeCoordinator({
8289
9622
  errorHandler: this.errorHandler,
8290
9623
  cleanup: (scope, taskID) => this.performCleanup(scope, taskID)
8291
9624
  });
8292
- if (options.provider === "agora" /* agora */) {
8293
- this.qualityHistory = new QualityHistory(options.model);
9625
+ if (options.provider === "agora" /* agora */ || options.provider === "trtc" /* trtc */) {
9626
+ this.qualityHistory = new QualityHistory(options.model, options.provider);
8294
9627
  this.qualityController = new QualityController(async (change, signal) => {
8295
- if (signal.aborted || this.currentState.connectionState !== "Generating" /* generating */) {
8296
- throw new XmaxError("CANCELLED" /* cancelled */, "Adaptive video format update was cancelled");
8297
- }
8298
9628
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
8299
- const track = this.currentUplinkTrack;
8300
- const state2 = track && this.qualityStates.get(track);
8301
- if (!track || !state2) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Adaptive video baseline is unavailable");
8302
- const format = qualityVideoFormat(state2.baseline, change.to);
8303
- await this.streamController.updateUplinkVideoFormat(format);
8304
- token.ensureCurrent();
8305
- this.generationVideoFormat ?? (this.generationVideoFormat = state2.baseline);
8306
- track.updateVideoFormat(format);
8307
- state2.level = change.to;
8308
- this.qualityHistory?.write("uplink", change.to);
9629
+ await this.applyUplinkQuality(change.to, token, signal);
8309
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));
8310
9635
  });
8311
9636
  this.downlinkQualityController = new QualityController(async (change, signal) => {
8312
9637
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
@@ -8363,13 +9688,61 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8363
9688
  this.applyRemoteInterpolation();
8364
9689
  });
8365
9690
  }
9691
+ /**
9692
+ * 手动调整上行档位,保留原始基准,自动调档从新档位继续。
9693
+ * @param direction 升一档或降一档;到达 L1/L5 边界时不重复应用。
9694
+ */
9695
+ async adjustUplinkQuality(direction) {
9696
+ await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
9697
+ const controller = this.qualityController;
9698
+ const track = this.currentUplinkTrack;
9699
+ const state2 = track && this.qualityStates.get(track);
9700
+ if (!controller || !state2 || this.currentState.connectionState !== "Generating" /* generating */ || direction !== "upgrade" && direction !== "downgrade") {
9701
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Uplink quality adjustment requires an active adaptive-quality generation and a valid direction");
9702
+ }
9703
+ const level = Math.max(1, Math.min(Object.keys(QUALITY_TIERS).length, state2.level + (direction === "upgrade" ? -1 : 1)));
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
+ }
9708
+ this.adaptiveQualityRevision++;
9709
+ controller.resetSamples();
9710
+ await this.applyUplinkQuality(level, token);
9711
+ controller.start(level);
9712
+ });
9713
+ }
9714
+ async applyUplinkQuality(level, token, signal) {
9715
+ const track = this.currentUplinkTrack;
9716
+ const state2 = track && this.qualityStates.get(track);
9717
+ const ensureActive2 = () => {
9718
+ token.ensureCurrent();
9719
+ if (signal?.aborted || !track || !state2 || track !== this.currentUplinkTrack || state2 !== this.qualityStates.get(track) || this.currentState.connectionState !== "Generating" /* generating */) {
9720
+ throw new XmaxError("CANCELLED" /* cancelled */, "Adaptive video format update was cancelled");
9721
+ }
9722
+ };
9723
+ ensureActive2();
9724
+ if (!track || !state2) return;
9725
+ const target = this.uplinkQualityFormat(state2.baseline, level);
9726
+ let format;
9727
+ if (this.options.provider === "trtc" /* trtc */) {
9728
+ format = await this.streamController.updateUplinkFrameRate(target.fps);
9729
+ } else {
9730
+ await this.streamController.updateUplinkVideoFormat(target);
9731
+ format = target;
9732
+ }
9733
+ ensureActive2();
9734
+ this.generationVideoFormat ?? (this.generationVideoFormat = state2.baseline);
9735
+ track.updateVideoFormat(format);
9736
+ state2.level = level;
9737
+ this.qualityHistory?.write("uplink", level);
9738
+ }
8366
9739
  /** 手动调试与自动调档使用同一尺寸信令路径,不修改生成或上行配置。 */
8367
9740
  async adjustDownlinkQuality(direction) {
8368
9741
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
8369
9742
  const controller = this.downlinkQualityController;
8370
9743
  const state2 = this.downlinkQualityState;
8371
9744
  if (!controller || !state2 || this.currentState.connectionState !== "Generating" /* generating */ || direction !== "upgrade" && direction !== "downgrade") {
8372
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Downlink quality adjustment requires an active Agora generation and a valid direction");
9745
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Downlink quality adjustment requires an active adaptive-quality generation and a valid direction");
8373
9746
  }
8374
9747
  const level = Math.max(1, Math.min(Object.keys(QUALITY_TIERS).length, state2.level + (direction === "upgrade" ? -1 : 1)));
8375
9748
  if (level === state2.level) return;
@@ -8451,7 +9824,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8451
9824
  applyRemoteInterpolation() {
8452
9825
  const revision = ++this.interpolationRevision;
8453
9826
  const size = this.interpolationSize;
8454
- this.connectionManager.setFrameInterpolation(size ? {
9827
+ this.renderController.setFrameInterpolation(size ? {
8455
9828
  size,
8456
9829
  onFailure: (error) => {
8457
9830
  if (revision === this.interpolationRevision) this.handleInterpolationFailure(error);
@@ -8621,7 +9994,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8621
9994
  "media" /* media */,
8622
9995
  void 0,
8623
9996
  async (token) => {
8624
- this.ensureNoLocalSource();
9997
+ this.mediaController.ensureNoLocalSource();
8625
9998
  await this.coordinator.commit(
8626
9999
  new RealtimeState({
8627
10000
  connectionState: "Preparing" /* preparing */
@@ -8642,7 +10015,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8642
10015
  let stream;
8643
10016
  try {
8644
10017
  token.ensureCurrent();
8645
- stream = await this.cameraController.createLocalCameraStream(options);
10018
+ stream = await this.mediaController.createLocalCameraStream(options);
8646
10019
  } catch (error) {
8647
10020
  this.disposePermissionWatch();
8648
10021
  this.launchTimer.cancel();
@@ -8651,7 +10024,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8651
10024
  token.ensureCurrent();
8652
10025
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8653
10026
  completeCamera();
8654
- this.cameraController.setPreviewReadyHandler((isCurrent) => {
10027
+ this.mediaController.setCameraPreviewReadyHandler((isCurrent) => {
8655
10028
  void this.coordinator.localPreviewDidBecomeReady(isCurrent);
8656
10029
  });
8657
10030
  return stream;
@@ -8667,12 +10040,12 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8667
10040
  */
8668
10041
  async createNetworkVideoStream(options) {
8669
10042
  return this.coordinator.run("media" /* media */, void 0, async (token) => {
8670
- this.ensureNoLocalSource();
8671
- const stream = this.networkVideoController.create(options, this.mediaService);
10043
+ this.mediaController.ensureNoLocalSource();
10044
+ const stream = this.mediaController.createNetworkVideoStream(options);
8672
10045
  token.setFailureScope(1 /* all */);
8673
10046
  this.launchTimer.start();
8674
10047
  this.clearVideoStatistics();
8675
- await this.rtcManager.initialize();
10048
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
8676
10049
  token.ensureCurrent();
8677
10050
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8678
10051
  await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
@@ -8688,22 +10061,32 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8688
10061
  */
8689
10062
  async createLocalVideoStream(options) {
8690
10063
  return this.coordinator.run("media" /* media */, void 0, async (token) => {
8691
- this.ensureNoLocalSource();
8692
- if (!this.rtcManager.setExternalMediaTracks) {
8693
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "The RTC provider does not support local video streams");
8694
- }
10064
+ this.mediaController.ensureNoLocalSource();
10065
+ this.mediaController.validateExternalMediaSupport("local");
8695
10066
  token.setFailureScope(1 /* all */);
8696
10067
  this.launchTimer.start();
8697
10068
  this.clearVideoStatistics();
8698
- const stream = await this.localVideoController.create(options, this.mediaService, token.signal);
8699
- token.ensureCurrent();
8700
- await this.rtcManager.initialize();
10069
+ const stream = await this.mediaController.createLocalVideoStream(options, token.signal);
8701
10070
  token.ensureCurrent();
8702
- await this.rtcManager.setExternalMediaTracks({
8703
- videoTrack: stream.videoTrack.mediaStreamTrack,
8704
- audioTrack: this.localVideoController.audioTrack
8705
- });
10071
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
10072
+ this.generationVideoFormat = stream.videoTrack?.videoFormat;
10073
+ await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
10074
+ return stream;
10075
+ });
10076
+ }
10077
+ /**
10078
+ * 创建本地图片静止视频源并准备 RTC,失败或取消时释放本次资源。
10079
+ */
10080
+ async createLocalImageStream(options) {
10081
+ return this.coordinator.run("media" /* media */, void 0, async (token) => {
10082
+ this.mediaController.ensureNoLocalSource();
10083
+ this.mediaController.validateExternalMediaSupport("image");
10084
+ token.setFailureScope(1 /* all */);
10085
+ this.launchTimer.start();
10086
+ this.clearVideoStatistics();
10087
+ const stream = await this.mediaController.createLocalImageStream(options, token.signal);
8706
10088
  token.ensureCurrent();
10089
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
8707
10090
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8708
10091
  await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
8709
10092
  return stream;
@@ -8716,18 +10099,13 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8716
10099
  */
8717
10100
  async createExternalVideoStream(options) {
8718
10101
  return this.coordinator.run("media" /* media */, void 0, async (token) => {
8719
- this.ensureNoLocalSource();
8720
- if (!this.rtcManager.setExternalMediaTracks) {
8721
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "The RTC provider does not support external video streams");
8722
- }
8723
- const stream = this.externalVideoController.create(options, this.mediaService);
10102
+ this.mediaController.ensureNoLocalSource();
10103
+ this.mediaController.validateExternalMediaSupport("external");
10104
+ const stream = this.mediaController.createExternalVideoStream(options);
8724
10105
  token.setFailureScope(1 /* all */);
8725
10106
  this.launchTimer.start();
8726
10107
  this.clearVideoStatistics();
8727
- await this.rtcManager.initialize();
8728
- token.ensureCurrent();
8729
- await this.rtcManager.setExternalMediaTracks({ videoTrack: stream.videoTrack.mediaStreamTrack });
8730
- token.ensureCurrent();
10108
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
8731
10109
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8732
10110
  await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
8733
10111
  return stream;
@@ -8735,19 +10113,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8735
10113
  }
8736
10114
  /** 当前可发布的本地视频轨,不包含服务端直接读取的网络视频。 */
8737
10115
  get currentUplinkTrack() {
8738
- return this.externalVideoController.currentTrack ?? this.localVideoController.currentTrack ?? this.cameraController.currentTrack;
10116
+ return this.mediaController.uplinkTrack;
8739
10117
  }
8740
10118
  /** 当前 Manager 拥有的媒体源轨道。 */
8741
10119
  get currentLocalTrack() {
8742
- return this.currentUplinkTrack ?? this.networkVideoController.currentTrack;
8743
- }
8744
- /**
8745
- * 创建前拒绝覆盖活动源,避免旧预览或连接资源被静默遗留。
8746
- */
8747
- ensureNoLocalSource() {
8748
- if (this.currentLocalTrack || this.localVideoController.isActive) {
8749
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local stream before creating another one");
8750
- }
10120
+ return this.mediaController.currentTrack;
8751
10121
  }
8752
10122
  /**
8753
10123
  * 移除媒体权限弹窗观察;授权决定已记录或不再需要观察时调用。
@@ -8796,7 +10166,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8796
10166
  const track = this.currentUplinkTrack;
8797
10167
  const previous = track?.videoFormat;
8798
10168
  const state2 = this.currentState.connectionState;
8799
- if (!track || !previous || state2 !== "Connected" /* connected */ && state2 !== "Generating" /* generating */) {
10169
+ if (!track || !previous || state2 !== "Connected" /* connected */ && state2 !== "Starting" /* starting */ && state2 !== "Generating" /* generating */) {
8800
10170
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Frame rate updates require a connected local camera, external or file stream");
8801
10171
  }
8802
10172
  if (!Number.isFinite(fps) || fps <= 0) {
@@ -8856,7 +10226,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8856
10226
  * 停止本地相机流并释放本地预览与 RTC 资源。
8857
10227
  */
8858
10228
  async stopLocalCameraStream() {
8859
- if (this.networkVideoController.currentTrack || this.localVideoController.isActive || this.externalVideoController.currentTrack) {
10229
+ if (this.mediaController.source && this.mediaController.source !== "camera") {
8860
10230
  return;
8861
10231
  }
8862
10232
  await this.coordinator.run(
@@ -8866,7 +10236,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8866
10236
  this.launchTimer.cancel();
8867
10237
  this.clearVideoStatistics();
8868
10238
  this.disposePermissionWatch();
8869
- await this.cameraController.stopLocalCameraStream();
10239
+ await this.mediaController.stopLocalCameraStream();
8870
10240
  token.ensureCurrent();
8871
10241
  this.generationVideoFormat = void 0;
8872
10242
  await this.coordinator.commit(
@@ -8889,11 +10259,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8889
10259
  "cameraSwitch" /* cameraSwitch */,
8890
10260
  void 0,
8891
10261
  async (token) => {
8892
- if (this.externalVideoController.currentTrack) {
10262
+ if (this.mediaController.source === "externalVideo") {
8893
10263
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video capture is managed by the caller");
8894
10264
  }
8895
10265
  this.qualityController?.stop();
8896
- const stream = await this.cameraController.switchCamera();
10266
+ const stream = await this.mediaController.switchCamera();
8897
10267
  token.ensureCurrent();
8898
10268
  return stream;
8899
10269
  }
@@ -8917,7 +10287,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8917
10287
  void 0,
8918
10288
  async (token) => {
8919
10289
  const connectionState = this.coordinator.currentState.connectionState;
8920
- if (connectionState === "Connecting" /* connecting */ || connectionState === "Connected" /* connected */ || connectionState === "Generating" /* generating */) {
10290
+ if (connectionState === "Connecting" /* connecting */ || connectionState === "Connected" /* connected */ || connectionState === "Starting" /* starting */ || connectionState === "Generating" /* generating */) {
8921
10291
  throw new XmaxError(
8922
10292
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
8923
10293
  "A realtime connection is already active"
@@ -8930,19 +10300,25 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8930
10300
  /**
8931
10301
  * 按需建立连接并开始生成。
8932
10302
  *
8933
- * 尚未连接时先建立实时连接;已在生成时仅更新生成条件,不重启生成。
10303
+ * 尚未连接时先建立实时连接;已在生成时默认更新条件,restart 为 true 时重新开始生成。
8934
10304
  * 首次生成必须提供条件上下文,之后缺省时复用最近一次缓存的上下文。
10305
+ * 返回后请将远端流挂载到 SDK 视频视图;首帧及渲染预热完成后状态才进入 generating。
8935
10306
  *
8936
10307
  * @param options.localStream 由当前 Manager 创建的摄像头、外部视频轨、本地文件或网络视频源。
8937
10308
  * @param options.context 本次生成使用的条件上下文;缺省时复用缓存。
10309
+ * @param options.restart 是否保留 RTC 连接并创建新任务发送 start,默认 false;不预先发送 stop。
8938
10310
  * @param options.faceRestoration 是否启用原始人脸回贴,默认 false;正在生成时通过 change_condition 更新。
8939
- * @returns 承载远端生成画面的媒体流。
10311
+ * @param options.extraParams 合并到本次 start 或 change_condition 的 params;不缓存,自动恢复时不携带,不允许覆盖 SDK 字段。
10312
+ * @param options.record 是否录制远端原始音视频约 20 秒,默认 false;仅 start 有效,条件更新时忽略,重启录制暂不支持。
10313
+ * @returns 可挂载的远端媒体流,不等待渲染预热;展示就绪以 generating 状态为准。
10314
+ * @throws extraParams 包含非 JSON 数据、循环引用或 SDK 保留字段时,在连接或更新前抛出 invalidConfiguration。
8940
10315
  * @throws 本地流不属于当前 Manager、缺少可用的条件上下文、信令发送
8941
10316
  * 或生成确认失败时抛出错误;失败时自动停止生成任务并释放连接资源。
8942
10317
  */
8943
10318
  async startGeneration(options) {
10319
+ const extraParams = snapshotExtraParams(options.extraParams);
8944
10320
  return this.coordinator.run(
8945
- "generation" /* generation */,
10321
+ options.restart === true ? "generationRestart" /* generationRestart */ : "generation" /* generation */,
8946
10322
  void 0,
8947
10323
  async (token) => {
8948
10324
  const localTrack = options.localStream.videoTrack;
@@ -8960,34 +10336,50 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8960
10336
  );
8961
10337
  }
8962
10338
  const currentState = this.coordinator.currentState;
8963
- if (currentState.connectionState === "Generating" /* generating */) {
10339
+ const hasGeneration = currentState.connectionState === "Generating" /* generating */ || currentState.connectionState === "Starting" /* starting */ && !!currentState.taskID;
10340
+ if (hasGeneration && options.restart !== true) {
8964
10341
  await this.generationManager.update(currentState.taskID, {
8965
10342
  videoFormat,
8966
- faceRestoration: options.faceRestoration ?? false,
10343
+ context: options.context,
8967
10344
  targetSize: this.downlinkQualityController ? this.downlinkQualityState?.size : void 0,
8968
- context: options.context
10345
+ faceRestoration: options.faceRestoration ?? false,
10346
+ extraParams
8969
10347
  });
8970
10348
  token.ensureCurrent();
8971
10349
  return this.connectionManager.makeRemoteStream();
8972
10350
  }
8973
10351
  const supplied = this.generationManager.validateContext(options.context);
8974
- const referenceVideo = this.networkVideoController.reference;
10352
+ const referenceVideo = this.mediaController.referenceVideo;
8975
10353
  if (!referenceVideo && supplied.referenceVideo) {
8976
10354
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Create a network video stream before using a reference video");
8977
10355
  }
8978
10356
  const context = referenceVideo ? new RealtimeContext({ ...supplied, referenceVideo }) : supplied;
8979
- if (currentState.connectionState !== "Connected" /* connected */) {
10357
+ if (currentState.connectionState !== "Connected" /* connected */ && !hasGeneration) {
8980
10358
  await this.performConnect(options.localStream, token);
8981
10359
  }
8982
- return this.performStartGeneration(token, videoFormat, context, options.faceRestoration ?? false);
10360
+ return this.performStartGeneration(
10361
+ token,
10362
+ videoFormat,
10363
+ context,
10364
+ options.restart ?? false,
10365
+ options.faceRestoration ?? false,
10366
+ extraParams,
10367
+ options.record ?? false
10368
+ );
8983
10369
  }
8984
10370
  );
8985
10371
  }
10372
+ /**
10373
+ * 设置录制最终事件监听器;清除监听不停止录制,录制错误不影响生成。
10374
+ */
10375
+ setRecordingListener(listener) {
10376
+ this.recordingController.setListener(listener);
10377
+ }
8986
10378
  /**
8987
10379
  * 断开实时连接并保留当前本地媒体预览。
8988
10380
  */
8989
10381
  async disconnect() {
8990
- this.localVideoController.pause();
10382
+ this.mediaController.pauseLocalVideo();
8991
10383
  this.launchTimer.cancel();
8992
10384
  this.clearVideoStatistics();
8993
10385
  await this.coordinator.disconnect();
@@ -8997,19 +10389,28 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8997
10389
  * 关闭期间重复调用会等待同一个释放任务;关闭完成后仍可重新创建本地流。
8998
10390
  */
8999
10391
  async close() {
9000
- this.localVideoController.pause();
10392
+ this.mediaController.pauseLocalVideo();
9001
10393
  this.launchTimer.cancel();
9002
10394
  this.clearVideoStatistics();
9003
10395
  await this.coordinator.terminate(1 /* all */);
9004
10396
  }
10397
+ /** TRTC 临时只按档位调整帧率;启动时也保留原始分辨率。 */
10398
+ uplinkQualityFormat(baseline, level) {
10399
+ const format = qualityVideoFormat(baseline, level);
10400
+ return this.options.provider === "trtc" /* trtc */ ? format.resized(baseline.width, baseline.height) : format;
10401
+ }
9005
10402
  /** 仅为尚无档位记录的本地上行选择初始档位;不应用配置或覆盖重连状态。 */
9006
10403
  resolveInitialUplinkQuality(track) {
9007
10404
  if (!this.qualityController) return void 0;
9008
- if (this.networkVideoController.currentTrack) return void 0;
10405
+ if (this.mediaController.source === "networkVideo") return void 0;
9009
10406
  if (this.qualityStates.has(track)) return void 0;
9010
10407
  const baseline = track.videoFormat;
9011
10408
  if (!baseline) return void 0;
9012
- 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 };
9013
10414
  }
9014
10415
  /**
9015
10416
  * 建立实时连接:创建会话、进房发布、启动心跳并准备远端渲染轨道。
@@ -9038,8 +10439,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9038
10439
  if (token.signal.aborted) abortWarmup();
9039
10440
  try {
9040
10441
  let beforePublish;
9041
- if (localTrack === this.cameraController.currentTrack && this.cameraController.isFrameValidationEnabled) {
9042
- const cameraReady = this.cameraController.waitForValidCameraFrame(warmupController.signal).then(() => {
10442
+ if (this.mediaController.isFrameValidationEnabled) {
10443
+ const cameraReady = this.mediaController.waitForValidCameraFrame(warmupController.signal).then(() => {
9043
10444
  token.ensureCurrent();
9044
10445
  });
9045
10446
  void cameraReady.catch(() => {
@@ -9047,7 +10448,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9047
10448
  beforePublish = () => cameraReady;
9048
10449
  }
9049
10450
  const initialQuality = this.resolveInitialUplinkQuality(localTrack);
9050
- const initialFormat = initialQuality ? qualityVideoFormat(initialQuality.baseline, initialQuality.level) : void 0;
10451
+ const initialFormat = initialQuality ? this.uplinkQualityFormat(initialQuality.baseline, initialQuality.level) : void 0;
9051
10452
  const remote = await this.connectionManager.connect({
9052
10453
  localTrack,
9053
10454
  remoteVideoFormat: this.generationVideoFormat ?? localTrack.videoFormat,
@@ -9060,8 +10461,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9060
10461
  this.qualityHistory?.write("uplink", initialQuality.level);
9061
10462
  },
9062
10463
  model: this.options.model,
9063
- includeLocalAudio: this.localVideoController.isActive ? this.localVideoController.audioTrack !== void 0 : localTrack === this.cameraController.currentTrack && this.cameraController.useMicrophone,
9064
- publishLocalMedia: !this.networkVideoController.currentTrack,
10464
+ includeLocalAudio: this.mediaController.hasAudio,
10465
+ publishLocalMedia: this.mediaController.source !== "networkVideo",
9065
10466
  ensureCurrent: () => token.ensureCurrent(),
9066
10467
  onPublished: () => {
9067
10468
  this.acceptsVideoStatistics = true;
@@ -9083,38 +10484,73 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9083
10484
  }
9084
10485
  /**
9085
10486
  * 编排插帧、首帧计时和生成状态,任务与确认由生成管理器负责。
9086
- */
9087
- async performStartGeneration(token, videoFormat, context, faceRestoration) {
10487
+ *
10488
+ * @param token 当前生成操作的取消与状态控制令牌。
10489
+ * @param videoFormat 模型生成使用的视频规格。
10490
+ * @param context 本次已解析的生成条件。
10491
+ * @param restart 是否替换当前生成任务并重新发送 start。
10492
+ * @param faceRestoration 是否回贴原始人脸。
10493
+ * @param extraParams 合并到本次 start 的 params,不缓存。
10494
+ * @param record 是否为本次任务开启独立录制,不影响生成返回时间。
10495
+ * @returns 承载生成结果的远端媒体流。
10496
+ */
10497
+ async performStartGeneration(token, videoFormat, context, restart, faceRestoration, extraParams, record2 = false) {
9088
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();
10508
+ if (restart) {
10509
+ void this.recordingController.stop("restarted" /* restarted */);
10510
+ }
9089
10511
  const initialLevel = this.qualityHistory?.read("downlink") ?? 1;
9090
10512
  const targetSize = this.downlinkQualityController ? qualityVideoSize(videoFormat, initialLevel) : void 0;
9091
10513
  await this.prepareFrameInterpolation(targetSize ?? videoFormat, token);
9092
10514
  const completeFirstFrame = this.launchTimer.startFirstFrame();
10515
+ this.remoteRenderWarmupCompletedHandler = void 0;
9093
10516
  this.remoteFrameDisplayHandler = () => {
9094
- if (!token.signal.aborted) completeFirstFrame();
10517
+ if (!token.signal.aborted) {
10518
+ completeFirstFrame();
10519
+ this.remoteRenderWarmupCompletedHandler ?? (this.remoteRenderWarmupCompletedHandler = this.launchTimer.startRenderWarmup());
10520
+ }
9095
10521
  };
10522
+ this.renderController.prepareForGeneration();
9096
10523
  const taskID = await this.generationManager.start({
9097
10524
  videoFormat,
9098
- targetSize,
9099
- context,
9100
- faceRestoration,
9101
- mirror: this.currentLocalTrack === this.cameraController.currentTrack && this.cameraController.currentTrack?.position === "front" /* front */,
9102
10525
  signal: token.signal,
9103
10526
  ensureCurrent: () => token.ensureCurrent(),
9104
- onStartSent: this.localVideoController.isActive ? async () => {
9105
- token.ensureCurrent();
9106
- await this.localVideoController.start(token.signal);
9107
- token.ensureCurrent();
9108
- } : void 0,
9109
10527
  waitUntilRemoteReady: () => this.connectionManager.waitUntilRemoteTrackReady(
9110
10528
  REMOTE_FIRST_FRAME_TIMEOUT_MS,
9111
10529
  token.signal
9112
- )
9113
- });
9114
- const sessionID = this.connectionManager.currentSessionID;
9115
- if (!sessionID) {
9116
- throw new XmaxError("SESSION_ERROR" /* sessionError */, "Realtime session is unavailable");
9117
- }
10530
+ ),
10531
+ context,
10532
+ restart,
10533
+ onRemoteReady: record2 ? (taskID2) => {
10534
+ if (restart) {
10535
+ this.recordingController.reportFailure(taskID2, new XmaxError(
10536
+ "INVALID_CONFIGURATION" /* invalidConfiguration */,
10537
+ "Recording restarted generation tasks is not supported"
10538
+ ));
10539
+ } else {
10540
+ this.recordingController.start(taskID2, () => this.streamController.getRemoteRecordingTracks());
10541
+ }
10542
+ } : void 0,
10543
+ targetSize,
10544
+ mirror: this.mediaController.source === "camera" && this.currentLocalTrack?.position === "front" /* front */,
10545
+ faceRestoration,
10546
+ onStartSent: this.mediaController.source === "localVideo" ? async () => {
10547
+ token.ensureCurrent();
10548
+ await this.mediaController.startLocalVideo(token.signal);
10549
+ token.ensureCurrent();
10550
+ } : void 0,
10551
+ extraParams
10552
+ });
10553
+ token.ensureCurrent();
9118
10554
  this.downlinkQualityController?.stop();
9119
10555
  this.downlinkQualityState = {
9120
10556
  taskID,
@@ -9123,20 +10559,38 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9123
10559
  size: targetSize ?? { width: videoFormat.width, height: videoFormat.height }
9124
10560
  };
9125
10561
  await this.coordinator.commit(
9126
- new RealtimeState({ connectionState: "Generating" /* generating */, sessionID, taskID }),
10562
+ new RealtimeState({ connectionState: "Starting" /* starting */, sessionID, taskID }),
9127
10563
  token
9128
10564
  );
10565
+ token.ensureCurrent();
10566
+ await this.completeGenerationPresentation();
10567
+ token.ensureCurrent();
9129
10568
  this.qualityHistory?.write("downlink", initialLevel);
9130
- const onFinish = this.networkVideoController.onFinish;
9131
- if (this.networkVideoController.currentTrack) {
9132
- this.streamController.activateNetworkVideoCompletion(onFinish && (() => {
9133
- if (this.currentState.taskID === taskID && this.currentState.connectionState === "Generating" /* generating */) {
9134
- return onFinish();
10569
+ const onFinish = this.mediaController.onNetworkVideoFinish;
10570
+ if (this.mediaController.source === "networkVideo") {
10571
+ this.streamController.activateNetworkVideoCompletion(() => {
10572
+ if (this.currentState.taskID === taskID && (this.currentState.connectionState === "Starting" /* starting */ || this.currentState.connectionState === "Generating" /* generating */)) {
10573
+ void this.recordingController.stop("generationEnded" /* generationEnded */);
10574
+ return onFinish?.();
9135
10575
  }
9136
- }));
10576
+ });
9137
10577
  }
9138
10578
  return this.connectionManager.makeRemoteStream();
9139
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
+ }
9140
10594
  /**
9141
10595
  * 心跳失败或会话失效:结束当前连接生命周期并通过最终状态给出原因。
9142
10596
  */
@@ -9151,9 +10605,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9151
10605
  * 统一清理顺序:停止心跳与生成,再断开连接;all 额外释放本地媒体。
9152
10606
  */
9153
10607
  async performCleanup(scope, taskID) {
9154
- this.localVideoController.pause();
10608
+ const recordingFinished = this.recordingController.stop("generationEnded" /* generationEnded */);
10609
+ this.mediaController.pauseLocalVideo();
9155
10610
  this.launchTimer.cancel();
9156
10611
  this.remoteFrameDisplayHandler = void 0;
10612
+ this.remoteRenderWarmupCompletedHandler = void 0;
9157
10613
  this.clearVideoStatistics();
9158
10614
  this.interpolationSize = void 0;
9159
10615
  this.applyRemoteInterpolation();
@@ -9161,27 +10617,17 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9161
10617
  this.connectionManager.stopHeartbeat();
9162
10618
  await this.generationManager.reset(taskID);
9163
10619
  const sessionID = await this.connectionManager.disconnect();
10620
+ await recordingFinished;
9164
10621
  if (scope === 1 /* all */) {
9165
- const hadExternalSource = this.networkVideoController.currentTrack !== void 0 || this.localVideoController.isActive || this.externalVideoController.currentTrack !== void 0;
9166
- this.networkVideoController.stop();
9167
10622
  this.generationVideoFormat = void 0;
9168
10623
  this.disposePermissionWatch();
9169
10624
  try {
9170
- if (hadExternalSource) {
9171
- await this.rtcManager.destroy();
9172
- } else {
9173
- await this.cameraController.stopLocalCameraStream();
9174
- }
10625
+ await this.mediaController.stopLocalStream();
9175
10626
  } catch (error) {
9176
10627
  XmaxLogger.realtime.error(
9177
10628
  () => `\u91CA\u653E\u672C\u5730\u5A92\u4F53\u5931\u8D25 (Failed to Release Local Media)
9178
10629
  \u2514\u2500 ${XmaxLogger.localized("\u539F\u56E0\uFF1A", "Reason: ")}${XmaxError.from(error).message}`
9179
10630
  );
9180
- } finally {
9181
- this.externalVideoController.stop();
9182
- if (this.localVideoController.isActive) {
9183
- await this.localVideoController.stop();
9184
- }
9185
10631
  }
9186
10632
  }
9187
10633
  return {
@@ -9706,9 +11152,9 @@ var XmaxClient = class {
9706
11152
  /**
9707
11153
  * 创建媒体处理与能力查询 Service。
9708
11154
  *
9709
- * @param model 媒体输入规则使用的模型,默认为 `x2.0-trtc`。
11155
+ * @param model 媒体输入规则使用的模型,默认为 `x2.1.1-preview-cn`。
9710
11156
  */
9711
- createMediaService(model = "x2.0-trtc" /* x2_0_trtc */) {
11157
+ createMediaService(model = "x2.1.1-preview-cn" /* x2_1_1_preview_cn */) {
9712
11158
  return new MediaService(model);
9713
11159
  }
9714
11160
  /**
@@ -9726,7 +11172,7 @@ var RealtimeConfiguration = class {
9726
11172
  /**
9727
11173
  * 创建实时业务配置。
9728
11174
  *
9729
- * @param init.model 实时生成业务使用的模型;未知名称原样透传,默认配置同 x2.1-preview。
11175
+ * @param init.model 实时生成业务使用的模型;未知名称原样透传,默认配置同 x2.1.1-preview-cn。
9730
11176
  * @param init.provider RTC 提供方,默认按模型定义选择;必须受模型支持,创建后不可切换。
9731
11177
  * @param init.isFrameInterpolationEnabled 是否默认开启远端生成画面的插帧,默认 false。
9732
11178
  */
@@ -9759,13 +11205,6 @@ var RealtimeConfiguration = class {
9759
11205
  }
9760
11206
  };
9761
11207
 
9762
- // src/Foundation/Media/Video/VideoContentMode.ts
9763
- var VideoContentMode = /* @__PURE__ */ ((VideoContentMode2) => {
9764
- VideoContentMode2["fit"] = "fit";
9765
- VideoContentMode2["fill"] = "fill";
9766
- return VideoContentMode2;
9767
- })(VideoContentMode || {});
9768
-
9769
11208
  // src/Render/Video/RenderStatisticsController.ts
9770
11209
  var RenderStatisticsController = class {
9771
11210
  /**
@@ -9906,6 +11345,75 @@ var RenderStatisticsController = class {
9906
11345
  }
9907
11346
  };
9908
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
+
9909
11417
  // ../../node_modules/.pnpm/framegen@1.4.0/node_modules/framegen/rt.js
9910
11418
  var WG = 8;
9911
11419
  var texBgSeq = 0;
@@ -12275,6 +13783,498 @@ var RemoteVideoFramePipeline = class {
12275
13783
  }
12276
13784
  };
12277
13785
 
13786
+ // src/Render/Trajectory/TrajectoryOverlayView.ts
13787
+ var TrajectoryOverlayView = class {
13788
+ /**
13789
+ * 安装透明交互层及独立 Renderer;只有任务就绪时接管指针。
13790
+ */
13791
+ constructor(renderer) {
13792
+ __publicField(this, "renderer", renderer);
13793
+ /**
13794
+ * 视图与显示配置
13795
+ */
13796
+ __publicField(this, "element", document.createElement("div"));
13797
+ __publicField(this, "binding");
13798
+ __publicField(this, "format");
13799
+ __publicField(this, "contentMode", "fill" /* fill */);
13800
+ __publicField(this, "mirrored", false);
13801
+ __publicField(this, "requested", true);
13802
+ __publicField(this, "enabled", false);
13803
+ /**
13804
+ * 手势采样与生命周期资源
13805
+ */
13806
+ __publicField(this, "pointers", /* @__PURE__ */ new Map());
13807
+ __publicField(this, "nextID", 0);
13808
+ __publicField(this, "animation");
13809
+ __publicField(this, "lastSample", 0);
13810
+ __publicField(this, "resizeObserver");
13811
+ __publicField(this, "viewport");
13812
+ __publicField(this, "visibilityChanged", () => {
13813
+ if (document.hidden) {
13814
+ this.cancel();
13815
+ }
13816
+ this.updateStyle();
13817
+ });
13818
+ __publicField(this, "onDown", (event) => this.begin(event));
13819
+ __publicField(this, "onMove", (event) => this.move(event));
13820
+ __publicField(this, "onEnd", (event) => this.end(event.pointerId));
13821
+ Object.assign(this.element.style, { position: "absolute", inset: "0", zIndex: "2", overflow: "hidden" });
13822
+ Object.assign(renderer.element.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
13823
+ this.element.appendChild(renderer.element);
13824
+ this.element.addEventListener("pointerdown", this.onDown);
13825
+ this.element.addEventListener("pointermove", this.onMove);
13826
+ this.element.addEventListener("pointerup", this.onEnd);
13827
+ this.element.addEventListener("pointercancel", this.onEnd);
13828
+ this.element.addEventListener("lostpointercapture", this.onEnd);
13829
+ document.addEventListener("visibilitychange", this.visibilityChanged);
13830
+ if (typeof ResizeObserver !== "undefined") {
13831
+ this.resizeObserver = new ResizeObserver(() => this.cancel());
13832
+ this.resizeObserver.observe(this.element);
13833
+ }
13834
+ this.updateStyle();
13835
+ }
13836
+ /**
13837
+ * 更新任务绑定,清除旧任务手势和采样。
13838
+ */
13839
+ setBinding(binding, format, enabled = false) {
13840
+ this.cancel();
13841
+ this.binding = binding;
13842
+ this.format = format;
13843
+ this.enabled = enabled;
13844
+ this.updateStyle();
13845
+ }
13846
+ /**
13847
+ * 同步显示配置;配置变化后旧坐标不再继续发送。
13848
+ */
13849
+ configure(mode, mirrored, enabled) {
13850
+ if (this.contentMode !== mode || this.mirrored !== mirrored || this.requested !== enabled) {
13851
+ this.cancel();
13852
+ }
13853
+ this.contentMode = mode;
13854
+ this.mirrored = mirrored;
13855
+ this.requested = enabled;
13856
+ this.updateStyle();
13857
+ }
13858
+ /**
13859
+ * 卸载时取消采样、动画、捕获和事件监听;Renderer 可以再次挂载。
13860
+ */
13861
+ dispose() {
13862
+ this.binding?.detach(this);
13863
+ this.cancel();
13864
+ this.resizeObserver?.disconnect();
13865
+ document.removeEventListener("visibilitychange", this.visibilityChanged);
13866
+ this.element.removeEventListener("pointerdown", this.onDown);
13867
+ this.element.removeEventListener("pointermove", this.onMove);
13868
+ this.element.removeEventListener("pointerup", this.onEnd);
13869
+ this.element.removeEventListener("pointercancel", this.onEnd);
13870
+ this.element.removeEventListener("lostpointercapture", this.onEnd);
13871
+ this.element.remove();
13872
+ }
13873
+ /**
13874
+ * 无任务或被禁用时不阻止页面滚动。
13875
+ */
13876
+ updateStyle() {
13877
+ const active = this.requested && this.enabled && !!this.binding && !document.hidden;
13878
+ this.element.style.pointerEvents = active ? "auto" : "none";
13879
+ this.element.style.touchAction = active ? "none" : "auto";
13880
+ this.element.style.userSelect = active ? "none" : "auto";
13881
+ }
13882
+ /**
13883
+ * 根据实际显示区域拒绝黑边,移动到视口外时限制在可见视频边缘。
13884
+ */
13885
+ point(event, id, clamp) {
13886
+ const viewport = this.element.getBoundingClientRect();
13887
+ if (!this.format) {
13888
+ return void 0;
13889
+ }
13890
+ const rect = InteractionCoordinateMapper.displayedFrame(viewport, this.format, this.contentMode);
13891
+ if (!rect || !Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
13892
+ return void 0;
13893
+ }
13894
+ const minX = Math.max(0, rect.x);
13895
+ const minY = Math.max(0, rect.y);
13896
+ const maxX = Math.min(viewport.width, rect.x + rect.width);
13897
+ const maxY = Math.min(viewport.height, rect.y + rect.height);
13898
+ let x = event.clientX - viewport.left;
13899
+ let y = event.clientY - viewport.top;
13900
+ if (!clamp && (x < minX || x > maxX || y < minY || y > maxY)) {
13901
+ return void 0;
13902
+ }
13903
+ x = Math.max(minX, Math.min(maxX, x));
13904
+ y = Math.max(minY, Math.min(maxY, y));
13905
+ return Object.freeze({
13906
+ id,
13907
+ location: Object.freeze({ x, y }),
13908
+ normalizedLocation: Object.freeze({ x: (x - rect.x) / rect.width, y: (y - rect.y) / rect.height }),
13909
+ timestamp: performance.now()
13910
+ });
13911
+ }
13912
+ /**
13913
+ * 按下立即采样,不等待定时窗口;只接受鼠标主键。
13914
+ */
13915
+ begin(event) {
13916
+ if (!this.requested || !this.enabled || !this.binding || document.hidden || event.button !== 0) {
13917
+ return;
13918
+ }
13919
+ const point = this.point(event, ++this.nextID, false);
13920
+ if (!point) {
13921
+ return;
13922
+ }
13923
+ event.preventDefault();
13924
+ this.pointers.set(event.pointerId, point);
13925
+ try {
13926
+ this.element.setPointerCapture(event.pointerId);
13927
+ } catch {
13928
+ }
13929
+ this.render(() => this.renderer.renderBegan([point]));
13930
+ this.sample(performance.now(), true);
13931
+ this.schedule();
13932
+ }
13933
+ /**
13934
+ * 高频指针事件只更新最新位置及视觉效果,不直接发送网络消息。
13935
+ */
13936
+ move(event) {
13937
+ const previous = this.pointers.get(event.pointerId);
13938
+ if (!previous) {
13939
+ return;
13940
+ }
13941
+ const point = this.point(event, previous.id, true);
13942
+ if (point) {
13943
+ this.pointers.set(event.pointerId, point);
13944
+ this.render(() => this.renderer.renderMoved([point]));
13945
+ }
13946
+ }
13947
+ /**
13948
+ * 释放单个指针,多指交互继续;最后一个指针释放后停止采样。
13949
+ */
13950
+ end(pointerID) {
13951
+ const point = this.pointers.get(pointerID);
13952
+ if (!point) {
13953
+ return;
13954
+ }
13955
+ this.pointers.delete(pointerID);
13956
+ try {
13957
+ this.element.releasePointerCapture(pointerID);
13958
+ } catch {
13959
+ }
13960
+ this.render(() => this.renderer.renderEnded([point.id]));
13961
+ if (!this.pointers.size) {
13962
+ this.stopSampling();
13963
+ }
13964
+ }
13965
+ /**
13966
+ * 取消全部手势及视觉效果,不保留跨生命周期轨迹。
13967
+ */
13968
+ cancel() {
13969
+ for (const id of [...this.pointers.keys()]) {
13970
+ this.end(id);
13971
+ }
13972
+ this.stopSampling();
13973
+ this.render(() => this.renderer.reset());
13974
+ }
13975
+ /**
13976
+ * 清理采样回调与视口快照。
13977
+ */
13978
+ stopSampling() {
13979
+ if (this.animation !== void 0) {
13980
+ cancelAnimationFrame(this.animation);
13981
+ }
13982
+ this.animation = void 0;
13983
+ this.viewport = void 0;
13984
+ this.lastSample = 0;
13985
+ }
13986
+ /**
13987
+ * 只在存在活动触点时运行动画时钟。
13988
+ */
13989
+ schedule() {
13990
+ if (this.animation === void 0 && this.pointers.size) {
13991
+ this.animation = requestAnimationFrame((now) => {
13992
+ this.animation = void 0;
13993
+ this.sample(now);
13994
+ this.schedule();
13995
+ });
13996
+ }
13997
+ }
13998
+ /**
13999
+ * 固定频率发送所有活动触点的快照,布局变化时取消而非发送旧坐标。
14000
+ */
14001
+ sample(now, force = false) {
14002
+ if (document.hidden) {
14003
+ this.cancel();
14004
+ return;
14005
+ }
14006
+ const { width, height } = this.element.getBoundingClientRect();
14007
+ if (this.viewport && (width !== this.viewport.width || height !== this.viewport.height)) {
14008
+ this.cancel();
14009
+ return;
14010
+ }
14011
+ this.viewport = { width, height };
14012
+ if (!force && now - this.lastSample < 1e3 / 30) {
14013
+ return;
14014
+ }
14015
+ this.lastSample = now;
14016
+ this.binding?.submit({
14017
+ points: [...this.pointers.values()].map((point) => point.location),
14018
+ viewportSize: this.viewport,
14019
+ contentMode: this.contentMode,
14020
+ mirrored: this.mirrored
14021
+ });
14022
+ }
14023
+ /**
14024
+ * 自定义效果异常不影响触点采集和网络发送。
14025
+ */
14026
+ render(action) {
14027
+ try {
14028
+ action();
14029
+ } catch {
14030
+ XmaxLogger.render.warning(() => "\u8F68\u8FF9\u6548\u679C\u7ED8\u5236\u5931\u8D25 (Trajectory Rendering Failed)");
14031
+ }
14032
+ }
14033
+ };
14034
+
14035
+ // src/Render/Trajectory/DefaultTrajectoryEffectRenderer.ts
14036
+ var VISUAL = {
14037
+ outerWidth: 18,
14038
+ outerBlur: 12,
14039
+ outerAlpha: 0.35,
14040
+ middleWidth: 10,
14041
+ middleBlur: 6,
14042
+ middleAlpha: 0.75,
14043
+ coreWidth: 3,
14044
+ headRadius: 5,
14045
+ headGlowRadius: 14,
14046
+ headGlowAlpha: 0.9,
14047
+ ringCount: 2,
14048
+ ringStartRadius: 10,
14049
+ ringSpacing: 14,
14050
+ ringLineWidth: 2,
14051
+ ringPulseSpeed: 1.2,
14052
+ ringMaxAlpha: 0.6,
14053
+ particleCount: 4,
14054
+ particleRadius: 3,
14055
+ particleOrbitRadius: 22,
14056
+ particleSpeed: 0.06,
14057
+ particleAlpha: 0.6,
14058
+ fadeAlpha: 0.05,
14059
+ idleFadeFrames: 64
14060
+ };
14061
+ var DefaultTrajectoryEffectRenderer = class {
14062
+ /**
14063
+ * 安装独立视觉图层,不注册指针事件。
14064
+ */
14065
+ constructor() {
14066
+ /**
14067
+ * 画布与动画资源;尾迹保留像素渐隐,触点装饰每帧重绘。
14068
+ */
14069
+ __publicField(this, "element", document.createElement("div"));
14070
+ __publicField(this, "trailCanvas", document.createElement("canvas"));
14071
+ __publicField(this, "effectCanvas", document.createElement("canvas"));
14072
+ __publicField(this, "animation");
14073
+ __publicField(this, "hasTrailPixels", false);
14074
+ __publicField(this, "idleFadeFrames", 0);
14075
+ /**
14076
+ * 活动触点和等待下一帧绘制的线段。
14077
+ */
14078
+ __publicField(this, "pointers", /* @__PURE__ */ new Map());
14079
+ __publicField(this, "segments", []);
14080
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
14081
+ Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
14082
+ this.element.appendChild(canvas);
14083
+ }
14084
+ }
14085
+ /**
14086
+ * 开始触点装饰,脉冲动画以本次按下时间为起点。
14087
+ */
14088
+ renderBegan(points) {
14089
+ for (const point of points) {
14090
+ this.pointers.set(point.id, { point, startedAt: point.timestamp });
14091
+ }
14092
+ this.idleFadeFrames = 0;
14093
+ this.schedule();
14094
+ }
14095
+ /**
14096
+ * 只绘制新线段,旧像素自然衰减,不反复加亮整条历史轨迹。
14097
+ */
14098
+ renderMoved(points) {
14099
+ for (const point of points) {
14100
+ const pointer = this.pointers.get(point.id);
14101
+ if (pointer) {
14102
+ this.segments.push({ from: pointer.point, to: point });
14103
+ pointer.point = point;
14104
+ }
14105
+ }
14106
+ this.schedule();
14107
+ }
14108
+ /**
14109
+ * 释放触点后移除装饰,仅保留尾迹的渐隐动画。
14110
+ */
14111
+ renderEnded(identifiers) {
14112
+ for (const id of identifiers) {
14113
+ this.pointers.delete(id);
14114
+ }
14115
+ this.schedule();
14116
+ }
14117
+ /**
14118
+ * 清除画面、待绘制数据及动画,允许同一 Renderer 再次挂载。
14119
+ */
14120
+ reset() {
14121
+ if (this.animation !== void 0) {
14122
+ cancelAnimationFrame(this.animation);
14123
+ }
14124
+ this.animation = void 0;
14125
+ this.pointers.clear();
14126
+ this.segments.length = 0;
14127
+ this.hasTrailPixels = false;
14128
+ this.idleFadeFrames = 0;
14129
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
14130
+ canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
14131
+ }
14132
+ }
14133
+ /**
14134
+ * 仅在触点、待绘制线段或残留尾迹存在时申请下一帧。
14135
+ */
14136
+ schedule() {
14137
+ if (this.animation === void 0 && (this.pointers.size || this.segments.length || this.hasTrailPixels)) {
14138
+ this.animation = requestAnimationFrame((now) => this.draw(now));
14139
+ }
14140
+ }
14141
+ /**
14142
+ * 同步画布分辨率,分别更新尾迹和触点视觉图层。
14143
+ */
14144
+ draw(now) {
14145
+ this.animation = void 0;
14146
+ const trail = this.trailCanvas.getContext("2d");
14147
+ const effects = this.effectCanvas.getContext("2d");
14148
+ if (!trail || !effects) {
14149
+ this.reset();
14150
+ return;
14151
+ }
14152
+ const rect = this.element.getBoundingClientRect();
14153
+ const dpr = globalThis.devicePixelRatio || 1;
14154
+ const width = Math.max(1, Math.round(rect.width * dpr));
14155
+ const height = Math.max(1, Math.round(rect.height * dpr));
14156
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
14157
+ if (canvas.width !== width || canvas.height !== height) {
14158
+ canvas.width = width;
14159
+ canvas.height = height;
14160
+ this.hasTrailPixels = false;
14161
+ }
14162
+ }
14163
+ if (this.hasTrailPixels) {
14164
+ trail.save();
14165
+ trail.globalCompositeOperation = "destination-out";
14166
+ trail.fillStyle = "rgba(0,0,0," + VISUAL.fadeAlpha + ")";
14167
+ trail.fillRect(0, 0, width, height);
14168
+ trail.restore();
14169
+ }
14170
+ if (this.segments.length) {
14171
+ this.drawSegments(trail, dpr);
14172
+ this.segments.length = 0;
14173
+ this.hasTrailPixels = true;
14174
+ this.idleFadeFrames = 0;
14175
+ }
14176
+ effects.clearRect(0, 0, width, height);
14177
+ effects.save();
14178
+ effects.globalCompositeOperation = "lighter";
14179
+ for (const { point, startedAt } of this.pointers.values()) {
14180
+ const x = point.location.x * dpr;
14181
+ const y = point.location.y * dpr;
14182
+ if (this.pointers.size === 1) {
14183
+ this.drawRings(effects, x, y, dpr, (now - startedAt) / 1e3);
14184
+ this.drawParticles(effects, x, y, dpr, (now - startedAt) / 1e3);
14185
+ }
14186
+ this.drawGlow(effects, x, y, VISUAL.headGlowRadius * dpr, VISUAL.headGlowAlpha, true);
14187
+ effects.fillStyle = "rgba(255,255,255,1)";
14188
+ effects.beginPath();
14189
+ effects.arc(x, y, VISUAL.headRadius * dpr, 0, Math.PI * 2);
14190
+ effects.fill();
14191
+ }
14192
+ effects.restore();
14193
+ if (this.pointers.size) {
14194
+ this.idleFadeFrames = 0;
14195
+ } else if (this.hasTrailPixels && ++this.idleFadeFrames > VISUAL.idleFadeFrames) {
14196
+ trail.clearRect(0, 0, width, height);
14197
+ this.hasTrailPixels = false;
14198
+ this.idleFadeFrames = 0;
14199
+ }
14200
+ this.schedule();
14201
+ }
14202
+ /**
14203
+ * 叠加绿色外晕、中层和白色线芯;多指时省略阴影降低绘制开销。
14204
+ */
14205
+ drawSegments(context, dpr) {
14206
+ const layers = [
14207
+ { width: VISUAL.outerWidth, blur: VISUAL.outerBlur, color: this.green(VISUAL.outerAlpha) },
14208
+ { width: VISUAL.middleWidth, blur: VISUAL.middleBlur, color: this.green(VISUAL.middleAlpha) },
14209
+ { width: VISUAL.coreWidth, blur: 0, color: "rgba(255,255,255,1)" }
14210
+ ];
14211
+ for (const layer of layers) {
14212
+ context.save();
14213
+ context.globalCompositeOperation = "lighter";
14214
+ context.lineCap = "round";
14215
+ context.lineJoin = "round";
14216
+ context.lineWidth = layer.width * dpr;
14217
+ context.strokeStyle = layer.color;
14218
+ context.shadowBlur = this.pointers.size > 1 ? 0 : layer.blur * dpr;
14219
+ context.shadowColor = context.shadowBlur ? layer.color : "transparent";
14220
+ context.beginPath();
14221
+ for (const { from, to } of this.segments) {
14222
+ context.moveTo(from.location.x * dpr, from.location.y * dpr);
14223
+ context.lineTo(to.location.x * dpr, to.location.y * dpr);
14224
+ }
14225
+ context.stroke();
14226
+ context.restore();
14227
+ }
14228
+ }
14229
+ /**
14230
+ * 绘制触点周围两层不同相位的脉冲圆环。
14231
+ */
14232
+ drawRings(context, x, y, dpr, elapsed) {
14233
+ context.lineWidth = VISUAL.ringLineWidth * dpr;
14234
+ for (let index = 0; index < VISUAL.ringCount; index++) {
14235
+ const pulse = (Math.sin((elapsed * VISUAL.ringPulseSpeed + index * 0.5) * Math.PI * 2) + 1) / 2;
14236
+ const radius = (VISUAL.ringStartRadius + index * VISUAL.ringSpacing + pulse * 6) * dpr;
14237
+ context.strokeStyle = this.green(VISUAL.ringMaxAlpha * (1 - index * 0.2) * (0.5 + pulse * 0.5));
14238
+ context.beginPath();
14239
+ context.arc(x, y, radius, 0, Math.PI * 2);
14240
+ context.stroke();
14241
+ }
14242
+ }
14243
+ /**
14244
+ * 绘制四个反向交错运动的柔和环绕粒子。
14245
+ */
14246
+ drawParticles(context, x, y, dpr, elapsed) {
14247
+ for (let index = 0; index < VISUAL.particleCount; index++) {
14248
+ const direction = index % 2 === 0 ? 1 : -1;
14249
+ const angle = index / VISUAL.particleCount * Math.PI * 2 + elapsed * VISUAL.particleSpeed * direction;
14250
+ const radius = VISUAL.particleOrbitRadius * dpr;
14251
+ const alpha = VISUAL.particleAlpha * (0.6 + Math.sin(elapsed * 3 + index) * 0.4);
14252
+ this.drawGlow(context, x + Math.cos(angle) * radius, y + Math.sin(angle) * radius, VISUAL.particleRadius * 2 * dpr, alpha);
14253
+ }
14254
+ }
14255
+ /**
14256
+ * 绘制径向绿色光晕,触点核心增加中间色阶。
14257
+ */
14258
+ drawGlow(context, x, y, radius, alpha, head = false) {
14259
+ const gradient = context.createRadialGradient(x, y, 0, x, y, radius);
14260
+ gradient.addColorStop(0, this.green(alpha));
14261
+ if (head) {
14262
+ gradient.addColorStop(0.4, this.green(alpha * 0.6));
14263
+ }
14264
+ gradient.addColorStop(1, this.green(0));
14265
+ context.fillStyle = gradient;
14266
+ context.beginPath();
14267
+ context.arc(x, y, radius, 0, Math.PI * 2);
14268
+ context.fill();
14269
+ }
14270
+ /**
14271
+ * 统一轨迹主题色,透明度由各视觉层指定。
14272
+ */
14273
+ green(alpha) {
14274
+ return "rgba(0,255,100," + alpha + ")";
14275
+ }
14276
+ };
14277
+
12278
14278
  // src/Render/Video/XmaxVideoView.ts
12279
14279
  var OBJECT_FIT = {
12280
14280
  ["fit" /* fit */]: "contain",
@@ -12328,15 +14328,27 @@ var XmaxVideoView = class {
12328
14328
  * 呈现统计组件
12329
14329
  */
12330
14330
  __publicField(this, "statisticsController");
14331
+ /**
14332
+ * 远端画面预热与渐入组件
14333
+ */
14334
+ __publicField(this, "warmupController");
12331
14335
  /**
12332
14336
  * DOM 挂载状态
12333
14337
  */
12334
14338
  __publicField(this, "detached", false);
14339
+ /**
14340
+ * 轨迹交互配置与懒加载视图
14341
+ */
14342
+ __publicField(this, "interactionEnabled", true);
14343
+ __publicField(this, "customTrajectoryRenderer");
14344
+ __publicField(this, "trajectoryOverlay");
12335
14345
  this.contentMode = options?.videoContentMode ?? "fill" /* fill */;
14346
+ this.interactionEnabled = options?.isInteractionEnabled ?? true;
12336
14347
  this.element = document.createElement("div");
12337
14348
  this.element.style.position = "relative";
12338
14349
  this.element.style.overflow = "hidden";
12339
14350
  this.element.style.backgroundColor = "black";
14351
+ this.warmupController = new RenderWarmupController(this.element);
12340
14352
  this.videoElement = document.createElement("video");
12341
14353
  this.statisticsController = new RenderStatisticsController(this.videoElement);
12342
14354
  this.videoElement.autoplay = true;
@@ -12349,6 +14361,36 @@ var XmaxVideoView = class {
12349
14361
  this.applyContentMode();
12350
14362
  this.element.appendChild(this.videoElement);
12351
14363
  }
14364
+ /**
14365
+ * 是否允许远端轨迹交互;本地轨道没有交互绑定,不受此开关影响。
14366
+ */
14367
+ get isInteractionEnabled() {
14368
+ return this.interactionEnabled;
14369
+ }
14370
+ /**
14371
+ * 关闭时立即取消当前手势,不影响视频播放。
14372
+ */
14373
+ set isInteractionEnabled(enabled) {
14374
+ this.interactionEnabled = enabled;
14375
+ this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, enabled && !this.warmupController.isPending);
14376
+ }
14377
+ /**
14378
+ * 自定义轨迹视觉效果;undefined 表示使用 SDK 默认效果。
14379
+ */
14380
+ get trajectoryRenderer() {
14381
+ return this.customTrajectoryRenderer;
14382
+ }
14383
+ /**
14384
+ * 替换效果前释放旧动画,采样及网络发送仍由 SDK 管理。
14385
+ */
14386
+ set trajectoryRenderer(renderer) {
14387
+ if (this.customTrajectoryRenderer === renderer) {
14388
+ return;
14389
+ }
14390
+ this.disposeTrajectory();
14391
+ this.customTrajectoryRenderer = renderer;
14392
+ this.attachTrajectory();
14393
+ }
12352
14394
  /**
12353
14395
  * 当前显示的视频轨道;切换时自动解绑旧轨道。
12354
14396
  */
@@ -12421,6 +14463,7 @@ var XmaxVideoView = class {
12421
14463
  attach(to) {
12422
14464
  this.detached = false;
12423
14465
  to.appendChild(this.element);
14466
+ this.attachTrajectory();
12424
14467
  if ((this.videoElement.srcObject || this.videoElement.getAttribute("src")) && this.videoElement.autoplay && !this.videoElement.ended && this.videoElement.paused && typeof this.videoElement.play === "function") {
12425
14468
  void this.videoElement.play().catch((error) => {
12426
14469
  XmaxLogger.render.warning(() => `\u6062\u590D\u89C6\u9891\u64AD\u653E\u5931\u8D25 (Failed to Resume Video Playback)
@@ -12430,6 +14473,7 @@ var XmaxVideoView = class {
12430
14473
  this.startInterpolation();
12431
14474
  this.startRenderStatistics();
12432
14475
  if ((this.videoElement.srcObject || this.videoElement.getAttribute("src")) && !this.hasNotifiedFrameDisplay) {
14476
+ this.prepareRenderWarmup();
12433
14477
  this.armFrameDisplayNotification();
12434
14478
  }
12435
14479
  }
@@ -12438,9 +14482,12 @@ var XmaxVideoView = class {
12438
14482
  */
12439
14483
  detach() {
12440
14484
  this.detached = true;
14485
+ this.disposeTrajectory();
12441
14486
  this.statisticsController.stop();
12442
14487
  this.stopInterpolation();
12443
14488
  this.cancelFrameDisplayNotification();
14489
+ this.warmupController.reset();
14490
+ this.hasNotifiedFrameDisplay = false;
12444
14491
  if (this.videoElement.getAttribute("src")) {
12445
14492
  this.videoElement.pause();
12446
14493
  }
@@ -12450,6 +14497,7 @@ var XmaxVideoView = class {
12450
14497
  * 设置网络视频预览;静音播放一次,不捕获或发布媒体轨道。 @internal
12451
14498
  */
12452
14499
  setVideoURL(url) {
14500
+ this.warmupController.reset();
12453
14501
  this.statisticsController.stop();
12454
14502
  this.stopInterpolation();
12455
14503
  this.cancelFrameDisplayNotification();
@@ -12478,13 +14526,27 @@ var XmaxVideoView = class {
12478
14526
  this.cancelFrameDisplayNotification();
12479
14527
  this.hasNotifiedFrameDisplay = false;
12480
14528
  this.videoElement.srcObject = stream;
14529
+ this.warmupController.reset();
12481
14530
  if (stream) {
12482
14531
  this.videoElement.autoplay = true;
14532
+ this.prepareRenderWarmup();
12483
14533
  this.armFrameDisplayNotification();
12484
14534
  this.startInterpolation();
12485
14535
  this.startRenderStatistics();
12486
14536
  }
12487
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
+ }
12488
14550
  /**
12489
14551
  * 由远端绑定注入配置;本地预览不走此管线。 @internal
12490
14552
  */
@@ -12544,6 +14606,7 @@ var XmaxVideoView = class {
12544
14606
  const binding = VideoRenderRegistry.binding(track);
12545
14607
  if (binding) {
12546
14608
  binding.attachHandler(this, this.contentMode);
14609
+ this.attachTrajectory();
12547
14610
  return;
12548
14611
  }
12549
14612
  if (track.mediaStreamTrack) {
@@ -12558,6 +14621,8 @@ var XmaxVideoView = class {
12558
14621
  * 解绑当前轨道并清空渲染内容。
12559
14622
  */
12560
14623
  detachCurrentTrack() {
14624
+ this.warmupController.reset();
14625
+ this.disposeTrajectory();
12561
14626
  this.statisticsController.stop();
12562
14627
  const track = this.currentTrack;
12563
14628
  if (track) {
@@ -12584,8 +14649,15 @@ var XmaxVideoView = class {
12584
14649
  }
12585
14650
  this.hasNotifiedFrameDisplay = true;
12586
14651
  this.cancelFrameDisplayNotification();
14652
+ const notifiedSequence = this.frameNotificationSequence;
12587
14653
  binding?.frameDisplayHandler?.();
12588
14654
  this.frameDisplayHandler?.();
14655
+ if (notifiedSequence === this.frameNotificationSequence) {
14656
+ this.warmupController.start(() => {
14657
+ this.applyContentMode();
14658
+ binding?.renderWarmupCompletedHandler?.();
14659
+ });
14660
+ }
12589
14661
  };
12590
14662
  if (typeof this.videoElement.requestVideoFrameCallback === "function") {
12591
14663
  this.videoFrameCallbackID = this.videoElement.requestVideoFrameCallback(notify);
@@ -12613,10 +14685,19 @@ var XmaxVideoView = class {
12613
14685
  this.onPlaying = void 0;
12614
14686
  }
12615
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
+ }
12616
14696
  /**
12617
14697
  * 应用显示模式与镜像样式。
12618
14698
  */
12619
14699
  applyContentMode() {
14700
+ this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, this.interactionEnabled && !this.warmupController.isPending);
12620
14701
  this.videoElement.style.objectFit = OBJECT_FIT[this.contentMode];
12621
14702
  this.videoElement.style.transform = this.mirrored ? "scaleX(-1)" : "";
12622
14703
  if (this.interpolationCanvas) {
@@ -12630,6 +14711,30 @@ var XmaxVideoView = class {
12630
14711
  startRenderStatistics() {
12631
14712
  if (!this.detached) this.statisticsController.start();
12632
14713
  }
14714
+ /**
14715
+ * 仅为具备交互能力的轨道创建覆盖层,普通视频和本地预览不分配 Canvas。
14716
+ */
14717
+ attachTrajectory() {
14718
+ if (this.detached || this.trajectoryOverlay || !this.currentTrack) {
14719
+ return;
14720
+ }
14721
+ const binding = TrajectoryRegistry.binding(this.currentTrack);
14722
+ if (!binding) {
14723
+ return;
14724
+ }
14725
+ const overlay = new TrajectoryOverlayView(this.customTrajectoryRenderer ?? new DefaultTrajectoryEffectRenderer());
14726
+ this.trajectoryOverlay = overlay;
14727
+ overlay.configure(this.contentMode, this.mirrored, this.interactionEnabled && !this.warmupController.isPending);
14728
+ this.element.appendChild(overlay.element);
14729
+ binding.attach(overlay);
14730
+ }
14731
+ /**
14732
+ * 换轨或卸载时移除全部交互资源;再次挂载时重新建立绑定。
14733
+ */
14734
+ disposeTrajectory() {
14735
+ this.trajectoryOverlay?.dispose();
14736
+ this.trajectoryOverlay = void 0;
14737
+ }
12633
14738
  };
12634
14739
 
12635
14740
  // src/Render/Video/XmaxRealtimeVideoView.ts
@@ -12668,8 +14773,8 @@ var XmaxRealtimeVideoView = class {
12668
14773
  this.element.style.position = "relative";
12669
14774
  this.element.style.overflow = "hidden";
12670
14775
  this.element.style.backgroundColor = "black";
12671
- this.localVideoView = new XmaxVideoView({ videoContentMode: this.contentMode });
12672
- this.remoteVideoView = new XmaxVideoView({ videoContentMode: this.contentMode });
14776
+ this.localVideoView = new XmaxVideoView({ videoContentMode: this.contentMode, isInteractionEnabled: false });
14777
+ this.remoteVideoView = new XmaxVideoView({ videoContentMode: this.contentMode, isInteractionEnabled: options?.isInteractionEnabled });
12673
14778
  for (const view of [this.localVideoView, this.remoteVideoView]) {
12674
14779
  view.element.style.position = "absolute";
12675
14780
  view.element.style.inset = "0";
@@ -12684,6 +14789,30 @@ var XmaxRealtimeVideoView = class {
12684
14789
  get localTrack() {
12685
14790
  return this.currentLocalTrack;
12686
14791
  }
14792
+ /**
14793
+ * 只控制远端视图的轨迹交互,本地预览始终不接管手势。
14794
+ */
14795
+ get isInteractionEnabled() {
14796
+ return this.remoteVideoView.isInteractionEnabled;
14797
+ }
14798
+ /**
14799
+ * 转交远端交互开关。
14800
+ */
14801
+ set isInteractionEnabled(enabled) {
14802
+ this.remoteVideoView.isInteractionEnabled = enabled;
14803
+ }
14804
+ /**
14805
+ * 远端视图的自定义轨迹效果。
14806
+ */
14807
+ get trajectoryRenderer() {
14808
+ return this.remoteVideoView.trajectoryRenderer;
14809
+ }
14810
+ /**
14811
+ * undefined 恢复默认效果,不改变轨迹发送逻辑。
14812
+ */
14813
+ set trajectoryRenderer(renderer) {
14814
+ this.remoteVideoView.trajectoryRenderer = renderer;
14815
+ }
12687
14816
  /**
12688
14817
  * 替换本地预览轨道;轨道未变化时保留当前绑定。
12689
14818
  */
@@ -12696,7 +14825,7 @@ var XmaxRealtimeVideoView = class {
12696
14825
  }
12697
14826
  /**
12698
14827
  * 当前显示的远端生成视频轨道。
12699
- * 设置新轨道后等待首帧提交显示再渐入;置空时立即恢复本地预览。
14828
+ * SDK 远端轨道等待首帧及预热后渐入;置空时立即恢复本地预览。
12700
14829
  */
12701
14830
  get remoteTrack() {
12702
14831
  return this.currentRemoteTrack;
@@ -12757,18 +14886,23 @@ var XmaxRealtimeVideoView = class {
12757
14886
  this.element.remove();
12758
14887
  }
12759
14888
  /**
12760
- * 更新远端轨道绑定:重置渐入状态,等待首帧提交后再显示远端画面。
14889
+ * 更新远端轨道绑定;SDK 轨道共用单轨视图的预热,普通轨道等待首帧后渐入。
12761
14890
  */
12762
14891
  updateRemoteTrack() {
12763
14892
  const remoteTrack = this.currentRemoteTrack;
12764
14893
  this.remoteVideoView.frameDisplayHandler = void 0;
14894
+ this.remoteVideoView.track = void 0;
12765
14895
  this.remoteVideoView.element.style.transition = "";
12766
14896
  this.remoteVideoView.element.style.opacity = "0";
12767
14897
  this.remoteVideoView.element.style.visibility = "hidden";
12768
- this.remoteVideoView.track = void 0;
12769
14898
  if (!remoteTrack) {
12770
14899
  return;
12771
14900
  }
14901
+ if (VideoRenderRegistry.binding(remoteTrack)?.renderWarmupMs !== void 0) {
14902
+ this.remoteVideoView.element.style.visibility = "visible";
14903
+ this.remoteVideoView.track = remoteTrack;
14904
+ return;
14905
+ }
12772
14906
  this.remoteVideoView.frameDisplayHandler = () => {
12773
14907
  if (this.currentRemoteTrack !== remoteTrack) {
12774
14908
  return;
@@ -12815,6 +14949,9 @@ export {
12815
14949
  RealtimeVideoTrack,
12816
14950
  RealtimeMediaStream,
12817
14951
  RealtimeVideoSampleMethod,
14952
+ VideoContentMode,
14953
+ RealtimeRecordingEventType,
14954
+ RealtimeRecordingFinishReason,
12818
14955
  RealtimeContext,
12819
14956
  RealtimeConnectionState,
12820
14957
  RealtimeReason,
@@ -12825,8 +14962,7 @@ export {
12825
14962
  NonRealtimeTaskError,
12826
14963
  XmaxClient,
12827
14964
  RealtimeConfiguration,
12828
- VideoContentMode,
12829
14965
  XmaxVideoView,
12830
14966
  XmaxRealtimeVideoView
12831
14967
  };
12832
- //# sourceMappingURL=chunk-JGDZ3RD3.js.map
14968
+ //# sourceMappingURL=chunk-LOYJFYQT.js.map