@xmaxai/web-sdk 1.0.7 → 1.0.9

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.7";
208
+ var XMAX_SDK_VERSION = "1.0.9";
209
209
  var _RuntimeInfo = class _RuntimeInfo {
210
210
  /**
211
211
  * 序列化为接口传输使用的 snake_case 字段。
@@ -601,6 +601,9 @@ var RealtimeModel = /* @__PURE__ */ ((RealtimeModel2) => {
601
601
  RealtimeModel2["x2_0"] = "x2.0";
602
602
  RealtimeModel2["x2_0_trtc"] = "x2.0-trtc";
603
603
  RealtimeModel2["x2_1_preview"] = "x2.1-preview";
604
+ RealtimeModel2["x2_1_preview_1014"] = "x2.1-preview-1014";
605
+ RealtimeModel2["x2_1_1_preview_cn"] = "x2.1.1-preview-cn";
606
+ RealtimeModel2["x2_1_1_preview_us"] = "x2.1.1-preview-us";
604
607
  return RealtimeModel2;
605
608
  })(RealtimeModel || {});
606
609
  function isKnownModel(model) {
@@ -615,10 +618,16 @@ function modelDefaults(model) {
615
618
  var MODEL_DISPLAY_NAMES = {
616
619
  ["x2.0" /* x2_0 */]: "X2.0",
617
620
  ["x2.0-trtc" /* x2_0_trtc */]: "X2.1-preview-trtc",
618
- ["x2.1-preview" /* x2_1_preview */]: "X2.1-preview-agora"
621
+ ["x2.1-preview" /* x2_1_preview */]: "X2.1-preview-agora",
622
+ ["x2.1-preview-1014" /* x2_1_preview_1014 */]: "X2.1-preview-1014",
623
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "X2.1.1-preview-cn",
624
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "X2.1.1-preview-us"
619
625
  };
620
626
  var MODEL_BASE_URLS = {
621
- ["x2.1-preview" /* x2_1_preview */]: "https://cloud.xmax.22duck.cn/open/api/v1"
627
+ ["x2.1-preview" /* x2_1_preview */]: "https://cloud.xmax.22duck.cn/open/api/v1",
628
+ ["x2.1-preview-1014" /* x2_1_preview_1014 */]: "https://dev.xmaxai.com/open/api/v1",
629
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: "https://cloud.xmax.22duck.cn/open/api/v1",
630
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: "https://api.xmax.ai/open/api/v1"
622
631
  };
623
632
  var RESOLUTION_BUCKETS = {
624
633
  ["x2.0" /* x2_0 */]: [],
@@ -629,12 +638,27 @@ var RESOLUTION_BUCKETS = {
629
638
  ["x2.1-preview" /* x2_1_preview */]: [
630
639
  { width: 1024, height: 1920 },
631
640
  { width: 1920, height: 1024 }
641
+ ],
642
+ ["x2.1-preview-1014" /* x2_1_preview_1014 */]: [
643
+ { width: 1024, height: 1920 },
644
+ { width: 1920, height: 1024 }
645
+ ],
646
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: [
647
+ { width: 1024, height: 1920 },
648
+ { width: 1920, height: 1024 }
649
+ ],
650
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: [
651
+ { width: 1024, height: 1920 },
652
+ { width: 1920, height: 1024 }
632
653
  ]
633
654
  };
634
655
  var MAXIMUM_INPUT_PIXELS = {
635
656
  ["x2.0" /* x2_0 */]: 128e4,
636
657
  ["x2.0-trtc" /* x2_0_trtc */]: 21e5,
637
- ["x2.1-preview" /* x2_1_preview */]: 21e5
658
+ ["x2.1-preview" /* x2_1_preview */]: 21e5,
659
+ ["x2.1-preview-1014" /* x2_1_preview_1014 */]: 21e5,
660
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: 21e5,
661
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: 21e5
638
662
  };
639
663
  function modelDisplayName(model) {
640
664
  return isKnownModel(model) ? MODEL_DISPLAY_NAMES[model] : model;
@@ -666,7 +690,10 @@ function defaultCameraVideoFormat(model) {
666
690
  var MODEL_RTC_PROVIDERS = {
667
691
  ["x2.0" /* x2_0 */]: Object.freeze(["vertc" /* vertc */]),
668
692
  ["x2.0-trtc" /* x2_0_trtc */]: Object.freeze(["trtc" /* trtc */]),
669
- ["x2.1-preview" /* x2_1_preview */]: Object.freeze(["agora" /* agora */])
693
+ ["x2.1-preview" /* x2_1_preview */]: Object.freeze(["agora" /* agora */]),
694
+ ["x2.1-preview-1014" /* x2_1_preview_1014 */]: Object.freeze(["agora" /* agora */]),
695
+ ["x2.1.1-preview-cn" /* x2_1_1_preview_cn */]: Object.freeze(["agora" /* agora */]),
696
+ ["x2.1.1-preview-us" /* x2_1_1_preview_us */]: Object.freeze(["agora" /* agora */])
670
697
  };
671
698
  function supportedRtcProviders(model) {
672
699
  return isKnownModel(model) ? MODEL_RTC_PROVIDERS[model] : CUSTOM_MODEL_RTC_PROVIDERS;
@@ -1353,15 +1380,6 @@ var AgoraVideoSender = class {
1353
1380
  }
1354
1381
  throw new XmaxError("RTC_ERROR" /* rtcError */, "Browser did not retain WebRTC video sender parameters");
1355
1382
  }
1356
- XmaxLogger.rtc.info(() => `Agora \u53D1\u9001\u53C2\u6570\u5B9E\u9A8C (Agora Sender Parameters)
1357
- ${JSON.stringify({
1358
- capture: { width: capture.width, height: capture.height, frameRate: capture.frameRate },
1359
- target: { width: config.width, height: config.height, frameRate: config.frameRate },
1360
- scaleResolutionDownBy: actual.scaleResolutionDownBy,
1361
- maxFramerate: actual.maxFramerate,
1362
- maxBitrateKbps: actual.maxBitrate / 1e3,
1363
- minimumBitrateApplied: false
1364
- }, null, 2)}`, 2 /* performance */);
1365
1383
  }
1366
1384
  };
1367
1385
 
@@ -2133,6 +2151,12 @@ var _AgoraRtcManager = class _AgoraRtcManager {
2133
2151
  if (volume > 0 && this.requestedAudio.has(String(user.uid))) track.play();
2134
2152
  else track.stop();
2135
2153
  }
2154
+ /**
2155
+ * 获取原始远端音轨,不依赖声网音频播放器是否处于播放状态。
2156
+ */
2157
+ getRemoteAudioTrack(userID) {
2158
+ return this.client?.remoteUsers.find((user) => String(user.uid) === userID)?.audioTrack?.getMediaStreamTrack();
2159
+ }
2136
2160
  /**
2137
2161
  * 按房间顺序发送消息,限制每秒包数与总字节;失败返回给原业务调用。
2138
2162
  */
@@ -3031,6 +3055,15 @@ var TrtcRtcManager = class {
3031
3055
  get supportsRemoteAudioVolumeControl() {
3032
3056
  return canControlTrtcRemoteAudioVolume();
3033
3057
  }
3058
+ /**
3059
+ * 获取原始远端音轨;不支持独立播放音量的平台暂不开放录制。
3060
+ */
3061
+ getRemoteAudioTrack(userID) {
3062
+ if (!canControlTrtcRemoteAudioVolume()) {
3063
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "TRTC recording cannot preserve remote audio while muted on this platform");
3064
+ }
3065
+ return this.requireEngine().getAudioTrack(userID) ?? void 0;
3066
+ }
3034
3067
  /**
3035
3068
  * 设置指定远端用户的音频播放音量。
3036
3069
  *
@@ -3656,6 +3689,12 @@ var VeRtcManager = class {
3656
3689
  this.audioPlayers.set(userID, player);
3657
3690
  this.applyAudioVolume(userID);
3658
3691
  }
3692
+ /**
3693
+ * 获取原始远端音轨,独立于 audio 元素的静音和播放音量。
3694
+ */
3695
+ getRemoteAudioTrack(userID) {
3696
+ return this.requireEngine().getRemoteStreamTrack(userID, this.sdk.StreamIndex.STREAM_INDEX_MAIN, "audio");
3697
+ }
3659
3698
  setRemoteAudioVolume(volume, userID) {
3660
3699
  this.audioVolumes.set(userID, Number.isFinite(volume) ? Math.max(0, Math.min(100, Math.round(volume))) : 0);
3661
3700
  this.applyAudioVolume(userID);
@@ -3919,6 +3958,48 @@ function createRtcManager(provider, environment) {
3919
3958
  }
3920
3959
  }
3921
3960
 
3961
+ // src/Service/Realtime/RealtimeExtraParams.ts
3962
+ var RESERVED_PARAMS = /* @__PURE__ */ new Set([
3963
+ "model",
3964
+ "face_replace",
3965
+ "size",
3966
+ "target_size",
3967
+ "prompt",
3968
+ "ref_image_path",
3969
+ "mirror",
3970
+ "ref_video_path",
3971
+ "sample_method"
3972
+ ]);
3973
+ function snapshotExtraParams(params) {
3974
+ if (params === void 0) return void 0;
3975
+ const invalid2 = (message) => {
3976
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, message);
3977
+ };
3978
+ if (!params || typeof params !== "object" || Array.isArray(params)) {
3979
+ return invalid2("extraParams must be a JSON object");
3980
+ }
3981
+ for (const key of Object.keys(params)) {
3982
+ if (RESERVED_PARAMS.has(key)) return invalid2(`extraParams conflicts with SDK parameter: ${key}`);
3983
+ }
3984
+ const ancestors = /* @__PURE__ */ new Set();
3985
+ const validate = (value) => {
3986
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
3987
+ if (typeof value === "number" && Number.isFinite(value)) return;
3988
+ if (typeof value !== "object" || !value) return invalid2("extraParams must contain only JSON values");
3989
+ if (ancestors.has(value)) return invalid2("extraParams must not contain circular references");
3990
+ const prototype = Object.getPrototypeOf(value);
3991
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
3992
+ return invalid2("extraParams must contain only JSON objects and arrays");
3993
+ }
3994
+ if (Object.getOwnPropertySymbols(value).length) return invalid2("extraParams must not contain symbol keys");
3995
+ ancestors.add(value);
3996
+ for (const child of Array.isArray(value) ? value : Object.values(value)) validate(child);
3997
+ ancestors.delete(value);
3998
+ };
3999
+ validate(params);
4000
+ return JSON.parse(JSON.stringify(params));
4001
+ }
4002
+
3922
4003
  // src/Foundation/Media/Camera/CameraFrameValidator.ts
3923
4004
  var CAMERA_WARMUP_MS = 200;
3924
4005
  function waitForValidCameraFrame(track, signal) {
@@ -4413,121 +4494,26 @@ var _CameraController = class _CameraController {
4413
4494
  __publicField(_CameraController, "localVideoTrackID", "video0");
4414
4495
  var CameraController = _CameraController;
4415
4496
 
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();
4497
+ // src/Media/LocalMediaVideoFormat.ts
4498
+ function resolveLocalMediaVideoFormat(format, mediaService) {
4499
+ format.validate();
4500
+ const buckets = resolutionBuckets(mediaService.model);
4501
+ let target = buckets.find((size2) => size2.width === format.width && size2.height === format.height);
4502
+ if (!target && buckets.length > 0) {
4503
+ const ratio = format.width / format.height;
4504
+ let bestDistance = Number.POSITIVE_INFINITY;
4505
+ for (const bucket of buckets) {
4506
+ const distance = Math.abs(Math.log(bucket.width / bucket.height / ratio));
4507
+ const tied = Math.abs(distance - bestDistance) < 1e-9;
4508
+ if (distance < bestDistance - 1e-9 || tied && bucket.width >= bucket.height && target && target.width < target.height) {
4509
+ target = bucket;
4510
+ bestDistance = distance;
4506
4511
  }
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);
4512
+ }
4529
4513
  }
4530
- };
4514
+ const size = mediaService.resolveModelInputSize(target ?? format);
4515
+ return format.resized(size.width, size.height);
4516
+ }
4531
4517
 
4532
4518
  // src/Media/Video/LocalVideoController.ts
4533
4519
  var LocalVideoController = class {
@@ -4571,7 +4557,7 @@ var LocalVideoController = class {
4571
4557
  if (!(options.file instanceof Blob) || options.file.size === 0) {
4572
4558
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local video requires a non-empty File or Blob");
4573
4559
  }
4574
- const format = this.resolveVideoFormat(options.videoFormat, mediaService);
4560
+ const format = resolveLocalMediaVideoFormat(options.videoFormat, mediaService);
4575
4561
  if (signal.aborted) {
4576
4562
  throw this.cancelled();
4577
4563
  }
@@ -4798,29 +4784,6 @@ var LocalVideoController = class {
4798
4784
  context.fillRect(0, 0, canvas.width, canvas.height);
4799
4785
  context.drawImage(video, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);
4800
4786
  }
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
4787
  /**
4825
4788
  * 解码或绘制失败时停止媒体推进,并报告一次运行期错误。
4826
4789
  */
@@ -4842,101 +4805,1353 @@ var LocalVideoController = class {
4842
4805
  }
4843
4806
  };
4844
4807
 
4845
- // src/Service/Realtime/RealtimeContext.ts
4846
- var RealtimeContext = class {
4847
- /**
4848
- * 创建实时生成条件并规范化文本和参考路径。
4849
- */
4850
- constructor(init) {
4851
- /**
4852
- * 服务端直接读取的网络视频。
4853
- */
4854
- __publicField(this, "referenceVideo");
4855
- /**
4856
- * 生成条件
4857
- */
4858
- /**
4859
- * 实时生成使用的文本条件。
4860
- */
4861
- __publicField(this, "prompt");
4862
- /**
4863
- * 已上传参考图片的可选远端路径。
4864
- */
4865
- __publicField(this, "referencePath");
4866
- this.referenceVideo = init.referenceVideo && Object.freeze({ ...init.referenceVideo });
4867
- this.prompt = init.prompt.trim();
4868
- const normalizedReferencePath = init.referencePath?.trim();
4869
- this.referencePath = normalizedReferencePath || void 0;
4870
- }
4871
- };
4808
+ // src/Service/Realtime/RealtimeReferenceVideo.ts
4809
+ var RealtimeVideoSampleMethod = /* @__PURE__ */ ((RealtimeVideoSampleMethod2) => {
4810
+ RealtimeVideoSampleMethod2["time"] = "time";
4811
+ RealtimeVideoSampleMethod2["fps"] = "fps";
4812
+ return RealtimeVideoSampleMethod2;
4813
+ })(RealtimeVideoSampleMethod || {});
4872
4814
 
4873
- // src/Service/Realtime/RealtimeSession.ts
4874
- var RealtimeSession = class {
4875
- /**
4876
- * 创建实时会话。
4877
- *
4878
- * @param init.id 会话标识(sessionUid)。
4879
- * @param init.userID 业务用户标识。
4880
- * @param init.status 会话状态。
4881
- * @param init.connection RTC 连接参数。
4882
- * @param init.closeReason 会话关闭原因。
4883
- */
4884
- constructor(init) {
4885
- /**
4886
- * 会话信息
4887
- */
4888
- /**
4889
- * 会话标识(sessionUid)。
4890
- */
4891
- __publicField(this, "id");
4892
- /**
4893
- * 业务用户标识。
4894
- */
4895
- __publicField(this, "userID");
4896
- /**
4897
- * 运行状态
4898
- */
4899
- /**
4900
- * 会话状态(如 ACTIVE);响应未携带时为空。
4901
- */
4902
- __publicField(this, "status");
4903
- /**
4904
- * 会话关闭原因。
4905
- */
4906
- __publicField(this, "closeReason");
4815
+ // src/Media/Video/NetworkVideoController.ts
4816
+ var NetworkVideoController = class {
4817
+ constructor() {
4907
4818
  /**
4908
- * RTC 连接
4819
+ * 当前视频源与完成通知
4909
4820
  */
4821
+ __publicField(this, "currentTrack");
4822
+ __publicField(this, "reference");
4823
+ __publicField(this, "onFinish");
4910
4824
  /**
4911
- * RTC 连接参数;心跳等响应未携带完整参数时为空。
4825
+ * 预览进度与视图资源
4912
4826
  */
4913
- __publicField(this, "connection");
4914
- this.id = init.id;
4915
- this.userID = init.userID;
4916
- this.status = init.status;
4917
- this.connection = init.connection;
4918
- this.closeReason = init.closeReason;
4827
+ __publicField(this, "position", 0);
4828
+ __publicField(this, "ended", false);
4829
+ __publicField(this, "bindings", /* @__PURE__ */ new Map());
4919
4830
  }
4920
- };
4921
-
4922
- // src/Foundation/RTC/RoomJoinConfiguration.ts
4923
- var RoomJoinConfiguration = class {
4924
4831
  /**
4925
- * 创建房间加入参数。
4926
- *
4927
- * @param init.roomID TRTC 房间号。
4928
- * @param init.userID TRTC 登录身份。
4929
- * @param init.sdkAppID TRTC SDKAppID(字符串形式)。
4930
- * @param init.userSig TRTC 登录签名。
4931
- * @param init.privateMapKey TRTC 进房鉴权字段。
4832
+ * 创建网络视频源,校验地址、采样方式和模型支持的尺寸;不请求视频下载。
4932
4833
  */
4933
- constructor(init) {
4934
- /**
4935
- * RTC 提供方。
4936
- */
4937
- __publicField(this, "provider", "trtc" /* trtc */);
4938
- /**
4939
- * 进房参数
4834
+ create(options, mediaService) {
4835
+ let url;
4836
+ try {
4837
+ url = new URL(options.url);
4838
+ } catch {
4839
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an absolute HTTP or HTTPS URL");
4840
+ }
4841
+ if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password) {
4842
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Network video requires an HTTP or HTTPS URL without embedded credentials");
4843
+ }
4844
+ const sampleMethod = options.sampleMethod ?? "time" /* time */;
4845
+ if (!Object.values(RealtimeVideoSampleMethod).includes(sampleMethod)) {
4846
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Unsupported video sample method");
4847
+ }
4848
+ if (this.currentTrack) {
4849
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current network video stream before creating another one");
4850
+ }
4851
+ options.videoFormat.validate();
4852
+ const size = mediaService.resolveModelInputSize(options.videoFormat);
4853
+ const track = new RealtimeVideoTrack({
4854
+ id: "network-video",
4855
+ videoFormat: options.videoFormat.resized(size.width, size.height)
4856
+ });
4857
+ this.currentTrack = track;
4858
+ this.reference = Object.freeze({ path: options.url.trim(), sampleMethod });
4859
+ this.onFinish = options.onFinish;
4860
+ VideoRenderRegistry.register(track, {
4861
+ attachHandler: (view) => this.attach(view),
4862
+ detachHandler: (view) => this.detach(view)
4863
+ });
4864
+ return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
4865
+ }
4866
+ /**
4867
+ * 释放全部预览绑定及源信息,不影响其他媒体源。
4868
+ */
4869
+ stop() {
4870
+ for (const view of this.bindings.keys()) {
4871
+ this.detach(view);
4872
+ }
4873
+ if (this.currentTrack) {
4874
+ VideoRenderRegistry.unregister(this.currentTrack);
4875
+ }
4876
+ this.currentTrack = void 0;
4877
+ this.reference = void 0;
4878
+ this.onFinish = void 0;
4879
+ this.position = 0;
4880
+ this.ended = false;
4881
+ }
4882
+ /**
4883
+ * 绑定播放器,恢复预览位置;本地播放错误仅记录,不中断服务端生成。
4884
+ */
4885
+ attach(view) {
4886
+ if (!this.reference || !view.videoElement || !view.setVideoURL) {
4887
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "This render target does not support network video preview");
4888
+ }
4889
+ this.detach(view);
4890
+ const video = view.videoElement;
4891
+ video.autoplay = !this.ended;
4892
+ const restore = () => {
4893
+ if (this.position > 0) {
4894
+ video.currentTime = Math.min(this.position, Math.max(0, video.duration - 1e-3) || this.position);
4895
+ }
4896
+ if (this.ended) {
4897
+ video.pause();
4898
+ }
4899
+ };
4900
+ const fail = () => {
4901
+ XmaxLogger.render.warning(() => "\u7F51\u7EDC\u89C6\u9891\u9884\u89C8\u5931\u8D25 (Network Video Preview Failed)");
4902
+ };
4903
+ video.addEventListener("loadedmetadata", restore);
4904
+ video.addEventListener("error", fail);
4905
+ this.bindings.set(view, () => {
4906
+ this.position = video.currentTime;
4907
+ this.ended = video.ended || this.ended;
4908
+ video.removeEventListener("loadedmetadata", restore);
4909
+ video.removeEventListener("error", fail);
4910
+ view.setVideoURL?.(null);
4911
+ });
4912
+ view.isMirrored = false;
4913
+ view.setVideoURL(this.reference.path);
4914
+ }
4915
+ /**
4916
+ * 保存预览进度并释放该视图的地址和事件监听。
4917
+ */
4918
+ detach(view) {
4919
+ this.bindings.get(view)?.();
4920
+ this.bindings.delete(view);
4921
+ }
4922
+ };
4923
+
4924
+ // src/Media/Video/ExternalVideoController.ts
4925
+ var ExternalVideoController = class {
4926
+ constructor(onError) {
4927
+ __publicField(this, "onError", onError);
4928
+ __publicField(this, "track");
4929
+ __publicField(this, "onEnded", () => {
4930
+ this.onError(new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended"));
4931
+ });
4932
+ }
4933
+ get currentTrack() {
4934
+ return this.track;
4935
+ }
4936
+ create(options, mediaService) {
4937
+ if (this.track) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current external video first");
4938
+ const { videoTrack, videoFormat } = options;
4939
+ if (!videoTrack || videoTrack.kind !== "video" || videoTrack.readyState !== "live" || typeof videoTrack.clone !== "function") {
4940
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video requires a live video MediaStreamTrack");
4941
+ }
4942
+ videoFormat.validate();
4943
+ const size = mediaService.resolveModelInputSize(videoFormat);
4944
+ const format = videoFormat.resized(size.width, size.height);
4945
+ const nativeTrack = videoTrack.clone();
4946
+ if (nativeTrack.readyState !== "live") {
4947
+ nativeTrack.stop();
4948
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "External video source ended during setup");
4949
+ }
4950
+ const track = new RealtimeVideoTrack({ id: "external-video", videoFormat: format });
4951
+ track.mediaStreamTrack = nativeTrack;
4952
+ nativeTrack.addEventListener("ended", this.onEnded);
4953
+ this.track = track;
4954
+ return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
4955
+ }
4956
+ stop() {
4957
+ const nativeTrack = this.track?.mediaStreamTrack;
4958
+ this.track = void 0;
4959
+ nativeTrack?.removeEventListener("ended", this.onEnded);
4960
+ nativeTrack?.stop();
4961
+ }
4962
+ };
4963
+
4964
+ // src/Media/Image/LocalImageController.ts
4965
+ var LocalImageController = class {
4966
+ /**
4967
+ * 注入运行期错误通知,不创建浏览器资源。
4968
+ */
4969
+ constructor(onError) {
4970
+ __publicField(this, "onError", onError);
4971
+ /**
4972
+ * 当前图片源,包含尚未完成的解码操作。
4973
+ */
4974
+ __publicField(this, "source");
4975
+ }
4976
+ /**
4977
+ * 是否持有正在准备或可用的图片源。
4978
+ */
4979
+ get isActive() {
4980
+ return this.source !== void 0;
4981
+ }
4982
+ /**
4983
+ * 已准备的本地图片视频轨。
4984
+ */
4985
+ get currentTrack() {
4986
+ return this.source?.track;
4987
+ }
4988
+ /**
4989
+ * 解码图片、准备静止画面并持续输出帧;取消或失败时释放本次资源。
4990
+ */
4991
+ async create(options, mediaService, signal) {
4992
+ if (this.source) {
4993
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local image before creating another one");
4994
+ }
4995
+ if (!(options.file instanceof Blob) || options.file.size === 0) {
4996
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local image requires a non-empty File or Blob");
4997
+ }
4998
+ const format = resolveLocalMediaVideoFormat(options.videoFormat, mediaService);
4999
+ if (signal.aborted) {
5000
+ throw this.cancelled();
5001
+ }
5002
+ if (typeof document === "undefined") {
5003
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Local image requires a browser");
5004
+ }
5005
+ const image = document.createElement("img");
5006
+ const canvas = document.createElement("canvas");
5007
+ const context = canvas.getContext("2d");
5008
+ if (!context || typeof canvas.captureStream !== "function") {
5009
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Canvas video capture is unavailable");
5010
+ }
5011
+ canvas.width = format.width;
5012
+ canvas.height = format.height;
5013
+ const source = {
5014
+ image,
5015
+ canvas,
5016
+ url: URL.createObjectURL(options.file),
5017
+ abort: new AbortController(),
5018
+ targets: /* @__PURE__ */ new Set()
5019
+ };
5020
+ this.source = source;
5021
+ try {
5022
+ await this.load(source, signal);
5023
+ if (signal.aborted || this.source !== source) {
5024
+ throw this.cancelled();
5025
+ }
5026
+ if (!image.naturalWidth || !image.naturalHeight) {
5027
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "The image has no valid dimensions");
5028
+ }
5029
+ const scale = Math.min(canvas.width / image.naturalWidth, canvas.height / image.naturalHeight);
5030
+ const width = image.naturalWidth * scale;
5031
+ const height = image.naturalHeight * scale;
5032
+ context.fillStyle = "black";
5033
+ context.fillRect(0, 0, canvas.width, canvas.height);
5034
+ context.drawImage(image, (canvas.width - width) / 2, (canvas.height - height) / 2, width, height);
5035
+ source.stream = canvas.captureStream(format.fps);
5036
+ const nativeTrack = source.stream.getVideoTracks()[0];
5037
+ if (!nativeTrack || nativeTrack.readyState !== "live") {
5038
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Canvas did not provide a live video track");
5039
+ }
5040
+ const track = new RealtimeVideoTrack({ id: "local-image", videoFormat: format });
5041
+ track.mediaStreamTrack = nativeTrack;
5042
+ source.track = track;
5043
+ const refresh = () => {
5044
+ context.drawImage(canvas, 0, 0);
5045
+ if (typeof nativeTrack.requestFrame === "function") {
5046
+ nativeTrack.requestFrame();
5047
+ }
5048
+ };
5049
+ refresh();
5050
+ source.timer = setInterval(() => {
5051
+ if (this.source !== source) {
5052
+ return;
5053
+ }
5054
+ try {
5055
+ refresh();
5056
+ } catch {
5057
+ this.handleError(source);
5058
+ }
5059
+ }, 1e3 / format.fps);
5060
+ source.onEnded = () => this.handleError(source);
5061
+ nativeTrack.addEventListener("ended", source.onEnded);
5062
+ VideoRenderRegistry.register(track, {
5063
+ attachHandler: (view) => {
5064
+ if (this.source !== source) {
5065
+ return;
5066
+ }
5067
+ view.isMirrored = false;
5068
+ view.setMediaStream(source.stream);
5069
+ source.targets.add(view);
5070
+ },
5071
+ detachHandler: (view) => {
5072
+ source.targets.delete(view);
5073
+ view.setMediaStream(null);
5074
+ }
5075
+ });
5076
+ return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
5077
+ } catch (error) {
5078
+ if (this.source === source) {
5079
+ this.stop();
5080
+ }
5081
+ throw XmaxError.from(error);
5082
+ }
5083
+ }
5084
+ /**
5085
+ * 取消解码、清空预览并释放视频轨、定时器和对象 URL。
5086
+ */
5087
+ stop() {
5088
+ const source = this.source;
5089
+ this.source = void 0;
5090
+ if (!source) {
5091
+ return;
5092
+ }
5093
+ source.abort.abort();
5094
+ clearInterval(source.timer);
5095
+ if (source.track) {
5096
+ VideoRenderRegistry.unregister(source.track);
5097
+ if (source.onEnded) {
5098
+ source.track.mediaStreamTrack?.removeEventListener("ended", source.onEnded);
5099
+ }
5100
+ source.track.mediaStreamTrack = void 0;
5101
+ }
5102
+ for (const view of source.targets) {
5103
+ try {
5104
+ view.setMediaStream(null);
5105
+ } catch {
5106
+ }
5107
+ }
5108
+ source.targets.clear();
5109
+ source.stream?.getTracks().forEach((track) => track.stop());
5110
+ source.image.removeAttribute("src");
5111
+ URL.revokeObjectURL(source.url);
5112
+ source.canvas.width = 0;
5113
+ source.canvas.height = 0;
5114
+ }
5115
+ /**
5116
+ * 等待浏览器完成图片解码,支持主动停止、操作取消和超时。
5117
+ */
5118
+ load(source, signal) {
5119
+ return new Promise((resolve, reject) => {
5120
+ const finish = (error) => {
5121
+ clearTimeout(timer);
5122
+ source.image.removeEventListener("load", loaded);
5123
+ source.image.removeEventListener("error", failed);
5124
+ signal.removeEventListener("abort", aborted);
5125
+ source.abort.signal.removeEventListener("abort", aborted);
5126
+ if (error) {
5127
+ reject(error);
5128
+ } else {
5129
+ resolve();
5130
+ }
5131
+ };
5132
+ const loaded = () => finish();
5133
+ const failed = () => finish(new XmaxError("MEDIA_ERROR" /* mediaError */, "The browser cannot decode this image file"));
5134
+ const aborted = () => finish(this.cancelled());
5135
+ const timer = setTimeout(() => finish(new XmaxError("TIMEOUT" /* timeout */, "Local image preparation timed out")), 1e4);
5136
+ source.image.addEventListener("load", loaded, { once: true });
5137
+ source.image.addEventListener("error", failed, { once: true });
5138
+ signal.addEventListener("abort", aborted, { once: true });
5139
+ source.abort.signal.addEventListener("abort", aborted, { once: true });
5140
+ if (signal.aborted || source.abort.signal.aborted) {
5141
+ aborted();
5142
+ } else {
5143
+ source.image.src = source.url;
5144
+ }
5145
+ });
5146
+ }
5147
+ /**
5148
+ * 输出失效时停止刷新并仅报告一次,资源由业务生命周期统一释放。
5149
+ */
5150
+ handleError(source) {
5151
+ if (this.source !== source || source.timer === void 0) {
5152
+ return;
5153
+ }
5154
+ clearInterval(source.timer);
5155
+ source.timer = void 0;
5156
+ this.onError(new XmaxError("MEDIA_ERROR" /* mediaError */, "Local image video output failed"));
5157
+ }
5158
+ /**
5159
+ * 构造统一的图片操作取消错误。
5160
+ */
5161
+ cancelled() {
5162
+ return new XmaxError("CANCELLED" /* cancelled */, "Local image operation was cancelled");
5163
+ }
5164
+ };
5165
+
5166
+ // src/Foundation/Media/Video/VideoContentMode.ts
5167
+ var VideoContentMode = /* @__PURE__ */ ((VideoContentMode2) => {
5168
+ VideoContentMode2["fit"] = "fit";
5169
+ VideoContentMode2["fill"] = "fill";
5170
+ return VideoContentMode2;
5171
+ })(VideoContentMode || {});
5172
+
5173
+ // src/Service/Realtime/RealtimePoint.ts
5174
+ var RealtimePoint = class {
5175
+ /**
5176
+ * 创建模型输入像素坐标点。
5177
+ *
5178
+ * @param init.x 模型输入像素横坐标。
5179
+ * @param init.y 模型输入像素纵坐标。
5180
+ */
5181
+ constructor(init) {
5182
+ /**
5183
+ * 坐标
5184
+ */
5185
+ /**
5186
+ * 模型输入像素横坐标。
5187
+ */
5188
+ __publicField(this, "x");
5189
+ /**
5190
+ * 模型输入像素纵坐标。
5191
+ */
5192
+ __publicField(this, "y");
5193
+ this.x = init.x;
5194
+ this.y = init.y;
5195
+ }
5196
+ };
5197
+
5198
+ // src/Media/Interaction/InteractionCoordinateMapper.ts
5199
+ var InteractionCoordinateMapper = class {
5200
+ /**
5201
+ * 计算居中显示的视频矩形;fill 允许矩形超出视口,fit 留出黑边。
5202
+ */
5203
+ static displayedFrame(viewport, video, mode) {
5204
+ if (![viewport.width, viewport.height, video.width, video.height].every((value) => Number.isFinite(value) && value > 0)) {
5205
+ return void 0;
5206
+ }
5207
+ const scale = (mode === "fill" /* fill */ ? Math.max : Math.min)(viewport.width / video.width, viewport.height / video.height);
5208
+ const width = video.width * scale;
5209
+ const height = video.height * scale;
5210
+ return { x: (viewport.width - width) / 2, y: (viewport.height - height) / 2, width, height };
5211
+ }
5212
+ /**
5213
+ * 忽略黑边和无效点,撤销显示镜像后取整并限制在模型输入范围内。
5214
+ */
5215
+ static map(point, frame, video) {
5216
+ const rect = this.displayedFrame(frame.viewportSize, video, frame.contentMode);
5217
+ if (!rect || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
5218
+ return void 0;
5219
+ }
5220
+ 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)) {
5221
+ return void 0;
5222
+ }
5223
+ const x = (point.x - rect.x) / rect.width;
5224
+ const y = (point.y - rect.y) / rect.height;
5225
+ return new RealtimePoint({
5226
+ x: Math.min(video.width - 1, Math.max(0, Math.round((frame.mirrored ? 1 - x : x) * video.width))),
5227
+ y: Math.min(video.height - 1, Math.max(0, Math.round(y * video.height)))
5228
+ });
5229
+ }
5230
+ };
5231
+
5232
+ // src/Media/Interaction/InteractionController.ts
5233
+ var InteractionController = class {
5234
+ /**
5235
+ * 注入发送能力,不持有房间或具体 RTC。
5236
+ */
5237
+ constructor(listener) {
5238
+ __publicField(this, "listener", listener);
5239
+ /**
5240
+ * 活动任务与待发送采样;上下文对象身份隔离任务切换。
5241
+ */
5242
+ __publicField(this, "active");
5243
+ __publicField(this, "pending");
5244
+ __publicField(this, "draining");
5245
+ }
5246
+ /**
5247
+ * 激活或更新任务输入格式,清除旧采样。
5248
+ */
5249
+ startInteraction(taskID, videoFormat) {
5250
+ this.stopInteraction();
5251
+ if (taskID) {
5252
+ this.active = { taskID, videoFormat };
5253
+ }
5254
+ }
5255
+ /**
5256
+ * 立即使交互上下文失效;已提交给 RTC 的消息无法撤回。
5257
+ */
5258
+ stopInteraction() {
5259
+ this.active = void 0;
5260
+ this.pending = void 0;
5261
+ this.draining = void 0;
5262
+ }
5263
+ /**
5264
+ * 同步转换数据,发送忙时用最新采样覆盖旧采样,避免弱网积压。
5265
+ */
5266
+ submitInteraction(frame) {
5267
+ const context = this.active;
5268
+ if (!context) {
5269
+ return;
5270
+ }
5271
+ const points = frame.points.flatMap((point) => {
5272
+ const mapped = InteractionCoordinateMapper.map(point, frame, context.videoFormat);
5273
+ return mapped ? [mapped] : [];
5274
+ });
5275
+ if (!points.length) {
5276
+ return;
5277
+ }
5278
+ this.pending = { context, points };
5279
+ if (this.draining !== context) {
5280
+ void this.drain(context);
5281
+ }
5282
+ }
5283
+ /**
5284
+ * 顺序发送;失败只丢弃当前帧,不终止生成或阻止后续交互。
5285
+ */
5286
+ async drain(context) {
5287
+ this.draining = context;
5288
+ try {
5289
+ while (this.active === context && this.pending?.context === context) {
5290
+ const { points } = this.pending;
5291
+ this.pending = void 0;
5292
+ try {
5293
+ await this.listener(context.taskID, points);
5294
+ } catch {
5295
+ if (context === this.active) {
5296
+ XmaxLogger.interaction.warning(() => "\u53D1\u9001\u4EA4\u4E92\u8F68\u8FF9\u5931\u8D25\uFF0C\u5DF2\u4E22\u5F03\u5F53\u524D\u91C7\u6837\u5E27 (Interaction Sample Dropped)");
5297
+ }
5298
+ }
5299
+ }
5300
+ } finally {
5301
+ if (this.draining === context) {
5302
+ this.draining = void 0;
5303
+ }
5304
+ }
5305
+ }
5306
+ };
5307
+
5308
+ // src/Media/MediaController.ts
5309
+ var MediaController = class {
5310
+ /**
5311
+ * 注入基础能力与错误、交互输出,不自行创建跨层业务组件。
5312
+ */
5313
+ constructor(dependencies) {
5314
+ __publicField(this, "dependencies", dependencies);
5315
+ /**
5316
+ * 本地媒体与交互组件
5317
+ */
5318
+ __publicField(this, "camera");
5319
+ __publicField(this, "video");
5320
+ __publicField(this, "image");
5321
+ __publicField(this, "network", new NetworkVideoController());
5322
+ __publicField(this, "external");
5323
+ __publicField(this, "interaction");
5324
+ this.camera = dependencies.cameraController ?? new CameraController({
5325
+ rtcManager: dependencies.rtcManager,
5326
+ mediaService: dependencies.mediaService,
5327
+ errorListener: dependencies.onCameraError
5328
+ });
5329
+ this.video = new LocalVideoController(dependencies.onVideoError);
5330
+ this.image = new LocalImageController(dependencies.onImageError);
5331
+ this.external = new ExternalVideoController(dependencies.onExternalError);
5332
+ this.interaction = new InteractionController(dependencies.interactionListener);
5333
+ }
5334
+ /**
5335
+ * 当前可发布的轨道。
5336
+ */
5337
+ get uplinkTrack() {
5338
+ return this.external.currentTrack ?? this.video.currentTrack ?? this.image.currentTrack ?? this.camera.currentTrack;
5339
+ }
5340
+ /**
5341
+ * 当前本地源轨道,包含无上行的网络视频。
5342
+ */
5343
+ get currentTrack() {
5344
+ return this.uplinkTrack ?? this.network.currentTrack;
5345
+ }
5346
+ /**
5347
+ * 从实际资源推导来源,不维护另一份媒体状态。
5348
+ */
5349
+ get source() {
5350
+ if (this.external.currentTrack) {
5351
+ return "externalVideo";
5352
+ }
5353
+ if (this.video.isActive) {
5354
+ return "localVideo";
5355
+ }
5356
+ if (this.image.isActive) {
5357
+ return "localImage";
5358
+ }
5359
+ if (this.network.currentTrack) {
5360
+ return "networkVideo";
5361
+ }
5362
+ if (this.camera.currentTrack) {
5363
+ return "camera";
5364
+ }
5365
+ return void 0;
5366
+ }
5367
+ /**
5368
+ * 当前源需要发布的音频能力。
5369
+ */
5370
+ get hasAudio() {
5371
+ return this.video.isActive ? !!this.video.audioTrack : this.source === "camera" && this.camera.useMicrophone;
5372
+ }
5373
+ /**
5374
+ * 非相机源不等待相机预热。
5375
+ */
5376
+ get isFrameValidationEnabled() {
5377
+ return this.source === "camera" && this.camera.isFrameValidationEnabled;
5378
+ }
5379
+ /**
5380
+ * 网络视频的服务端读取参数。
5381
+ */
5382
+ get referenceVideo() {
5383
+ return this.network.reference;
5384
+ }
5385
+ /**
5386
+ * 网络视频完成回调。
5387
+ */
5388
+ get onNetworkVideoFinish() {
5389
+ return this.network.onFinish;
5390
+ }
5391
+ /**
5392
+ * 创建前拒绝覆盖活动源或尚未完成的文件准备。
5393
+ */
5394
+ ensureNoLocalSource() {
5395
+ if (this.currentTrack || this.video.isActive || this.image.isActive) {
5396
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local stream before creating another one");
5397
+ }
5398
+ }
5399
+ /**
5400
+ * 保持现有外部轨道能力校验与错误语义。
5401
+ */
5402
+ validateExternalMediaSupport(kind) {
5403
+ if (!this.dependencies.rtcManager.setExternalMediaTracks) {
5404
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, `The RTC provider does not support ${kind} video streams`);
5405
+ }
5406
+ }
5407
+ /**
5408
+ * 创建相机流,由相机组件管理 RTC 和预览。
5409
+ */
5410
+ createLocalCameraStream(options) {
5411
+ this.ensureNoLocalSource();
5412
+ return this.camera.createLocalCameraStream(options);
5413
+ }
5414
+ /**
5415
+ * 同步准备网络视频源;地址校验失败不初始化 RTC。
5416
+ */
5417
+ createNetworkVideoStream(options) {
5418
+ this.ensureNoLocalSource();
5419
+ return this.network.create(options, this.dependencies.mediaService);
5420
+ }
5421
+ /**
5422
+ * 先解锁用户触发的播放,再由 prepareSource 初始化 RTC。
5423
+ */
5424
+ createLocalVideoStream(options, signal) {
5425
+ this.ensureNoLocalSource();
5426
+ return this.video.create(options, this.dependencies.mediaService, signal);
5427
+ }
5428
+ /**
5429
+ * 解码本地图片并准备静止视频轨,不申请设备权限。
5430
+ */
5431
+ createLocalImageStream(options, signal) {
5432
+ this.ensureNoLocalSource();
5433
+ return this.image.create(options, this.dependencies.mediaService, signal);
5434
+ }
5435
+ /**
5436
+ * 接管外部视频轨副本,不停止调用方原始轨道。
5437
+ */
5438
+ createExternalVideoStream(options) {
5439
+ this.ensureNoLocalSource();
5440
+ return this.external.create(options, this.dependencies.mediaService);
5441
+ }
5442
+ /**
5443
+ * 初始化 RTC 并绑定非相机轨道;取消后不继续安装轨道。
5444
+ */
5445
+ async prepareSource(ensureCurrent) {
5446
+ const rtc = this.dependencies.rtcManager;
5447
+ await rtc.initialize();
5448
+ ensureCurrent();
5449
+ const track = this.external.currentTrack ?? this.video.currentTrack ?? this.image.currentTrack;
5450
+ if (track?.mediaStreamTrack) {
5451
+ await rtc.setExternalMediaTracks({ videoTrack: track.mediaStreamTrack, audioTrack: this.video.audioTrack });
5452
+ ensureCurrent();
5453
+ }
5454
+ }
5455
+ /**
5456
+ * 设置相机预览就绪通知。
5457
+ */
5458
+ setCameraPreviewReadyHandler(handler) {
5459
+ this.camera.setPreviewReadyHandler(handler);
5460
+ }
5461
+ /**
5462
+ * 等待相机预热。
5463
+ */
5464
+ waitForValidCameraFrame(signal) {
5465
+ return this.camera.waitForValidCameraFrame(signal);
5466
+ }
5467
+ /**
5468
+ * 切换相机;外部采集不由 SDK 控制。
5469
+ */
5470
+ switchCamera() {
5471
+ if (this.external.currentTrack) {
5472
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video capture is managed by the caller");
5473
+ }
5474
+ return this.camera.switchCamera();
5475
+ }
5476
+ /**
5477
+ * 停止相机资源。
5478
+ */
5479
+ stopLocalCameraStream() {
5480
+ return this.camera.stopLocalCameraStream();
5481
+ }
5482
+ /**
5483
+ * 生成开始后启动文件播放。
5484
+ */
5485
+ startLocalVideo(signal) {
5486
+ return this.video.start(signal);
5487
+ }
5488
+ /**
5489
+ * 断开时保留文件预览资源但暂停时间线。
5490
+ */
5491
+ pauseLocalVideo() {
5492
+ this.video.pause();
5493
+ }
5494
+ /**
5495
+ * 释放当前源,RTC 销毁失败也释放外部轨和解码器。
5496
+ */
5497
+ async stopLocalStream() {
5498
+ this.stopInteraction();
5499
+ const external = this.source !== void 0 && this.source !== "camera";
5500
+ this.network.stop();
5501
+ try {
5502
+ if (external) {
5503
+ await this.dependencies.rtcManager.destroy();
5504
+ } else {
5505
+ await this.camera.stopLocalCameraStream();
5506
+ }
5507
+ } finally {
5508
+ this.external.stop();
5509
+ if (this.image.isActive) {
5510
+ this.image.stop();
5511
+ }
5512
+ if (this.video.isActive) {
5513
+ await this.video.stop();
5514
+ }
5515
+ }
5516
+ }
5517
+ /**
5518
+ * 使用生成格式启用交互,不读取弱网调档后的上行规格。
5519
+ */
5520
+ startInteraction(taskID, format) {
5521
+ this.interaction.startInteraction(taskID, format);
5522
+ }
5523
+ /**
5524
+ * 结束任务时立即清除待发采样。
5525
+ */
5526
+ stopInteraction() {
5527
+ this.interaction.stopInteraction();
5528
+ }
5529
+ /**
5530
+ * 接收渲染层快照并交给坐标与发送队列处理。
5531
+ */
5532
+ submitInteraction(frame) {
5533
+ this.interaction.submitInteraction(frame);
5534
+ }
5535
+ };
5536
+
5537
+ // src/Service/Realtime/RealtimeRecordingEvent.ts
5538
+ var RealtimeRecordingEventType = /* @__PURE__ */ ((RealtimeRecordingEventType2) => {
5539
+ RealtimeRecordingEventType2["completed"] = "completed";
5540
+ RealtimeRecordingEventType2["interrupted"] = "interrupted";
5541
+ RealtimeRecordingEventType2["failed"] = "failed";
5542
+ return RealtimeRecordingEventType2;
5543
+ })(RealtimeRecordingEventType || {});
5544
+ var RealtimeRecordingFinishReason = /* @__PURE__ */ ((RealtimeRecordingFinishReason2) => {
5545
+ RealtimeRecordingFinishReason2["durationLimit"] = "durationLimit";
5546
+ RealtimeRecordingFinishReason2["generationEnded"] = "generationEnded";
5547
+ RealtimeRecordingFinishReason2["restarted"] = "restarted";
5548
+ RealtimeRecordingFinishReason2["mediaInterrupted"] = "mediaInterrupted";
5549
+ return RealtimeRecordingFinishReason2;
5550
+ })(RealtimeRecordingFinishReason || {});
5551
+
5552
+ // src/Media/Recording/RecordingController.ts
5553
+ var _RecordingController = class _RecordingController {
5554
+ constructor() {
5555
+ /**
5556
+ * 任务资源与结果监听
5557
+ */
5558
+ __publicField(this, "session");
5559
+ __publicField(this, "listener");
5560
+ }
5561
+ /**
5562
+ * 设置统一结果监听器;传入 undefined 清除,不停止正在进行的录制。
5563
+ */
5564
+ setListener(listener) {
5565
+ this.listener = listener;
5566
+ }
5567
+ /**
5568
+ * 开始准备当前任务的录制;准备和录制失败只通过独立事件通知。
5569
+ */
5570
+ start(taskID, readTracks) {
5571
+ if (this.session && !this.session.finished) {
5572
+ void this.stop("restarted" /* restarted */);
5573
+ }
5574
+ try {
5575
+ const tracks = readTracks();
5576
+ if (!tracks || tracks.videoTrack.readyState === "ended") {
5577
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Remote video is unavailable for recording");
5578
+ }
5579
+ if (typeof MediaRecorder === "undefined" || typeof AudioContext === "undefined") {
5580
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Audio/video recording is not supported by this browser");
5581
+ }
5582
+ let resolve;
5583
+ const completion = new Promise((done) => {
5584
+ resolve = done;
5585
+ });
5586
+ const session = {
5587
+ taskID,
5588
+ readTracks,
5589
+ videoTrack: tracks.videoTrack,
5590
+ chunks: [],
5591
+ bytes: 0,
5592
+ hasAudio: false,
5593
+ stopping: false,
5594
+ finished: false,
5595
+ reason: "durationLimit" /* durationLimit */,
5596
+ completion,
5597
+ resolve
5598
+ };
5599
+ this.session = session;
5600
+ session.timer = setTimeout(() => {
5601
+ this.fail(session, new XmaxError("TIMEOUT" /* timeout */, "Timed out preparing remote recording"));
5602
+ }, _RecordingController.preparationTimeoutMs);
5603
+ void this.prepare(session).catch((error) => this.fail(session, XmaxError.from(error)));
5604
+ } catch (error) {
5605
+ this.reportFailure(taskID, XmaxError.from(error));
5606
+ }
5607
+ }
5608
+ /**
5609
+ * 请求收尾并等待最终数据;重复停止共享完成结果,且绝不停止 RTC 原始轨道。
5610
+ */
5611
+ stop(reason) {
5612
+ const session = this.session;
5613
+ if (!session) {
5614
+ return Promise.resolve();
5615
+ }
5616
+ this.stopSession(session, reason);
5617
+ return session.completion;
5618
+ }
5619
+ /**
5620
+ * 不支持的录制请求使用同一错误出口,不改变生成生命周期。
5621
+ */
5622
+ reportFailure(taskID, error) {
5623
+ this.emit({ type: "failed" /* failed */, taskID, error });
5624
+ }
5625
+ /**
5626
+ * 使用固定音频输出轨处理晚到的音频;不在 MediaRecorder 运行中增删轨道。
5627
+ */
5628
+ async prepare(session) {
5629
+ const context = new AudioContext();
5630
+ session.audioContext = context;
5631
+ session.audioDestination = context.createMediaStreamDestination();
5632
+ const silence = context.createConstantSource();
5633
+ silence.offset.value = 0;
5634
+ silence.connect(session.audioDestination);
5635
+ silence.start();
5636
+ session.silenceSource = silence;
5637
+ await context.resume();
5638
+ if (session.stopping || session.finished) {
5639
+ return;
5640
+ }
5641
+ if (context.state !== "running") {
5642
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Browser did not allow recording audio to start");
5643
+ }
5644
+ this.updateAudio(session);
5645
+ const video = document.createElement("video");
5646
+ session.video = video;
5647
+ video.muted = true;
5648
+ video.playsInline = true;
5649
+ video.srcObject = new MediaStream([session.videoTrack]);
5650
+ const ready = () => {
5651
+ if (!session.stopping && !session.finished && video.readyState >= 2) {
5652
+ this.beginRecording(session);
5653
+ }
5654
+ };
5655
+ if (typeof video.requestVideoFrameCallback === "function") {
5656
+ session.frameCallback = video.requestVideoFrameCallback(ready);
5657
+ } else {
5658
+ video.onloadeddata = ready;
5659
+ video.onplaying = ready;
5660
+ }
5661
+ video.onerror = () => this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Cannot read remote recording video"));
5662
+ try {
5663
+ await video.play();
5664
+ } catch (error) {
5665
+ if (!session.recorder && !session.stopping && !session.finished) {
5666
+ throw error;
5667
+ }
5668
+ }
5669
+ if (typeof video.requestVideoFrameCallback !== "function") {
5670
+ ready();
5671
+ }
5672
+ }
5673
+ /**
5674
+ * 首帧可用后启动单个音视频录制器,计时不包含连接和媒体准备阶段。
5675
+ */
5676
+ beginRecording(session) {
5677
+ if (session.recorder || session.stopping || session.finished) {
5678
+ return;
5679
+ }
5680
+ try {
5681
+ session.hasAudio = false;
5682
+ this.updateAudio(session);
5683
+ const stream = new MediaStream([session.videoTrack, ...session.audioDestination.stream.getAudioTracks()]);
5684
+ const mimeType = ["video/mp4;codecs=avc1,mp4a.40.2", "video/webm;codecs=vp8,opus", "video/mp4", "video/webm"].find((type) => MediaRecorder.isTypeSupported(type));
5685
+ if (!mimeType) {
5686
+ throw new XmaxError("MEDIA_ERROR" /* mediaError */, "No supported audio/video recording format");
5687
+ }
5688
+ const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 6e6, audioBitsPerSecond: 128e3 });
5689
+ session.recorder = recorder;
5690
+ recorder.ondataavailable = (event) => {
5691
+ if (session.finished || session.error || !event.data.size) {
5692
+ return;
5693
+ }
5694
+ if (session.bytes + event.data.size > _RecordingController.maximumBytes) {
5695
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording exceeded the memory limit"));
5696
+ return;
5697
+ }
5698
+ session.chunks.push(event.data);
5699
+ session.bytes += event.data.size;
5700
+ };
5701
+ recorder.onerror = () => {
5702
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Remote recording encoder failed"));
5703
+ };
5704
+ recorder.onstop = () => {
5705
+ if (!session.stopping) {
5706
+ session.reason = "mediaInterrupted" /* mediaInterrupted */;
5707
+ }
5708
+ this.finish(session);
5709
+ };
5710
+ recorder.start(1e3);
5711
+ clearTimeout(session.timer);
5712
+ session.timer = setTimeout(() => this.stopSession(session, "durationLimit" /* durationLimit */), _RecordingController.durationMs);
5713
+ session.monitor = setInterval(() => {
5714
+ try {
5715
+ const tracks = session.readTracks();
5716
+ if (!tracks || tracks.videoTrack !== session.videoTrack || session.videoTrack.readyState === "ended") {
5717
+ this.stopSession(session, "mediaInterrupted" /* mediaInterrupted */);
5718
+ } else if (session.audioContext?.state !== "running") {
5719
+ this.fail(session, new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording audio context was suspended"));
5720
+ } else {
5721
+ this.updateAudio(session);
5722
+ }
5723
+ } catch (error) {
5724
+ this.fail(session, XmaxError.from(error));
5725
+ }
5726
+ }, 100);
5727
+ this.releaseVideoProbe(session);
5728
+ } catch (error) {
5729
+ this.fail(session, XmaxError.from(error));
5730
+ }
5731
+ }
5732
+ /**
5733
+ * 轮询仅在录制期间运行;接入原始音频,不连接扬声器,不受播放音量影响。
5734
+ */
5735
+ updateAudio(session) {
5736
+ const track = session.readTracks()?.audioTrack;
5737
+ const liveTrack = track?.readyState === "live" ? track : void 0;
5738
+ if (liveTrack !== session.audioTrack) {
5739
+ session.audioSource?.disconnect();
5740
+ session.audioSource = void 0;
5741
+ session.audioTrack = liveTrack;
5742
+ if (liveTrack) {
5743
+ session.audioSource = session.audioContext.createMediaStreamSource(new MediaStream([liveTrack]));
5744
+ session.audioSource.connect(session.audioDestination);
5745
+ }
5746
+ }
5747
+ if (liveTrack && !liveTrack.muted && liveTrack.enabled) {
5748
+ session.hasAudio = true;
5749
+ }
5750
+ }
5751
+ /**
5752
+ * 标记失败并尽力停止编码;任何错误都不能冒泡到实时生成。
5753
+ */
5754
+ fail(session, error) {
5755
+ if (session.finished) {
5756
+ return;
5757
+ }
5758
+ session.error ?? (session.error = error);
5759
+ this.stopSession(session, "mediaInterrupted" /* mediaInterrupted */);
5760
+ }
5761
+ /**
5762
+ * 保留最后一个 dataavailable,等待 stop 后再封装;异常路径有收尾超时保护。
5763
+ */
5764
+ stopSession(session, reason) {
5765
+ if (session.stopping || session.finished) {
5766
+ return;
5767
+ }
5768
+ session.stopping = true;
5769
+ session.reason = reason;
5770
+ clearTimeout(session.timer);
5771
+ clearInterval(session.monitor);
5772
+ this.releaseVideoProbe(session);
5773
+ if (!session.recorder || session.recorder.state === "inactive") {
5774
+ this.finish(session);
5775
+ return;
5776
+ }
5777
+ session.finalizationTimer = setTimeout(() => {
5778
+ session.error ?? (session.error = new XmaxError("TIMEOUT" /* timeout */, "Timed out finalizing remote recording"));
5779
+ this.finish(session);
5780
+ }, _RecordingController.finalizationTimeoutMs);
5781
+ try {
5782
+ session.recorder.stop();
5783
+ } catch (error) {
5784
+ session.error ?? (session.error = XmaxError.from(error));
5785
+ this.finish(session);
5786
+ }
5787
+ }
5788
+ /**
5789
+ * 一次性交付结果并释放所有自有资源,不保留文件副本。
5790
+ */
5791
+ finish(session) {
5792
+ if (session.finished) {
5793
+ return;
5794
+ }
5795
+ session.finished = true;
5796
+ clearTimeout(session.timer);
5797
+ clearTimeout(session.finalizationTimer);
5798
+ clearInterval(session.monitor);
5799
+ this.releaseVideoProbe(session);
5800
+ const blob = new Blob(session.chunks, { type: session.recorder?.mimeType ?? session.chunks[0]?.type });
5801
+ session.chunks = [];
5802
+ session.audioSource?.disconnect();
5803
+ session.silenceSource?.stop();
5804
+ session.silenceSource?.disconnect();
5805
+ session.audioDestination?.stream.getTracks().forEach((track) => track.stop());
5806
+ if (session.audioContext && session.audioContext.state !== "closed") {
5807
+ void session.audioContext.close().catch(() => {
5808
+ });
5809
+ }
5810
+ if (session.recorder) {
5811
+ session.recorder.ondataavailable = null;
5812
+ session.recorder.onerror = null;
5813
+ session.recorder.onstop = null;
5814
+ }
5815
+ if (this.session === session) {
5816
+ this.session = void 0;
5817
+ }
5818
+ if (session.error || !blob.size) {
5819
+ this.reportFailure(session.taskID, session.error ?? new XmaxError("MEDIA_ERROR" /* mediaError */, "Recording ended without usable media"));
5820
+ } else {
5821
+ void this.deliverResult(session.taskID, blob, session.hasAudio, session.reason);
5822
+ }
5823
+ session.resolve();
5824
+ }
5825
+ /**
5826
+ * 按需加载后处理,成功后回调;处理失败仍交付原文件,不丢失已完成的录像。
5827
+ */
5828
+ async deliverResult(taskID, original, hasAudio, reason) {
5829
+ let blob = original;
5830
+ try {
5831
+ const { RecordingProcessor } = await import("./RecordingProcessor-ZSBJFF4J.js");
5832
+ blob = await new RecordingProcessor().process(original);
5833
+ } catch (error) {
5834
+ XmaxLogger.realtime.warning(() => `Recording gap removal failed; keeping original clip: ${XmaxError.from(error).message}`);
5835
+ }
5836
+ this.emit({
5837
+ type: reason === "durationLimit" /* durationLimit */ ? "completed" /* completed */ : "interrupted" /* interrupted */,
5838
+ taskID,
5839
+ result: { blob, hasAudio, reason }
5840
+ });
5841
+ }
5842
+ /**
5843
+ * 释放首帧探测元素;srcObject 原始轨道不调用 stop。
5844
+ */
5845
+ releaseVideoProbe(session) {
5846
+ const video = session.video;
5847
+ if (!video) {
5848
+ return;
5849
+ }
5850
+ if (session.frameCallback !== void 0) {
5851
+ video.cancelVideoFrameCallback(session.frameCallback);
5852
+ }
5853
+ video.onloadeddata = null;
5854
+ video.onplaying = null;
5855
+ video.onerror = null;
5856
+ video.pause();
5857
+ video.srcObject = null;
5858
+ session.video = void 0;
5859
+ }
5860
+ /**
5861
+ * 隔离接入方监听器异常,包括异步监听器的 Promise 拒绝。
5862
+ */
5863
+ emit(event) {
5864
+ try {
5865
+ void Promise.resolve(this.listener?.(event)).catch(() => {
5866
+ XmaxLogger.realtime.warning(() => "Recording listener rejected");
5867
+ });
5868
+ } catch {
5869
+ XmaxLogger.realtime.warning(() => "Recording listener threw an error");
5870
+ }
5871
+ }
5872
+ };
5873
+ /**
5874
+ * 录制资源上限
5875
+ */
5876
+ __publicField(_RecordingController, "durationMs", 2e4);
5877
+ __publicField(_RecordingController, "maximumBytes", 64 * 1024 * 1024);
5878
+ __publicField(_RecordingController, "preparationTimeoutMs", 1e4);
5879
+ __publicField(_RecordingController, "finalizationTimeoutMs", 5e3);
5880
+ var RecordingController = _RecordingController;
5881
+
5882
+ // src/Render/Trajectory/TrajectoryBinding.ts
5883
+ var TrajectoryBinding = class {
5884
+ /**
5885
+ * 注入采样回调及模型输入格式,不引用 Media Controller 实例。
5886
+ */
5887
+ constructor(listener, format) {
5888
+ __publicField(this, "listener", listener);
5889
+ __publicField(this, "format", format);
5890
+ /**
5891
+ * 绑定视图与可用性
5892
+ */
5893
+ __publicField(this, "overlay");
5894
+ __publicField(this, "enabled", false);
5895
+ __publicField(this, "valid", true);
5896
+ }
5897
+ /**
5898
+ * 一个远端绑定只由一个交互视图持有,迁移时取消旧视图的触点。
5899
+ */
5900
+ attach(overlay) {
5901
+ if (!this.valid) {
5902
+ return;
5903
+ }
5904
+ if (this.overlay !== overlay) {
5905
+ this.overlay?.setBinding(void 0);
5906
+ }
5907
+ this.overlay = overlay;
5908
+ overlay.setBinding(this, this.format, this.enabled);
5909
+ }
5910
+ /**
5911
+ * 只解除当前所有者,旧视图迟到的 detach 不影响新视图。
5912
+ */
5913
+ detach(overlay) {
5914
+ if (this.overlay === overlay) {
5915
+ this.overlay = void 0;
5916
+ overlay.setBinding(void 0);
5917
+ }
5918
+ }
5919
+ /**
5920
+ * 开始、更新或结束任务时同步交互能力,并取消旧手势。
5921
+ */
5922
+ update(enabled, format = this.format) {
5923
+ this.enabled = enabled;
5924
+ this.format = format;
5925
+ this.overlay?.setBinding(this, format, enabled);
5926
+ }
5927
+ /**
5928
+ * 转发当前视图采样;注销或停用后忽略。
5929
+ */
5930
+ submit(frame) {
5931
+ if (this.valid && this.enabled) {
5932
+ this.listener(frame);
5933
+ }
5934
+ }
5935
+ /**
5936
+ * 注销绑定并释放视图交互资源,不允许旧引用重新挂载。
5937
+ */
5938
+ invalidate() {
5939
+ this.valid = false;
5940
+ this.overlay?.setBinding(void 0);
5941
+ this.overlay = void 0;
5942
+ }
5943
+ };
5944
+
5945
+ // src/Render/Trajectory/TrajectoryRegistry.ts
5946
+ var TrajectoryRegistry = class {
5947
+ /**
5948
+ * 替换绑定前先使旧绑定失效。
5949
+ */
5950
+ static register(track, binding) {
5951
+ this.unregister(track);
5952
+ this.bindings.set(track, binding);
5953
+ }
5954
+ /**
5955
+ * 查询交互能力,本地轨道不注册该能力。
5956
+ */
5957
+ static binding(track) {
5958
+ return this.bindings.get(track);
5959
+ }
5960
+ /**
5961
+ * 主动清理视图资源,不能仅依赖 WeakMap 垃圾回收。
5962
+ */
5963
+ static unregister(track) {
5964
+ this.bindings.get(track)?.invalidate();
5965
+ this.bindings.delete(track);
5966
+ }
5967
+ };
5968
+ __publicField(TrajectoryRegistry, "bindings", /* @__PURE__ */ new WeakMap());
5969
+
5970
+ // src/Render/RenderController.ts
5971
+ var RenderController = class {
5972
+ /**
5973
+ * 注入显示生命周期通知,不持有 Core 对象。
5974
+ */
5975
+ constructor(callbacks = {}) {
5976
+ __publicField(this, "callbacks", callbacks);
5977
+ /**
5978
+ * 当前轨道与唯一渲染目标
5979
+ */
5980
+ __publicField(this, "track");
5981
+ __publicField(this, "target");
5982
+ __publicField(this, "interactionEnabled", false);
5983
+ }
5984
+ /**
5985
+ * 注册新轨道并使旧绑定失效;本地轨道不经过该入口。
5986
+ */
5987
+ registerRemoteTrack(track, listener) {
5988
+ this.resetRemoteTrack();
5989
+ this.track = track;
5990
+ if (track.videoFormat) {
5991
+ TrajectoryRegistry.register(track, new TrajectoryBinding(listener, track.videoFormat));
5992
+ }
5993
+ VideoRenderRegistry.register(track, {
5994
+ frameDisplayHandler: () => {
5995
+ if (this.track === track) {
5996
+ this.callbacks.onFrameDisplayed?.();
5997
+ }
5998
+ },
5999
+ attachHandler: (view) => {
6000
+ if (this.track !== track) {
6001
+ return;
6002
+ }
6003
+ this.target?.setFrameInterpolation?.(void 0);
6004
+ view.isMirrored = false;
6005
+ this.target = view;
6006
+ this.callbacks.onAttached?.();
6007
+ view.setMediaStream(track.mediaStreamTrack ? new MediaStream([track.mediaStreamTrack]) : null);
6008
+ },
6009
+ detachHandler: (view) => {
6010
+ if (this.target === view) {
6011
+ this.callbacks.onDetached?.();
6012
+ this.target = void 0;
6013
+ }
6014
+ view.setFrameInterpolation?.(void 0);
6015
+ view.setMediaStream(null);
6016
+ }
6017
+ });
6018
+ }
6019
+ /**
6020
+ * 更新媒体画面;暂停显示时同时禁用交互。
6021
+ */
6022
+ setRemoteMedia(track) {
6023
+ if (this.track) {
6024
+ TrajectoryRegistry.binding(this.track)?.update(!!track && this.interactionEnabled);
6025
+ }
6026
+ this.target?.setMediaStream(track ? new MediaStream([track]) : null);
6027
+ }
6028
+ /**
6029
+ * 转交插帧配置,不重建交互视图。
6030
+ */
6031
+ setFrameInterpolation(options) {
6032
+ this.target?.setFrameInterpolation?.(options);
6033
+ }
6034
+ /**
6035
+ * 更新交互绑定的输入格式和可用性。
6036
+ */
6037
+ setInteractionEnabled(enabled, format) {
6038
+ this.interactionEnabled = enabled;
6039
+ if (this.track) {
6040
+ TrajectoryRegistry.binding(this.track)?.update(enabled, format);
6041
+ }
6042
+ }
6043
+ /**
6044
+ * 先使能力失效再清空画面,迟到首帧和旧手势不能恢复已结束任务。
6045
+ */
6046
+ resetRemoteTrack() {
6047
+ const track = this.track;
6048
+ this.track = void 0;
6049
+ this.interactionEnabled = false;
6050
+ if (track) {
6051
+ TrajectoryRegistry.unregister(track);
6052
+ VideoRenderRegistry.unregister(track);
6053
+ }
6054
+ this.target?.setFrameInterpolation?.(void 0);
6055
+ this.target?.setMediaStream(null);
6056
+ this.target = void 0;
6057
+ }
6058
+ };
6059
+
6060
+ // src/Service/Realtime/RealtimeContext.ts
6061
+ var RealtimeContext = class {
6062
+ /**
6063
+ * 创建实时生成条件并规范化文本和参考路径。
6064
+ */
6065
+ constructor(init) {
6066
+ /**
6067
+ * 服务端直接读取的网络视频。
6068
+ */
6069
+ __publicField(this, "referenceVideo");
6070
+ /**
6071
+ * 生成条件
6072
+ */
6073
+ /**
6074
+ * 实时生成使用的文本条件。
6075
+ */
6076
+ __publicField(this, "prompt");
6077
+ /**
6078
+ * 已上传参考图片的可选远端路径。
6079
+ */
6080
+ __publicField(this, "referencePath");
6081
+ this.referenceVideo = init.referenceVideo && Object.freeze({ ...init.referenceVideo });
6082
+ this.prompt = init.prompt.trim();
6083
+ const normalizedReferencePath = init.referencePath?.trim();
6084
+ this.referencePath = normalizedReferencePath || void 0;
6085
+ }
6086
+ };
6087
+
6088
+ // src/Service/Realtime/RealtimeSession.ts
6089
+ var RealtimeSession = class {
6090
+ /**
6091
+ * 创建实时会话。
6092
+ *
6093
+ * @param init.id 会话标识(sessionUid)。
6094
+ * @param init.userID 业务用户标识。
6095
+ * @param init.status 会话状态。
6096
+ * @param init.connection RTC 连接参数。
6097
+ * @param init.closeReason 会话关闭原因。
6098
+ */
6099
+ constructor(init) {
6100
+ /**
6101
+ * 会话信息
6102
+ */
6103
+ /**
6104
+ * 会话标识(sessionUid)。
6105
+ */
6106
+ __publicField(this, "id");
6107
+ /**
6108
+ * 业务用户标识。
6109
+ */
6110
+ __publicField(this, "userID");
6111
+ /**
6112
+ * 运行状态
6113
+ */
6114
+ /**
6115
+ * 会话状态(如 ACTIVE);响应未携带时为空。
6116
+ */
6117
+ __publicField(this, "status");
6118
+ /**
6119
+ * 会话关闭原因。
6120
+ */
6121
+ __publicField(this, "closeReason");
6122
+ /**
6123
+ * RTC 连接
6124
+ */
6125
+ /**
6126
+ * RTC 连接参数;心跳等响应未携带完整参数时为空。
6127
+ */
6128
+ __publicField(this, "connection");
6129
+ this.id = init.id;
6130
+ this.userID = init.userID;
6131
+ this.status = init.status;
6132
+ this.connection = init.connection;
6133
+ this.closeReason = init.closeReason;
6134
+ }
6135
+ };
6136
+
6137
+ // src/Foundation/RTC/RoomJoinConfiguration.ts
6138
+ var RoomJoinConfiguration = class {
6139
+ /**
6140
+ * 创建房间加入参数。
6141
+ *
6142
+ * @param init.roomID TRTC 房间号。
6143
+ * @param init.userID TRTC 登录身份。
6144
+ * @param init.sdkAppID TRTC SDKAppID(字符串形式)。
6145
+ * @param init.userSig TRTC 登录签名。
6146
+ * @param init.privateMapKey TRTC 进房鉴权字段。
6147
+ */
6148
+ constructor(init) {
6149
+ /**
6150
+ * RTC 提供方。
6151
+ */
6152
+ __publicField(this, "provider", "trtc" /* trtc */);
6153
+ /**
6154
+ * 进房参数
4940
6155
  */
4941
6156
  /**
4942
6157
  * TRTC 房间号(字符串房间号)。
@@ -5385,6 +6600,16 @@ var RemoteStream = class {
5385
6600
  var RoomEvent = class _RoomEvent {
5386
6601
  /**
5387
6602
  * 生成开始信令。
6603
+ *
6604
+ * @param options.userID 业务用户标识。
6605
+ * @param options.taskID 当前生成任务标识。
6606
+ * @param options.videoFormat 模型生成使用的视频规格。
6607
+ * @param options.context 本次生成条件。
6608
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
6609
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
6610
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
6611
+ * @param options.extraParams 已校验的本次扩展参数,合并到 params。
6612
+ * @returns 序列化后的 start 房间消息。
5388
6613
  */
5389
6614
  static start(options) {
5390
6615
  return _RoomEvent.encode({
@@ -5408,6 +6633,15 @@ var RoomEvent = class _RoomEvent {
5408
6633
  }
5409
6634
  /**
5410
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 房间消息。
5411
6645
  */
5412
6646
  static changeCondition(options) {
5413
6647
  return _RoomEvent.encode({
@@ -5464,18 +6698,23 @@ var RoomEvent = class _RoomEvent {
5464
6698
  });
5465
6699
  }
5466
6700
  /**
5467
- * 生成条件参数:模型、生成尺寸、回传尺寸、文本与参考图路径。
6701
+ * 组装生成条件参数,包含人脸回贴配置及本次扩展参数。
6702
+ *
6703
+ * @param options 本次生成条件类信令的参数。
6704
+ * @returns start 与 change_condition 共用的 params 字段。
5468
6705
  */
5469
6706
  static generationParameters(options) {
5470
6707
  return {
5471
6708
  model: "default",
6709
+ face_replace: options.faceRestoration ?? false,
5472
6710
  size: [options.videoFormat.width, options.videoFormat.height],
5473
6711
  target_size: options.targetSize ? [
5474
6712
  Math.round(options.targetSize.width),
5475
6713
  Math.round(options.targetSize.height)
5476
6714
  ] : void 0,
5477
6715
  prompt: options.context.prompt,
5478
- ref_image_path: options.context.referencePath
6716
+ ref_image_path: options.context.referencePath,
6717
+ ...options.extraParams
5479
6718
  };
5480
6719
  }
5481
6720
  /**
@@ -5839,23 +7078,38 @@ var RoomController = class {
5839
7078
  /**
5840
7079
  * 发送生成开始信令。
5841
7080
  *
7081
+ * @param options.taskID 当前生成任务标识。
7082
+ * @param options.videoFormat 模型生成使用的视频规格。
7083
+ * @param options.context 本次生成条件。
7084
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
7085
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
7086
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7087
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
5842
7088
  * @throws 房间未就绪或信令发送失败时抛出错误。
5843
7089
  */
5844
7090
  startGeneration(options) {
5845
7091
  return this.send(
5846
7092
  RoomEvent.start({
5847
- mirror: options.mirror ?? false,
5848
7093
  userID: this.requireUserID(),
5849
7094
  taskID: options.taskID,
5850
7095
  videoFormat: options.videoFormat,
7096
+ context: options.context,
5851
7097
  targetSize: options.targetSize,
5852
- context: options.context
7098
+ mirror: options.mirror ?? false,
7099
+ faceRestoration: options.faceRestoration ?? false,
7100
+ extraParams: options.extraParams
5853
7101
  })
5854
7102
  );
5855
7103
  }
5856
7104
  /**
5857
7105
  * 发送生成条件变更信令。
5858
7106
  *
7107
+ * @param options.taskID 当前生成任务标识。
7108
+ * @param options.videoFormat 模型生成使用的视频规格。
7109
+ * @param options.context 本次生成条件。
7110
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
7111
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7112
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
5859
7113
  * @throws 房间未就绪或信令发送失败时抛出错误。
5860
7114
  */
5861
7115
  changeGenerationCondition(options) {
@@ -5864,8 +7118,10 @@ var RoomController = class {
5864
7118
  userID: this.requireUserID(),
5865
7119
  taskID: options.taskID,
5866
7120
  videoFormat: options.videoFormat,
7121
+ context: options.context,
5867
7122
  targetSize: options.targetSize,
5868
- context: options.context
7123
+ faceRestoration: options.faceRestoration ?? false,
7124
+ extraParams: options.extraParams
5869
7125
  })
5870
7126
  );
5871
7127
  }
@@ -6262,6 +7518,17 @@ var StreamController = class {
6262
7518
  get hasGenerationTask() {
6263
7519
  return this.state.generationTaskID !== void 0;
6264
7520
  }
7521
+ /**
7522
+ * 按活动结果流身份读取原始音视频,避免录制房间内其他用户的媒体。
7523
+ */
7524
+ getRemoteRecordingTracks() {
7525
+ const userID = this.state.activeRemoteStream?.userID;
7526
+ const videoTrack = userID && this.state.remoteVideoTracks.get(userID);
7527
+ if (!userID || !videoTrack) {
7528
+ return void 0;
7529
+ }
7530
+ return { videoTrack, audioTrack: this.rtcManager.getRemoteAudioTrack(userID) };
7531
+ }
6265
7532
  /**
6266
7533
  * 设置本地主视频流统计监听器,不改变 RTC 事件监听权。
6267
7534
  */
@@ -6343,8 +7610,17 @@ var StreamController = class {
6343
7610
  /**
6344
7611
  * 建立生成任务并发送开始信令。
6345
7612
  *
7613
+ * @param options.taskID 当前生成任务标识。
7614
+ * @param options.videoFormat 模型生成使用的视频规格。
7615
+ * @param options.context 本次生成条件。
7616
+ * @param options.restart 是否保留连接并替换当前生成任务,默认 false;不预先发送 stop。
7617
+ * @param options.targetSize 回传尺寸,缺省时不发送;网络视频默认使用生成尺寸。
7618
+ * @param options.mirror 是否由后端镜像输入画面,默认 false。
7619
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7620
+ * @param options.onStartSent start 发送成功后的回调,不等待生成确认。
7621
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
6346
7622
  * @returns 等待远端结果流确认的 Promise。
6347
- * @throws 任务标识无效、RTC 房间未就绪或已有生成任务时同步抛错;
7623
+ * @throws 任务标识无效、RTC 房间未就绪、旧任务仍在启动或未指定 restart 且已有生成任务时同步抛错;
6348
7624
  * 发送失败或确认超时通过返回的 Promise 拒绝。
6349
7625
  */
6350
7626
  beginGeneration(options) {
@@ -6358,7 +7634,7 @@ var StreamController = class {
6358
7634
  if (!this.state.roomID) {
6359
7635
  throw new XmaxError("RTC_ERROR" /* rtcError */, "RTC room is not configured");
6360
7636
  }
6361
- if (this.state.generationTaskID) {
7637
+ if (this.state.generationTaskID && (!options.restart || this.state.generationWaiter)) {
6362
7638
  throw new XmaxError(
6363
7639
  "RTC_ERROR" /* rtcError */,
6364
7640
  "Realtime generation is already active"
@@ -6393,10 +7669,12 @@ var StreamController = class {
6393
7669
  try {
6394
7670
  const sending = this.roomController.startGeneration({
6395
7671
  taskID,
6396
- mirror: options.mirror ?? false,
6397
7672
  videoFormat: options.videoFormat,
7673
+ context: options.context,
6398
7674
  targetSize: options.targetSize,
6399
- context: options.context
7675
+ mirror: options.mirror ?? false,
7676
+ faceRestoration: options.faceRestoration ?? false,
7677
+ extraParams: options.extraParams
6400
7678
  });
6401
7679
  const state2 = this.state;
6402
7680
  const sent = Promise.resolve(sending).then(async () => {
@@ -6448,6 +7726,15 @@ var StreamController = class {
6448
7726
  /**
6449
7727
  * 发送生成条件变更信令。
6450
7728
  *
7729
+ * @param options.taskID 当前生成任务标识。
7730
+ * @param options.videoFormat 模型生成使用的视频规格。
7731
+ * @param options.context 本次生成条件。
7732
+ * @param options.restart 本次条件更新不使用该字段。
7733
+ * @param options.targetSize 回传尺寸,缺省时不发送。
7734
+ * @param options.mirror 本次条件更新不使用该字段。
7735
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
7736
+ * @param options.onStartSent 本次条件更新不调用该回调。
7737
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
6451
7738
  * @throws RTC 房间未就绪或条件变更信令发送失败时抛出错误。
6452
7739
  */
6453
7740
  async updateGeneration(options) {
@@ -6455,8 +7742,10 @@ var StreamController = class {
6455
7742
  await this.roomController.changeGenerationCondition({
6456
7743
  taskID: options.taskID,
6457
7744
  videoFormat: options.videoFormat,
7745
+ context: options.context,
6458
7746
  targetSize: options.targetSize,
6459
- context: options.context
7747
+ faceRestoration: options.faceRestoration ?? false,
7748
+ extraParams: options.extraParams
6460
7749
  });
6461
7750
  } catch (error) {
6462
7751
  throw XmaxError.from(error);
@@ -7050,8 +8339,9 @@ function qualityVideoFormat(baseline, level) {
7050
8339
 
7051
8340
  // src/Stream/Quality/QualityHistory.ts
7052
8341
  var QualityHistory = class {
7053
- constructor(model) {
8342
+ constructor(model, provider) {
7054
8343
  __publicField(this, "model", model);
8344
+ __publicField(this, "provider", provider);
7055
8345
  }
7056
8346
  /** 无记录、损坏、SSR 或浏览器禁止存储时使用默认 L2。 */
7057
8347
  read(direction) {
@@ -7071,7 +8361,7 @@ var QualityHistory = class {
7071
8361
  }
7072
8362
  }
7073
8363
  key(direction) {
7074
- return `xmax:quality:v1:agora:${encodeURIComponent(this.model)}:${direction}`;
8364
+ return `xmax:quality:v1:${this.provider}:${encodeURIComponent(this.model)}:${direction}`;
7075
8365
  }
7076
8366
  isValid(level) {
7077
8367
  return Number.isInteger(level) && level >= 1 && level <= Object.keys(QUALITY_TIERS).length;
@@ -7620,7 +8910,6 @@ var XmaxRealtimeConnectionManager = class {
7620
8910
  * 远端轨道与渲染绑定
7621
8911
  */
7622
8912
  __publicField(this, "activeRemoteTrack");
7623
- __publicField(this, "remoteTarget");
7624
8913
  }
7625
8914
  /**
7626
8915
  * 当前活动会话标识;尚未连接或清理完成后为空。
@@ -7638,6 +8927,16 @@ var XmaxRealtimeConnectionManager = class {
7638
8927
  * 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
7639
8928
  * 进房结束即记录连接耗时;可选 beforePublish 检查阻塞发布,计入连接耗时;
7640
8929
  * 发布本地流单独计时。
8930
+ * @param options.localTrack 当前本地媒体轨道。
8931
+ * @param options.remoteVideoFormat 原始生成及远端画面规格,缺省使用本地轨道规格。
8932
+ * @param options.localVideoFormat 发布前实际应用的上行编码格式;与原始生成及远端格式分开。
8933
+ * @param options.onVideoEncoderConfigured 上行编码配置成功后的回调。
8934
+ * @param options.model 创建业务会话使用的模型名称。
8935
+ * @param options.includeLocalAudio 是否发布本地音频。
8936
+ * @param options.ensureCurrent 校验当前操作仍有效。
8937
+ * @param options.onPublished 本地发布完成后的回调。
8938
+ * @param options.beforePublish 发布前的可选异步检查。
8939
+ * @param options.publishLocalMedia 是否发布本地媒体,默认 true;网络视频源传 false。
7641
8940
  * @throws 会话、进房、发布失败或操作租约已失效时抛出错误,由上层统一清理。
7642
8941
  */
7643
8942
  async connect(options) {
@@ -7700,21 +8999,15 @@ var XmaxRealtimeConnectionManager = class {
7700
8999
  videoFormat: options.remoteVideoFormat ?? options.localTrack.videoFormat
7701
9000
  });
7702
9001
  this.activeRemoteTrack = remoteTrack;
7703
- this.registerRemoteBinding(remoteTrack);
9002
+ this.dependencies.renderController.registerRemoteTrack(remoteTrack, this.dependencies.onInteraction);
7704
9003
  streamController.setRemoteAudioVolume(this.dependencies.remoteAudioVolume());
7705
9004
  return new RealtimeMediaStream({ id: "stream1" /* remote */, videoTrack: remoteTrack });
7706
9005
  }
7707
- /**
7708
- * 更新已绑定远端视图的插帧配置;传 undefined 停用插帧。
7709
- */
7710
- setFrameInterpolation(options) {
7711
- this.remoteTarget?.setFrameInterpolation?.(options);
7712
- }
7713
9006
  /**
7714
9007
  * 清空远端视图的媒体流,保留轨道与视图绑定以供统一清理。
7715
9008
  */
7716
9009
  clearRemoteMedia() {
7717
- this.remoteTarget?.setMediaStream(null);
9010
+ this.dependencies.renderController.setRemoteMedia(null);
7718
9011
  }
7719
9012
  /**
7720
9013
  * 停止会话心跳;未配置会话服务时不执行操作。
@@ -7794,7 +9087,7 @@ var XmaxRealtimeConnectionManager = class {
7794
9087
  if (track) {
7795
9088
  track.mediaStreamTrack = void 0;
7796
9089
  }
7797
- this.remoteTarget?.setMediaStream(null);
9090
+ this.dependencies.renderController.setRemoteMedia(null);
7798
9091
  return;
7799
9092
  }
7800
9093
  if (!track) {
@@ -7804,35 +9097,7 @@ var XmaxRealtimeConnectionManager = class {
7804
9097
  );
7805
9098
  }
7806
9099
  track.mediaStreamTrack = binding.videoTrack;
7807
- this.remoteTarget?.setMediaStream(new MediaStream([binding.videoTrack]));
7808
- }
7809
- /**
7810
- * 为远端轨道注册渲染绑定:attach 时挂流占位,媒体轨到达后送入画面。
7811
- */
7812
- registerRemoteBinding(track) {
7813
- VideoRenderRegistry.register(track, {
7814
- frameDisplayHandler: () => {
7815
- if (this.activeRemoteTrack === track) {
7816
- this.dependencies.onFrameDisplayed();
7817
- }
7818
- },
7819
- attachHandler: (view) => {
7820
- this.remoteTarget?.setFrameInterpolation?.(void 0);
7821
- view.isMirrored = false;
7822
- this.remoteTarget = view;
7823
- this.dependencies.onRenderAttached();
7824
- const mediaTrack = track.mediaStreamTrack;
7825
- view.setMediaStream(mediaTrack ? new MediaStream([mediaTrack]) : null);
7826
- },
7827
- detachHandler: (view) => {
7828
- if (this.remoteTarget === view) {
7829
- this.dependencies.onRenderDetached();
7830
- this.remoteTarget = void 0;
7831
- }
7832
- view.setFrameInterpolation?.(void 0);
7833
- view.setMediaStream(null);
7834
- }
7835
- });
9100
+ this.dependencies.renderController.setRemoteMedia(binding.videoTrack);
7836
9101
  }
7837
9102
  /**
7838
9103
  * 构造当前远端生成结果媒体流。
@@ -7905,12 +9170,11 @@ var XmaxRealtimeConnectionManager = class {
7905
9170
  }
7906
9171
  const track = this.activeRemoteTrack;
7907
9172
  if (track) {
7908
- VideoRenderRegistry.unregister(track);
9173
+ this.dependencies.renderController.resetRemoteTrack();
7909
9174
  track.mediaStreamTrack = void 0;
7910
9175
  }
7911
9176
  this.activeSession = void 0;
7912
9177
  this.activeRemoteTrack = void 0;
7913
- this.remoteTarget = void 0;
7914
9178
  if (sessionID && this.dependencies.sessionService) {
7915
9179
  try {
7916
9180
  await this.dependencies.sessionService.closeSession(sessionID);
@@ -7940,8 +9204,10 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
7940
9204
  /**
7941
9205
  * 注入房间传输组件和任务标识生成器,不创建会话或启动生成。
7942
9206
  */
7943
- constructor(streamController, taskIDGenerator = _XmaxRealtimeGenerationManager.createTaskID) {
9207
+ constructor(streamController, interactionController, renderController, taskIDGenerator = _XmaxRealtimeGenerationManager.createTaskID) {
7944
9208
  __publicField(this, "streamController", streamController);
9209
+ __publicField(this, "interactionController", interactionController);
9210
+ __publicField(this, "renderController", renderController);
7945
9211
  __publicField(this, "taskIDGenerator", taskIDGenerator);
7946
9212
  /**
7947
9213
  * 当前生成条件与任务标识
@@ -7966,28 +9232,51 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
7966
9232
  /**
7967
9233
  * 发送生成指令并等待确认、远端就绪和音频激活,成功后返回任务标识。
7968
9234
  * 条件上下文在生成确认后缓存;启动失败时尽力停止本次任务并保留原始错误。
9235
+ * @param options.videoFormat 模型生成使用的视频规格。
9236
+ * @param options.signal 取消本次生成启动的信号。
9237
+ * @param options.ensureCurrent 校验当前操作仍有效。
9238
+ * @param options.waitUntilRemoteReady 等待远端生成流就绪。
9239
+ * @param options.context 生成条件,缺省复用最近缓存的上下文。
9240
+ * @param options.restart 是否替换当前任务并重新发送 start,默认 false;不发送 stop。
9241
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
9242
+ * @param options.mirror 是否由后端水平翻转输入,默认 false;仅用于 start 信令。
9243
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
9244
+ * @param options.onStartSent start 信令发送成功后的可选异步回调。
9245
+ * @param options.onRemoteReady 远端视频就绪后通知任务标识,不等待录制等附加功能。
9246
+ * @param options.extraParams 合并到本次 start 的 params,不缓存。
7969
9247
  */
7970
9248
  async start(options) {
7971
9249
  const context = this.validateContext(options.context);
7972
9250
  options.ensureCurrent();
7973
9251
  const taskID = this.taskIDGenerator();
9252
+ if (options.restart) {
9253
+ this.interactionController.stopInteraction();
9254
+ this.renderController.setInteractionEnabled(false);
9255
+ }
7974
9256
  this.activeTaskID = taskID;
7975
9257
  try {
7976
9258
  const confirmation = this.streamController.beginGeneration({
7977
9259
  taskID,
7978
9260
  videoFormat: options.videoFormat,
7979
9261
  context,
9262
+ restart: options.restart,
7980
9263
  ...options.targetSize && { targetSize: options.targetSize },
7981
9264
  mirror: options.mirror ?? false,
7982
- ...options.onStartSent && { onStartSent: options.onStartSent }
9265
+ faceRestoration: options.faceRestoration ?? false,
9266
+ ...options.onStartSent && { onStartSent: options.onStartSent },
9267
+ extraParams: options.extraParams
7983
9268
  });
7984
9269
  await this.awaitConfirmation(confirmation, options.signal);
7985
9270
  options.ensureCurrent();
7986
9271
  this.currentContext = context;
7987
9272
  await options.waitUntilRemoteReady();
7988
9273
  options.ensureCurrent();
9274
+ options.onRemoteReady?.(taskID);
9275
+ options.ensureCurrent();
7989
9276
  await this.streamController.activateRemoteAudio();
7990
9277
  options.ensureCurrent();
9278
+ this.interactionController.startInteraction(taskID, options.videoFormat);
9279
+ this.renderController.setInteractionEnabled(true, options.videoFormat);
7991
9280
  return taskID;
7992
9281
  } catch (error) {
7993
9282
  await this.stop(taskID);
@@ -7996,6 +9285,12 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
7996
9285
  }
7997
9286
  /**
7998
9287
  * 更新活动任务的生成条件;信令发送成功后才覆盖缓存,无任务或上下文时抛错。
9288
+ * @param taskID 当前活动任务标识。
9289
+ * @param options.videoFormat 模型生成使用的视频规格。
9290
+ * @param options.context 生成条件,缺省复用最近缓存的上下文。
9291
+ * @param options.targetSize 服务端生成后的回传尺寸,缺省时不发送。
9292
+ * @param options.faceRestoration 是否回贴原始人脸,默认 false。
9293
+ * @param options.extraParams 合并到本次 change_condition 的 params,不缓存。
7999
9294
  */
8000
9295
  async update(taskID, options) {
8001
9296
  const context = options.context ?? this.currentContext;
@@ -8006,10 +9301,14 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8006
9301
  taskID,
8007
9302
  videoFormat: options.videoFormat,
8008
9303
  context,
8009
- ...options.targetSize && { targetSize: options.targetSize }
9304
+ ...options.targetSize && { targetSize: options.targetSize },
9305
+ faceRestoration: options.faceRestoration ?? false,
9306
+ extraParams: options.extraParams
8010
9307
  });
8011
9308
  if (this.activeTaskID !== taskID) throw RealtimeCoordinator.cancelledError();
8012
9309
  this.currentContext = context;
9310
+ this.interactionController.startInteraction(taskID, options.videoFormat);
9311
+ this.renderController.setInteractionEnabled(true, options.videoFormat);
8013
9312
  }
8014
9313
  /**
8015
9314
  * 幂等停止指定活动任务;并发调用共享结果,停止失败仅记录日志。
@@ -8020,6 +9319,8 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8020
9319
  }
8021
9320
  if (!taskID || this.activeTaskID !== taskID) return;
8022
9321
  this.activeTaskID = void 0;
9322
+ this.interactionController.stopInteraction();
9323
+ this.renderController.setInteractionEnabled(false);
8023
9324
  const completion = (async () => {
8024
9325
  try {
8025
9326
  await this.streamController.stopGeneration(taskID);
@@ -8040,6 +9341,8 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
8040
9341
  */
8041
9342
  async reset(taskID) {
8042
9343
  this.currentContext = void 0;
9344
+ this.interactionController.stopInteraction();
9345
+ this.renderController.setInteractionEnabled(false);
8043
9346
  await this.stop(this.activeTaskID ?? (taskID || void 0));
8044
9347
  }
8045
9348
  /**
@@ -8096,6 +9399,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8096
9399
  */
8097
9400
  __publicField(this, "options");
8098
9401
  __publicField(this, "mediaService");
9402
+ /**
9403
+ * 可选的任务录制;未开启时不分配浏览器录制资源。
9404
+ */
9405
+ __publicField(this, "recordingController", new RecordingController());
8099
9406
  /**
8100
9407
  * 本地流创建时的生成格式;上行编码调整不改变模型输入配置与远端插帧尺寸。
8101
9408
  */
@@ -8112,12 +9419,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8112
9419
  */
8113
9420
  __publicField(this, "coordinator");
8114
9421
  __publicField(this, "errorHandler");
8115
- __publicField(this, "cameraController");
8116
- __publicField(this, "networkVideoController", new NetworkVideoController());
8117
- __publicField(this, "localVideoController", new LocalVideoController((error) => {
8118
- void this.coordinator.terminateWithError(error, 0 /* connection */);
8119
- }));
8120
- __publicField(this, "rtcManager");
9422
+ __publicField(this, "mediaController");
9423
+ __publicField(this, "renderController");
8121
9424
  /**
8122
9425
  * 启动计时与首帧通知
8123
9426
  */
@@ -8162,14 +9465,6 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8162
9465
  this.supportsInterpolation = dependencies?.supportsFrameInterpolation ?? (async (size) => await frameInterpolationAdapter(size) !== null);
8163
9466
  this.errorHandler = new RealtimeErrorHandler();
8164
9467
  const rtcManager = dependencies?.rtcManager ?? createRtcManager(options.provider, dependencies?.environment ?? "china" /* china */);
8165
- this.rtcManager = rtcManager;
8166
- this.cameraController = dependencies?.cameraController ?? new CameraController({
8167
- rtcManager,
8168
- mediaService: this.mediaService,
8169
- errorListener: (error) => {
8170
- void this.errorHandler.report(error);
8171
- }
8172
- });
8173
9468
  const sessionService = dependencies?.sessionService ?? (dependencies?.apiService ? new RealtimeSessionService({ apiService: dependencies.apiService, provider: options.provider }) : void 0);
8174
9469
  this.streamController = dependencies?.streamController ?? new StreamController({
8175
9470
  rtcManager,
@@ -8180,10 +9475,38 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8180
9475
  void this.errorHandler.report(error);
8181
9476
  },
8182
9477
  remoteStreamListener: (binding) => {
9478
+ if (!binding) {
9479
+ void this.recordingController.stop("mediaInterrupted" /* mediaInterrupted */);
9480
+ }
8183
9481
  this.clearRemoteVideoStatistics();
8184
9482
  this.connectionManager.handleRemoteStreamBinding(binding);
8185
9483
  }
8186
9484
  });
9485
+ this.mediaController = new MediaController({
9486
+ rtcManager,
9487
+ mediaService: this.mediaService,
9488
+ cameraController: dependencies?.cameraController,
9489
+ onCameraError: (error) => {
9490
+ void this.errorHandler.report(error);
9491
+ },
9492
+ onVideoError: (error) => {
9493
+ void this.coordinator.terminateWithError(error, 0 /* connection */);
9494
+ },
9495
+ onExternalError: (error) => {
9496
+ void this.coordinator.terminateWithError(error, 1 /* all */);
9497
+ },
9498
+ onImageError: (error) => {
9499
+ void this.coordinator.terminateWithError(error, 1 /* all */);
9500
+ },
9501
+ interactionListener: (taskID, points) => this.streamController.sendTracks(taskID, points)
9502
+ });
9503
+ this.renderController = new RenderController({
9504
+ onFrameDisplayed: () => this.remoteFrameDisplayHandler?.(),
9505
+ onAttached: () => this.applyRemoteInterpolation(),
9506
+ onDetached: () => {
9507
+ this.interpolationRevision++;
9508
+ }
9509
+ });
8187
9510
  this.connectionManager = new XmaxRealtimeConnectionManager({
8188
9511
  provider: options.provider,
8189
9512
  sessionService,
@@ -8193,34 +9516,19 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8193
9516
  onHeartbeatFailure: (sessionID, error) => {
8194
9517
  void this.handleHeartbeatFailure(sessionID, error);
8195
9518
  },
8196
- onFrameDisplayed: () => this.remoteFrameDisplayHandler?.(),
8197
- onRenderAttached: () => this.applyRemoteInterpolation(),
8198
- onRenderDetached: () => {
8199
- this.interpolationRevision++;
8200
- }
9519
+ renderController: this.renderController,
9520
+ onInteraction: (frame) => this.mediaController.submitInteraction(frame)
8201
9521
  });
8202
- this.generationManager = new XmaxRealtimeGenerationManager(this.streamController);
9522
+ this.generationManager = new XmaxRealtimeGenerationManager(this.streamController, this.mediaController, this.renderController);
8203
9523
  this.coordinator = new RealtimeCoordinator({
8204
9524
  errorHandler: this.errorHandler,
8205
9525
  cleanup: (scope, taskID) => this.performCleanup(scope, taskID)
8206
9526
  });
8207
- if (options.provider === "agora" /* agora */) {
8208
- this.qualityHistory = new QualityHistory(options.model);
9527
+ if (options.provider === "agora" /* agora */ || options.provider === "trtc" /* trtc */) {
9528
+ this.qualityHistory = new QualityHistory(options.model, options.provider);
8209
9529
  this.qualityController = new QualityController(async (change, signal) => {
8210
- if (signal.aborted || this.currentState.connectionState !== "Generating" /* generating */) {
8211
- throw new XmaxError("CANCELLED" /* cancelled */, "Adaptive video format update was cancelled");
8212
- }
8213
9530
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
8214
- const track = this.localVideoController.currentTrack ?? this.cameraController.currentTrack;
8215
- const state2 = track && this.qualityStates.get(track);
8216
- if (!track || !state2) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Adaptive video baseline is unavailable");
8217
- const format = qualityVideoFormat(state2.baseline, change.to);
8218
- await this.streamController.updateUplinkVideoFormat(format);
8219
- token.ensureCurrent();
8220
- this.generationVideoFormat ?? (this.generationVideoFormat = state2.baseline);
8221
- track.updateVideoFormat(format);
8222
- state2.level = change.to;
8223
- this.qualityHistory?.write("uplink", change.to);
9531
+ await this.applyUplinkQuality(change.to, token, signal);
8224
9532
  });
8225
9533
  });
8226
9534
  this.downlinkQualityController = new QualityController(async (change, signal) => {
@@ -8278,13 +9586,58 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8278
9586
  this.applyRemoteInterpolation();
8279
9587
  });
8280
9588
  }
9589
+ /**
9590
+ * 手动调整上行档位,保留原始基准,自动调档从新档位继续。
9591
+ * @param direction 升一档或降一档;到达 L1/L5 边界时不重复应用。
9592
+ */
9593
+ async adjustUplinkQuality(direction) {
9594
+ await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
9595
+ const controller = this.qualityController;
9596
+ const track = this.currentUplinkTrack;
9597
+ const state2 = track && this.qualityStates.get(track);
9598
+ if (!controller || !state2 || this.currentState.connectionState !== "Generating" /* generating */ || direction !== "upgrade" && direction !== "downgrade") {
9599
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Uplink quality adjustment requires an active adaptive-quality generation and a valid direction");
9600
+ }
9601
+ const level = Math.max(1, Math.min(Object.keys(QUALITY_TIERS).length, state2.level + (direction === "upgrade" ? -1 : 1)));
9602
+ if (level === state2.level) return;
9603
+ this.adaptiveQualityRevision++;
9604
+ controller.resetSamples();
9605
+ await this.applyUplinkQuality(level, token);
9606
+ controller.start(level);
9607
+ });
9608
+ }
9609
+ async applyUplinkQuality(level, token, signal) {
9610
+ const track = this.currentUplinkTrack;
9611
+ const state2 = track && this.qualityStates.get(track);
9612
+ const ensureActive2 = () => {
9613
+ token.ensureCurrent();
9614
+ if (signal?.aborted || !track || !state2 || track !== this.currentUplinkTrack || state2 !== this.qualityStates.get(track) || this.currentState.connectionState !== "Generating" /* generating */) {
9615
+ throw new XmaxError("CANCELLED" /* cancelled */, "Adaptive video format update was cancelled");
9616
+ }
9617
+ };
9618
+ ensureActive2();
9619
+ if (!track || !state2) return;
9620
+ const target = this.uplinkQualityFormat(state2.baseline, level);
9621
+ let format;
9622
+ if (this.options.provider === "trtc" /* trtc */) {
9623
+ format = await this.streamController.updateUplinkFrameRate(target.fps);
9624
+ } else {
9625
+ await this.streamController.updateUplinkVideoFormat(target);
9626
+ format = target;
9627
+ }
9628
+ ensureActive2();
9629
+ this.generationVideoFormat ?? (this.generationVideoFormat = state2.baseline);
9630
+ track.updateVideoFormat(format);
9631
+ state2.level = level;
9632
+ this.qualityHistory?.write("uplink", level);
9633
+ }
8281
9634
  /** 手动调试与自动调档使用同一尺寸信令路径,不修改生成或上行配置。 */
8282
9635
  async adjustDownlinkQuality(direction) {
8283
9636
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
8284
9637
  const controller = this.downlinkQualityController;
8285
9638
  const state2 = this.downlinkQualityState;
8286
9639
  if (!controller || !state2 || this.currentState.connectionState !== "Generating" /* generating */ || direction !== "upgrade" && direction !== "downgrade") {
8287
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Downlink quality adjustment requires an active Agora generation and a valid direction");
9640
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Downlink quality adjustment requires an active adaptive-quality generation and a valid direction");
8288
9641
  }
8289
9642
  const level = Math.max(1, Math.min(Object.keys(QUALITY_TIERS).length, state2.level + (direction === "upgrade" ? -1 : 1)));
8290
9643
  if (level === state2.level) return;
@@ -8366,7 +9719,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8366
9719
  applyRemoteInterpolation() {
8367
9720
  const revision = ++this.interpolationRevision;
8368
9721
  const size = this.interpolationSize;
8369
- this.connectionManager.setFrameInterpolation(size ? {
9722
+ this.renderController.setFrameInterpolation(size ? {
8370
9723
  size,
8371
9724
  onFailure: (error) => {
8372
9725
  if (revision === this.interpolationRevision) this.handleInterpolationFailure(error);
@@ -8523,6 +9876,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8523
9876
  *
8524
9877
  * 将返回的轨道绑定到预览视图;收到有效帧且视图已绑定后进入 `ready`。
8525
9878
  *
9879
+ * @param options.videoFormat 旋转后的目标规格,尺寸按模型规则解析。
9880
+ * @param options.position 前置或后置摄像头。
9881
+ * @param options.useMicrophone 是否采集并发布麦克风音频。
8526
9882
  * @param options.enableFrameValidation 是否在发布前等待相机预热(固定 200ms),默认 true;false 跳过等待。
8527
9883
  * @param options.rotation 输入帧顺时针旋转角度,默认 0;videoFormat 为旋转后的规格,目前仅 Agora 支持非零角度。
8528
9884
  * @returns 包含本地相机视频轨道的媒体流。
@@ -8533,7 +9889,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8533
9889
  "media" /* media */,
8534
9890
  void 0,
8535
9891
  async (token) => {
8536
- this.ensureNoLocalSource();
9892
+ this.mediaController.ensureNoLocalSource();
8537
9893
  await this.coordinator.commit(
8538
9894
  new RealtimeState({
8539
9895
  connectionState: "Preparing" /* preparing */
@@ -8554,7 +9910,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8554
9910
  let stream;
8555
9911
  try {
8556
9912
  token.ensureCurrent();
8557
- stream = await this.cameraController.createLocalCameraStream(options);
9913
+ stream = await this.mediaController.createLocalCameraStream(options);
8558
9914
  } catch (error) {
8559
9915
  this.disposePermissionWatch();
8560
9916
  this.launchTimer.cancel();
@@ -8563,7 +9919,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8563
9919
  token.ensureCurrent();
8564
9920
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8565
9921
  completeCamera();
8566
- this.cameraController.setPreviewReadyHandler((isCurrent) => {
9922
+ this.mediaController.setCameraPreviewReadyHandler((isCurrent) => {
8567
9923
  void this.coordinator.localPreviewDidBecomeReady(isCurrent);
8568
9924
  });
8569
9925
  return stream;
@@ -8572,15 +9928,19 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8572
9928
  }
8573
9929
  /**
8574
9930
  * 创建网络视频源并初始化接收端 RTC;预览是否加载成功不影响服务端读取视频。
9931
+ * @param options.url 服务端可直接访问的 HTTP/HTTPS 视频地址。
9932
+ * @param options.videoFormat 模型输入及生成画面规格,不是上行编码参数。
9933
+ * @param options.sampleMethod 服务端采样方式,默认 time。
9934
+ * @param options.onFinish 服务端完成当前任务后的回调,不代表尾帧已播放完。
8575
9935
  */
8576
9936
  async createNetworkVideoStream(options) {
8577
9937
  return this.coordinator.run("media" /* media */, void 0, async (token) => {
8578
- this.ensureNoLocalSource();
8579
- const stream = this.networkVideoController.create(options, this.mediaService);
9938
+ this.mediaController.ensureNoLocalSource();
9939
+ const stream = this.mediaController.createNetworkVideoStream(options);
8580
9940
  token.setFailureScope(1 /* all */);
8581
9941
  this.launchTimer.start();
8582
9942
  this.clearVideoStatistics();
8583
- await this.rtcManager.initialize();
9943
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
8584
9944
  token.ensureCurrent();
8585
9945
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8586
9946
  await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
@@ -8589,43 +9949,70 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8589
9949
  }
8590
9950
  /**
8591
9951
  * 创建文件音视频源并准备外部轨道;准备失败时统一释放本次媒体和 RTC 资源。
9952
+ * @param options.file 包含可解码视频的本地 File 或 Blob。
9953
+ * @param options.videoFormat 期望的视频规格;尺寸按宽高比匹配模型档位。
9954
+ * @param options.loop 是否循环播放,默认 true。
9955
+ * @param options.includeAudio 是否发送文件音频,默认 true。
8592
9956
  */
8593
9957
  async createLocalVideoStream(options) {
8594
9958
  return this.coordinator.run("media" /* media */, void 0, async (token) => {
8595
- this.ensureNoLocalSource();
8596
- if (!this.rtcManager.setExternalMediaTracks) {
8597
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "The RTC provider does not support local video streams");
8598
- }
9959
+ this.mediaController.ensureNoLocalSource();
9960
+ this.mediaController.validateExternalMediaSupport("local");
8599
9961
  token.setFailureScope(1 /* all */);
8600
9962
  this.launchTimer.start();
8601
9963
  this.clearVideoStatistics();
8602
- const stream = await this.localVideoController.create(options, this.mediaService, token.signal);
8603
- token.ensureCurrent();
8604
- await this.rtcManager.initialize();
8605
- token.ensureCurrent();
8606
- await this.rtcManager.setExternalMediaTracks({
8607
- videoTrack: stream.videoTrack.mediaStreamTrack,
8608
- audioTrack: this.localVideoController.audioTrack
8609
- });
9964
+ const stream = await this.mediaController.createLocalVideoStream(options, token.signal);
8610
9965
  token.ensureCurrent();
9966
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
8611
9967
  this.generationVideoFormat = stream.videoTrack?.videoFormat;
8612
9968
  await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
8613
9969
  return stream;
8614
9970
  });
8615
9971
  }
8616
9972
  /**
8617
- * 当前 Manager 拥有的媒体源轨道。
9973
+ * 创建本地图片静止视频源并准备 RTC,失败或取消时释放本次资源。
8618
9974
  */
8619
- get currentLocalTrack() {
8620
- return this.localVideoController.currentTrack ?? this.networkVideoController.currentTrack ?? this.cameraController.currentTrack;
9975
+ async createLocalImageStream(options) {
9976
+ return this.coordinator.run("media" /* media */, void 0, async (token) => {
9977
+ this.mediaController.ensureNoLocalSource();
9978
+ this.mediaController.validateExternalMediaSupport("image");
9979
+ token.setFailureScope(1 /* all */);
9980
+ this.launchTimer.start();
9981
+ this.clearVideoStatistics();
9982
+ const stream = await this.mediaController.createLocalImageStream(options, token.signal);
9983
+ token.ensureCurrent();
9984
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
9985
+ this.generationVideoFormat = stream.videoTrack?.videoFormat;
9986
+ await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
9987
+ return stream;
9988
+ });
8621
9989
  }
8622
9990
  /**
8623
- * 创建前拒绝覆盖活动源,避免旧预览或连接资源被静默遗留。
9991
+ * 接入外部视频轨;不创建采集设备或预览,所有异步失败统一释放 SDK 副本。
9992
+ * @param options.videoTrack 用户自行采集或处理的 live 视频轨,原始轨道归调用方所有。
9993
+ * @param options.videoFormat 期望的上传及生成规格,须符合模型规则;不代表修改输入像素。
8624
9994
  */
8625
- ensureNoLocalSource() {
8626
- if (this.currentLocalTrack || this.localVideoController.isActive) {
8627
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Close the current local stream before creating another one");
8628
- }
9995
+ async createExternalVideoStream(options) {
9996
+ return this.coordinator.run("media" /* media */, void 0, async (token) => {
9997
+ this.mediaController.ensureNoLocalSource();
9998
+ this.mediaController.validateExternalMediaSupport("external");
9999
+ const stream = this.mediaController.createExternalVideoStream(options);
10000
+ token.setFailureScope(1 /* all */);
10001
+ this.launchTimer.start();
10002
+ this.clearVideoStatistics();
10003
+ await this.mediaController.prepareSource(() => token.ensureCurrent());
10004
+ this.generationVideoFormat = stream.videoTrack?.videoFormat;
10005
+ await this.coordinator.commit(new RealtimeState({ connectionState: "Ready" /* ready */ }), token);
10006
+ return stream;
10007
+ });
10008
+ }
10009
+ /** 当前可发布的本地视频轨,不包含服务端直接读取的网络视频。 */
10010
+ get currentUplinkTrack() {
10011
+ return this.mediaController.uplinkTrack;
10012
+ }
10013
+ /** 当前 Manager 拥有的媒体源轨道。 */
10014
+ get currentLocalTrack() {
10015
+ return this.mediaController.currentTrack;
8629
10016
  }
8630
10017
  /**
8631
10018
  * 移除媒体权限弹窗观察;授权决定已记录或不再需要观察时调用。
@@ -8638,14 +10025,14 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8638
10025
  * 应用完整的上行视频格式,成功后更新本地轨道元数据,不改变生成格式或生命周期。
8639
10026
  * 复用配置操作准入,避免与建连、切换摄像头或其他配置更新并发。
8640
10027
  *
8641
- * @throws 无本地相机或文件视频流、参数无效、操作冲突或底层编码更新失败时抛错。
10028
+ * @throws 无本地相机、外部或文件视频流、参数无效、操作冲突或底层编码更新失败时抛错。
8642
10029
  */
8643
10030
  async updateUplinkVideoFormat(videoFormat) {
8644
10031
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
8645
- const track = this.localVideoController.currentTrack ?? this.cameraController.currentTrack;
10032
+ const track = this.currentUplinkTrack;
8646
10033
  const previousFormat = track?.videoFormat;
8647
10034
  if (!track || !previousFormat) {
8648
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local camera or file video stream is not started");
10035
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local camera, external or file video stream is not started");
8649
10036
  }
8650
10037
  videoFormat.validate();
8651
10038
  this.qualityController?.stop();
@@ -8671,11 +10058,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8671
10058
  }
8672
10059
  /** 手动仅调帧接口,成功后的当前格式将成为新的自适应 L1 基准。 */
8673
10060
  async applyUplinkFrameRate(fps, token) {
8674
- const track = this.localVideoController.currentTrack ?? this.cameraController.currentTrack;
10061
+ const track = this.currentUplinkTrack;
8675
10062
  const previous = track?.videoFormat;
8676
10063
  const state2 = this.currentState.connectionState;
8677
10064
  if (!track || !previous || state2 !== "Connected" /* connected */ && state2 !== "Generating" /* generating */) {
8678
- throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Frame rate updates require a connected local camera or file stream");
10065
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Frame rate updates require a connected local camera, external or file stream");
8679
10066
  }
8680
10067
  if (!Number.isFinite(fps) || fps <= 0) {
8681
10068
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Video frame rate must be greater than zero");
@@ -8702,7 +10089,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8702
10089
  downlink.resetSamples();
8703
10090
  return;
8704
10091
  }
8705
- const track = this.localVideoController.currentTrack ?? this.cameraController.currentTrack;
10092
+ const track = this.currentUplinkTrack;
8706
10093
  if (!track) {
8707
10094
  controller.stop();
8708
10095
  } else if (!controller.snapshot.active) {
@@ -8734,7 +10121,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8734
10121
  * 停止本地相机流并释放本地预览与 RTC 资源。
8735
10122
  */
8736
10123
  async stopLocalCameraStream() {
8737
- if (this.networkVideoController.currentTrack || this.localVideoController.isActive) {
10124
+ if (this.mediaController.source && this.mediaController.source !== "camera") {
8738
10125
  return;
8739
10126
  }
8740
10127
  await this.coordinator.run(
@@ -8744,7 +10131,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8744
10131
  this.launchTimer.cancel();
8745
10132
  this.clearVideoStatistics();
8746
10133
  this.disposePermissionWatch();
8747
- await this.cameraController.stopLocalCameraStream();
10134
+ await this.mediaController.stopLocalCameraStream();
8748
10135
  token.ensureCurrent();
8749
10136
  this.generationVideoFormat = void 0;
8750
10137
  await this.coordinator.commit(
@@ -8767,8 +10154,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8767
10154
  "cameraSwitch" /* cameraSwitch */,
8768
10155
  void 0,
8769
10156
  async (token) => {
10157
+ if (this.mediaController.source === "externalVideo") {
10158
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "External video capture is managed by the caller");
10159
+ }
8770
10160
  this.qualityController?.stop();
8771
- const stream = await this.cameraController.switchCamera();
10161
+ const stream = await this.mediaController.switchCamera();
8772
10162
  token.ensureCurrent();
8773
10163
  return stream;
8774
10164
  }
@@ -8781,7 +10171,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8781
10171
  * 启用帧检测时,发布前并行等待相机预热(固定 200ms),避免把黑帧推给 RTC。
8782
10172
  * 返回的远端媒体流在生成开始后承载远端生成画面。
8783
10173
  *
8784
- * @param localStream 由当前 Manager 创建的摄像头、本地文件或网络视频源。
10174
+ * @param localStream 由当前 Manager 创建的摄像头、外部视频轨、本地文件或网络视频源。
8785
10175
  * @returns 远端生成结果占位的媒体流。
8786
10176
  * @throws 本地流不属于当前 Manager、已有活动连接、会话创建或进房
8787
10177
  * 发布失败时抛出错误;失败时自动释放连接资源并恢复本地预览。
@@ -8805,18 +10195,24 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8805
10195
  /**
8806
10196
  * 按需建立连接并开始生成。
8807
10197
  *
8808
- * 尚未连接时先建立实时连接;已在生成时仅更新生成条件,不重启生成。
10198
+ * 尚未连接时先建立实时连接;已在生成时默认更新条件,restart 为 true 时重新开始生成。
8809
10199
  * 首次生成必须提供条件上下文,之后缺省时复用最近一次缓存的上下文。
8810
10200
  *
8811
- * @param options.localStream 由当前 Manager 创建的摄像头、本地文件或网络视频源。
10201
+ * @param options.localStream 由当前 Manager 创建的摄像头、外部视频轨、本地文件或网络视频源。
8812
10202
  * @param options.context 本次生成使用的条件上下文;缺省时复用缓存。
10203
+ * @param options.restart 是否保留 RTC 连接并创建新任务发送 start,默认 false;不预先发送 stop。
10204
+ * @param options.faceRestoration 是否启用原始人脸回贴,默认 false;正在生成时通过 change_condition 更新。
10205
+ * @param options.extraParams 合并到本次 start 或 change_condition 的 params;不缓存,自动恢复时不携带,不允许覆盖 SDK 字段。
10206
+ * @param options.record 是否录制远端原始音视频约 20 秒,默认 false;仅 start 有效,条件更新时忽略,重启录制暂不支持。
8813
10207
  * @returns 承载远端生成画面的媒体流。
10208
+ * @throws extraParams 包含非 JSON 数据、循环引用或 SDK 保留字段时,在连接或更新前抛出 invalidConfiguration。
8814
10209
  * @throws 本地流不属于当前 Manager、缺少可用的条件上下文、信令发送
8815
10210
  * 或生成确认失败时抛出错误;失败时自动停止生成任务并释放连接资源。
8816
10211
  */
8817
10212
  async startGeneration(options) {
10213
+ const extraParams = snapshotExtraParams(options.extraParams);
8818
10214
  return this.coordinator.run(
8819
- "generation" /* generation */,
10215
+ options.restart === true ? "generationRestart" /* generationRestart */ : "generation" /* generation */,
8820
10216
  void 0,
8821
10217
  async (token) => {
8822
10218
  const localTrack = options.localStream.videoTrack;
@@ -8834,33 +10230,50 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8834
10230
  );
8835
10231
  }
8836
10232
  const currentState = this.coordinator.currentState;
8837
- if (currentState.connectionState === "Generating" /* generating */) {
10233
+ const isGenerating = currentState.connectionState === "Generating" /* generating */;
10234
+ if (isGenerating && options.restart !== true) {
8838
10235
  await this.generationManager.update(currentState.taskID, {
8839
10236
  videoFormat,
10237
+ context: options.context,
8840
10238
  targetSize: this.downlinkQualityController ? this.downlinkQualityState?.size : void 0,
8841
- context: options.context
10239
+ faceRestoration: options.faceRestoration ?? false,
10240
+ extraParams
8842
10241
  });
8843
10242
  token.ensureCurrent();
8844
10243
  return this.connectionManager.makeRemoteStream();
8845
10244
  }
8846
10245
  const supplied = this.generationManager.validateContext(options.context);
8847
- const referenceVideo = this.networkVideoController.reference;
10246
+ const referenceVideo = this.mediaController.referenceVideo;
8848
10247
  if (!referenceVideo && supplied.referenceVideo) {
8849
10248
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Create a network video stream before using a reference video");
8850
10249
  }
8851
10250
  const context = referenceVideo ? new RealtimeContext({ ...supplied, referenceVideo }) : supplied;
8852
- if (currentState.connectionState !== "Connected" /* connected */) {
10251
+ if (currentState.connectionState !== "Connected" /* connected */ && !isGenerating) {
8853
10252
  await this.performConnect(options.localStream, token);
8854
10253
  }
8855
- return this.performStartGeneration(token, videoFormat, context);
10254
+ return this.performStartGeneration(
10255
+ token,
10256
+ videoFormat,
10257
+ context,
10258
+ options.restart ?? false,
10259
+ options.faceRestoration ?? false,
10260
+ extraParams,
10261
+ options.record ?? false
10262
+ );
8856
10263
  }
8857
10264
  );
8858
10265
  }
10266
+ /**
10267
+ * 设置录制最终事件监听器;清除监听不停止录制,录制错误不影响生成。
10268
+ */
10269
+ setRecordingListener(listener) {
10270
+ this.recordingController.setListener(listener);
10271
+ }
8859
10272
  /**
8860
10273
  * 断开实时连接并保留当前本地媒体预览。
8861
10274
  */
8862
10275
  async disconnect() {
8863
- this.localVideoController.pause();
10276
+ this.mediaController.pauseLocalVideo();
8864
10277
  this.launchTimer.cancel();
8865
10278
  this.clearVideoStatistics();
8866
10279
  await this.coordinator.disconnect();
@@ -8870,15 +10283,20 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8870
10283
  * 关闭期间重复调用会等待同一个释放任务;关闭完成后仍可重新创建本地流。
8871
10284
  */
8872
10285
  async close() {
8873
- this.localVideoController.pause();
10286
+ this.mediaController.pauseLocalVideo();
8874
10287
  this.launchTimer.cancel();
8875
10288
  this.clearVideoStatistics();
8876
10289
  await this.coordinator.terminate(1 /* all */);
8877
10290
  }
10291
+ /** TRTC 临时只按档位调整帧率;启动时也保留原始分辨率。 */
10292
+ uplinkQualityFormat(baseline, level) {
10293
+ const format = qualityVideoFormat(baseline, level);
10294
+ return this.options.provider === "trtc" /* trtc */ ? format.resized(baseline.width, baseline.height) : format;
10295
+ }
8878
10296
  /** 仅为尚无档位记录的本地上行选择初始档位;不应用配置或覆盖重连状态。 */
8879
10297
  resolveInitialUplinkQuality(track) {
8880
10298
  if (!this.qualityController) return void 0;
8881
- if (this.networkVideoController.currentTrack) return void 0;
10299
+ if (this.mediaController.source === "networkVideo") return void 0;
8882
10300
  if (this.qualityStates.has(track)) return void 0;
8883
10301
  const baseline = track.videoFormat;
8884
10302
  if (!baseline) return void 0;
@@ -8911,8 +10329,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8911
10329
  if (token.signal.aborted) abortWarmup();
8912
10330
  try {
8913
10331
  let beforePublish;
8914
- if (!this.localVideoController.isActive && !this.networkVideoController.currentTrack && this.cameraController.isFrameValidationEnabled) {
8915
- const cameraReady = this.cameraController.waitForValidCameraFrame(warmupController.signal).then(() => {
10332
+ if (this.mediaController.isFrameValidationEnabled) {
10333
+ const cameraReady = this.mediaController.waitForValidCameraFrame(warmupController.signal).then(() => {
8916
10334
  token.ensureCurrent();
8917
10335
  });
8918
10336
  void cameraReady.catch(() => {
@@ -8920,7 +10338,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8920
10338
  beforePublish = () => cameraReady;
8921
10339
  }
8922
10340
  const initialQuality = this.resolveInitialUplinkQuality(localTrack);
8923
- const initialFormat = initialQuality ? qualityVideoFormat(initialQuality.baseline, initialQuality.level) : void 0;
10341
+ const initialFormat = initialQuality ? this.uplinkQualityFormat(initialQuality.baseline, initialQuality.level) : void 0;
8924
10342
  const remote = await this.connectionManager.connect({
8925
10343
  localTrack,
8926
10344
  remoteVideoFormat: this.generationVideoFormat ?? localTrack.videoFormat,
@@ -8933,8 +10351,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8933
10351
  this.qualityHistory?.write("uplink", initialQuality.level);
8934
10352
  },
8935
10353
  model: this.options.model,
8936
- includeLocalAudio: this.localVideoController.isActive ? this.localVideoController.audioTrack !== void 0 : !this.networkVideoController.currentTrack && this.cameraController.useMicrophone,
8937
- publishLocalMedia: !this.networkVideoController.currentTrack,
10354
+ includeLocalAudio: this.mediaController.hasAudio,
10355
+ publishLocalMedia: this.mediaController.source !== "networkVideo",
8938
10356
  ensureCurrent: () => token.ensureCurrent(),
8939
10357
  onPublished: () => {
8940
10358
  this.acceptsVideoStatistics = true;
@@ -8956,9 +10374,21 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8956
10374
  }
8957
10375
  /**
8958
10376
  * 编排插帧、首帧计时和生成状态,任务与确认由生成管理器负责。
8959
- */
8960
- async performStartGeneration(token, videoFormat, context) {
10377
+ *
10378
+ * @param token 当前生成操作的取消与状态控制令牌。
10379
+ * @param videoFormat 模型生成使用的视频规格。
10380
+ * @param context 本次已解析的生成条件。
10381
+ * @param restart 是否替换当前生成任务并重新发送 start。
10382
+ * @param faceRestoration 是否回贴原始人脸。
10383
+ * @param extraParams 合并到本次 start 的 params,不缓存。
10384
+ * @param record 是否为本次任务开启独立录制,不影响生成返回时间。
10385
+ * @returns 承载生成结果的远端媒体流。
10386
+ */
10387
+ async performStartGeneration(token, videoFormat, context, restart, faceRestoration, extraParams, record2 = false) {
8961
10388
  token.setFailureScope(0 /* connection */);
10389
+ if (restart) {
10390
+ void this.recordingController.stop("restarted" /* restarted */);
10391
+ }
8962
10392
  const initialLevel = this.qualityHistory?.read("downlink") ?? 1;
8963
10393
  const targetSize = this.downlinkQualityController ? qualityVideoSize(videoFormat, initialLevel) : void 0;
8964
10394
  await this.prepareFrameInterpolation(targetSize ?? videoFormat, token);
@@ -8968,20 +10398,33 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8968
10398
  };
8969
10399
  const taskID = await this.generationManager.start({
8970
10400
  videoFormat,
8971
- targetSize,
8972
- context,
8973
- mirror: this.currentLocalTrack === this.cameraController.currentTrack && this.cameraController.currentTrack?.position === "front" /* front */,
8974
10401
  signal: token.signal,
8975
10402
  ensureCurrent: () => token.ensureCurrent(),
8976
- onStartSent: this.localVideoController.isActive ? async () => {
8977
- token.ensureCurrent();
8978
- await this.localVideoController.start(token.signal);
8979
- token.ensureCurrent();
8980
- } : void 0,
8981
10403
  waitUntilRemoteReady: () => this.connectionManager.waitUntilRemoteTrackReady(
8982
10404
  REMOTE_FIRST_FRAME_TIMEOUT_MS,
8983
10405
  token.signal
8984
- )
10406
+ ),
10407
+ context,
10408
+ restart,
10409
+ onRemoteReady: record2 ? (taskID2) => {
10410
+ if (restart) {
10411
+ this.recordingController.reportFailure(taskID2, new XmaxError(
10412
+ "INVALID_CONFIGURATION" /* invalidConfiguration */,
10413
+ "Recording restarted generation tasks is not supported"
10414
+ ));
10415
+ } else {
10416
+ this.recordingController.start(taskID2, () => this.streamController.getRemoteRecordingTracks());
10417
+ }
10418
+ } : void 0,
10419
+ targetSize,
10420
+ mirror: this.mediaController.source === "camera" && this.currentLocalTrack?.position === "front" /* front */,
10421
+ faceRestoration,
10422
+ onStartSent: this.mediaController.source === "localVideo" ? async () => {
10423
+ token.ensureCurrent();
10424
+ await this.mediaController.startLocalVideo(token.signal);
10425
+ token.ensureCurrent();
10426
+ } : void 0,
10427
+ extraParams
8985
10428
  });
8986
10429
  const sessionID = this.connectionManager.currentSessionID;
8987
10430
  if (!sessionID) {
@@ -8999,13 +10442,14 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
8999
10442
  token
9000
10443
  );
9001
10444
  this.qualityHistory?.write("downlink", initialLevel);
9002
- const onFinish = this.networkVideoController.onFinish;
9003
- if (this.networkVideoController.currentTrack) {
9004
- this.streamController.activateNetworkVideoCompletion(onFinish && (() => {
10445
+ const onFinish = this.mediaController.onNetworkVideoFinish;
10446
+ if (this.mediaController.source === "networkVideo") {
10447
+ this.streamController.activateNetworkVideoCompletion(() => {
9005
10448
  if (this.currentState.taskID === taskID && this.currentState.connectionState === "Generating" /* generating */) {
9006
- return onFinish();
10449
+ void this.recordingController.stop("generationEnded" /* generationEnded */);
10450
+ return onFinish?.();
9007
10451
  }
9008
- }));
10452
+ });
9009
10453
  }
9010
10454
  return this.connectionManager.makeRemoteStream();
9011
10455
  }
@@ -9023,7 +10467,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9023
10467
  * 统一清理顺序:停止心跳与生成,再断开连接;all 额外释放本地媒体。
9024
10468
  */
9025
10469
  async performCleanup(scope, taskID) {
9026
- this.localVideoController.pause();
10470
+ const recordingFinished = this.recordingController.stop("generationEnded" /* generationEnded */);
10471
+ this.mediaController.pauseLocalVideo();
9027
10472
  this.launchTimer.cancel();
9028
10473
  this.remoteFrameDisplayHandler = void 0;
9029
10474
  this.clearVideoStatistics();
@@ -9033,26 +10478,17 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
9033
10478
  this.connectionManager.stopHeartbeat();
9034
10479
  await this.generationManager.reset(taskID);
9035
10480
  const sessionID = await this.connectionManager.disconnect();
10481
+ await recordingFinished;
9036
10482
  if (scope === 1 /* all */) {
9037
- const hadFileSource = this.networkVideoController.currentTrack !== void 0 || this.localVideoController.isActive;
9038
- this.networkVideoController.stop();
9039
10483
  this.generationVideoFormat = void 0;
9040
10484
  this.disposePermissionWatch();
9041
10485
  try {
9042
- if (hadFileSource) {
9043
- await this.rtcManager.destroy();
9044
- } else {
9045
- await this.cameraController.stopLocalCameraStream();
9046
- }
10486
+ await this.mediaController.stopLocalStream();
9047
10487
  } catch (error) {
9048
10488
  XmaxLogger.realtime.error(
9049
10489
  () => `\u91CA\u653E\u672C\u5730\u5A92\u4F53\u5931\u8D25 (Failed to Release Local Media)
9050
10490
  \u2514\u2500 ${XmaxLogger.localized("\u539F\u56E0\uFF1A", "Reason: ")}${XmaxError.from(error).message}`
9051
10491
  );
9052
- } finally {
9053
- if (this.localVideoController.isActive) {
9054
- await this.localVideoController.stop();
9055
- }
9056
10492
  }
9057
10493
  }
9058
10494
  return {
@@ -9630,13 +11066,6 @@ var RealtimeConfiguration = class {
9630
11066
  }
9631
11067
  };
9632
11068
 
9633
- // src/Foundation/Media/Video/VideoContentMode.ts
9634
- var VideoContentMode = /* @__PURE__ */ ((VideoContentMode2) => {
9635
- VideoContentMode2["fit"] = "fit";
9636
- VideoContentMode2["fill"] = "fill";
9637
- return VideoContentMode2;
9638
- })(VideoContentMode || {});
9639
-
9640
11069
  // src/Render/Video/RenderStatisticsController.ts
9641
11070
  var RenderStatisticsController = class {
9642
11071
  /**
@@ -12146,6 +13575,498 @@ var RemoteVideoFramePipeline = class {
12146
13575
  }
12147
13576
  };
12148
13577
 
13578
+ // src/Render/Trajectory/TrajectoryOverlayView.ts
13579
+ var TrajectoryOverlayView = class {
13580
+ /**
13581
+ * 安装透明交互层及独立 Renderer;只有任务就绪时接管指针。
13582
+ */
13583
+ constructor(renderer) {
13584
+ __publicField(this, "renderer", renderer);
13585
+ /**
13586
+ * 视图与显示配置
13587
+ */
13588
+ __publicField(this, "element", document.createElement("div"));
13589
+ __publicField(this, "binding");
13590
+ __publicField(this, "format");
13591
+ __publicField(this, "contentMode", "fill" /* fill */);
13592
+ __publicField(this, "mirrored", false);
13593
+ __publicField(this, "requested", true);
13594
+ __publicField(this, "enabled", false);
13595
+ /**
13596
+ * 手势采样与生命周期资源
13597
+ */
13598
+ __publicField(this, "pointers", /* @__PURE__ */ new Map());
13599
+ __publicField(this, "nextID", 0);
13600
+ __publicField(this, "animation");
13601
+ __publicField(this, "lastSample", 0);
13602
+ __publicField(this, "resizeObserver");
13603
+ __publicField(this, "viewport");
13604
+ __publicField(this, "visibilityChanged", () => {
13605
+ if (document.hidden) {
13606
+ this.cancel();
13607
+ }
13608
+ this.updateStyle();
13609
+ });
13610
+ __publicField(this, "onDown", (event) => this.begin(event));
13611
+ __publicField(this, "onMove", (event) => this.move(event));
13612
+ __publicField(this, "onEnd", (event) => this.end(event.pointerId));
13613
+ Object.assign(this.element.style, { position: "absolute", inset: "0", zIndex: "2", overflow: "hidden" });
13614
+ Object.assign(renderer.element.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
13615
+ this.element.appendChild(renderer.element);
13616
+ this.element.addEventListener("pointerdown", this.onDown);
13617
+ this.element.addEventListener("pointermove", this.onMove);
13618
+ this.element.addEventListener("pointerup", this.onEnd);
13619
+ this.element.addEventListener("pointercancel", this.onEnd);
13620
+ this.element.addEventListener("lostpointercapture", this.onEnd);
13621
+ document.addEventListener("visibilitychange", this.visibilityChanged);
13622
+ if (typeof ResizeObserver !== "undefined") {
13623
+ this.resizeObserver = new ResizeObserver(() => this.cancel());
13624
+ this.resizeObserver.observe(this.element);
13625
+ }
13626
+ this.updateStyle();
13627
+ }
13628
+ /**
13629
+ * 更新任务绑定,清除旧任务手势和采样。
13630
+ */
13631
+ setBinding(binding, format, enabled = false) {
13632
+ this.cancel();
13633
+ this.binding = binding;
13634
+ this.format = format;
13635
+ this.enabled = enabled;
13636
+ this.updateStyle();
13637
+ }
13638
+ /**
13639
+ * 同步显示配置;配置变化后旧坐标不再继续发送。
13640
+ */
13641
+ configure(mode, mirrored, enabled) {
13642
+ if (this.contentMode !== mode || this.mirrored !== mirrored || this.requested !== enabled) {
13643
+ this.cancel();
13644
+ }
13645
+ this.contentMode = mode;
13646
+ this.mirrored = mirrored;
13647
+ this.requested = enabled;
13648
+ this.updateStyle();
13649
+ }
13650
+ /**
13651
+ * 卸载时取消采样、动画、捕获和事件监听;Renderer 可以再次挂载。
13652
+ */
13653
+ dispose() {
13654
+ this.binding?.detach(this);
13655
+ this.cancel();
13656
+ this.resizeObserver?.disconnect();
13657
+ document.removeEventListener("visibilitychange", this.visibilityChanged);
13658
+ this.element.removeEventListener("pointerdown", this.onDown);
13659
+ this.element.removeEventListener("pointermove", this.onMove);
13660
+ this.element.removeEventListener("pointerup", this.onEnd);
13661
+ this.element.removeEventListener("pointercancel", this.onEnd);
13662
+ this.element.removeEventListener("lostpointercapture", this.onEnd);
13663
+ this.element.remove();
13664
+ }
13665
+ /**
13666
+ * 无任务或被禁用时不阻止页面滚动。
13667
+ */
13668
+ updateStyle() {
13669
+ const active = this.requested && this.enabled && !!this.binding && !document.hidden;
13670
+ this.element.style.pointerEvents = active ? "auto" : "none";
13671
+ this.element.style.touchAction = active ? "none" : "auto";
13672
+ this.element.style.userSelect = active ? "none" : "auto";
13673
+ }
13674
+ /**
13675
+ * 根据实际显示区域拒绝黑边,移动到视口外时限制在可见视频边缘。
13676
+ */
13677
+ point(event, id, clamp) {
13678
+ const viewport = this.element.getBoundingClientRect();
13679
+ if (!this.format) {
13680
+ return void 0;
13681
+ }
13682
+ const rect = InteractionCoordinateMapper.displayedFrame(viewport, this.format, this.contentMode);
13683
+ if (!rect || !Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) {
13684
+ return void 0;
13685
+ }
13686
+ const minX = Math.max(0, rect.x);
13687
+ const minY = Math.max(0, rect.y);
13688
+ const maxX = Math.min(viewport.width, rect.x + rect.width);
13689
+ const maxY = Math.min(viewport.height, rect.y + rect.height);
13690
+ let x = event.clientX - viewport.left;
13691
+ let y = event.clientY - viewport.top;
13692
+ if (!clamp && (x < minX || x > maxX || y < minY || y > maxY)) {
13693
+ return void 0;
13694
+ }
13695
+ x = Math.max(minX, Math.min(maxX, x));
13696
+ y = Math.max(minY, Math.min(maxY, y));
13697
+ return Object.freeze({
13698
+ id,
13699
+ location: Object.freeze({ x, y }),
13700
+ normalizedLocation: Object.freeze({ x: (x - rect.x) / rect.width, y: (y - rect.y) / rect.height }),
13701
+ timestamp: performance.now()
13702
+ });
13703
+ }
13704
+ /**
13705
+ * 按下立即采样,不等待定时窗口;只接受鼠标主键。
13706
+ */
13707
+ begin(event) {
13708
+ if (!this.requested || !this.enabled || !this.binding || document.hidden || event.button !== 0) {
13709
+ return;
13710
+ }
13711
+ const point = this.point(event, ++this.nextID, false);
13712
+ if (!point) {
13713
+ return;
13714
+ }
13715
+ event.preventDefault();
13716
+ this.pointers.set(event.pointerId, point);
13717
+ try {
13718
+ this.element.setPointerCapture(event.pointerId);
13719
+ } catch {
13720
+ }
13721
+ this.render(() => this.renderer.renderBegan([point]));
13722
+ this.sample(performance.now(), true);
13723
+ this.schedule();
13724
+ }
13725
+ /**
13726
+ * 高频指针事件只更新最新位置及视觉效果,不直接发送网络消息。
13727
+ */
13728
+ move(event) {
13729
+ const previous = this.pointers.get(event.pointerId);
13730
+ if (!previous) {
13731
+ return;
13732
+ }
13733
+ const point = this.point(event, previous.id, true);
13734
+ if (point) {
13735
+ this.pointers.set(event.pointerId, point);
13736
+ this.render(() => this.renderer.renderMoved([point]));
13737
+ }
13738
+ }
13739
+ /**
13740
+ * 释放单个指针,多指交互继续;最后一个指针释放后停止采样。
13741
+ */
13742
+ end(pointerID) {
13743
+ const point = this.pointers.get(pointerID);
13744
+ if (!point) {
13745
+ return;
13746
+ }
13747
+ this.pointers.delete(pointerID);
13748
+ try {
13749
+ this.element.releasePointerCapture(pointerID);
13750
+ } catch {
13751
+ }
13752
+ this.render(() => this.renderer.renderEnded([point.id]));
13753
+ if (!this.pointers.size) {
13754
+ this.stopSampling();
13755
+ }
13756
+ }
13757
+ /**
13758
+ * 取消全部手势及视觉效果,不保留跨生命周期轨迹。
13759
+ */
13760
+ cancel() {
13761
+ for (const id of [...this.pointers.keys()]) {
13762
+ this.end(id);
13763
+ }
13764
+ this.stopSampling();
13765
+ this.render(() => this.renderer.reset());
13766
+ }
13767
+ /**
13768
+ * 清理采样回调与视口快照。
13769
+ */
13770
+ stopSampling() {
13771
+ if (this.animation !== void 0) {
13772
+ cancelAnimationFrame(this.animation);
13773
+ }
13774
+ this.animation = void 0;
13775
+ this.viewport = void 0;
13776
+ this.lastSample = 0;
13777
+ }
13778
+ /**
13779
+ * 只在存在活动触点时运行动画时钟。
13780
+ */
13781
+ schedule() {
13782
+ if (this.animation === void 0 && this.pointers.size) {
13783
+ this.animation = requestAnimationFrame((now) => {
13784
+ this.animation = void 0;
13785
+ this.sample(now);
13786
+ this.schedule();
13787
+ });
13788
+ }
13789
+ }
13790
+ /**
13791
+ * 固定频率发送所有活动触点的快照,布局变化时取消而非发送旧坐标。
13792
+ */
13793
+ sample(now, force = false) {
13794
+ if (document.hidden) {
13795
+ this.cancel();
13796
+ return;
13797
+ }
13798
+ const { width, height } = this.element.getBoundingClientRect();
13799
+ if (this.viewport && (width !== this.viewport.width || height !== this.viewport.height)) {
13800
+ this.cancel();
13801
+ return;
13802
+ }
13803
+ this.viewport = { width, height };
13804
+ if (!force && now - this.lastSample < 1e3 / 30) {
13805
+ return;
13806
+ }
13807
+ this.lastSample = now;
13808
+ this.binding?.submit({
13809
+ points: [...this.pointers.values()].map((point) => point.location),
13810
+ viewportSize: this.viewport,
13811
+ contentMode: this.contentMode,
13812
+ mirrored: this.mirrored
13813
+ });
13814
+ }
13815
+ /**
13816
+ * 自定义效果异常不影响触点采集和网络发送。
13817
+ */
13818
+ render(action) {
13819
+ try {
13820
+ action();
13821
+ } catch {
13822
+ XmaxLogger.render.warning(() => "\u8F68\u8FF9\u6548\u679C\u7ED8\u5236\u5931\u8D25 (Trajectory Rendering Failed)");
13823
+ }
13824
+ }
13825
+ };
13826
+
13827
+ // src/Render/Trajectory/DefaultTrajectoryEffectRenderer.ts
13828
+ var VISUAL = {
13829
+ outerWidth: 18,
13830
+ outerBlur: 12,
13831
+ outerAlpha: 0.35,
13832
+ middleWidth: 10,
13833
+ middleBlur: 6,
13834
+ middleAlpha: 0.75,
13835
+ coreWidth: 3,
13836
+ headRadius: 5,
13837
+ headGlowRadius: 14,
13838
+ headGlowAlpha: 0.9,
13839
+ ringCount: 2,
13840
+ ringStartRadius: 10,
13841
+ ringSpacing: 14,
13842
+ ringLineWidth: 2,
13843
+ ringPulseSpeed: 1.2,
13844
+ ringMaxAlpha: 0.6,
13845
+ particleCount: 4,
13846
+ particleRadius: 3,
13847
+ particleOrbitRadius: 22,
13848
+ particleSpeed: 0.06,
13849
+ particleAlpha: 0.6,
13850
+ fadeAlpha: 0.05,
13851
+ idleFadeFrames: 64
13852
+ };
13853
+ var DefaultTrajectoryEffectRenderer = class {
13854
+ /**
13855
+ * 安装独立视觉图层,不注册指针事件。
13856
+ */
13857
+ constructor() {
13858
+ /**
13859
+ * 画布与动画资源;尾迹保留像素渐隐,触点装饰每帧重绘。
13860
+ */
13861
+ __publicField(this, "element", document.createElement("div"));
13862
+ __publicField(this, "trailCanvas", document.createElement("canvas"));
13863
+ __publicField(this, "effectCanvas", document.createElement("canvas"));
13864
+ __publicField(this, "animation");
13865
+ __publicField(this, "hasTrailPixels", false);
13866
+ __publicField(this, "idleFadeFrames", 0);
13867
+ /**
13868
+ * 活动触点和等待下一帧绘制的线段。
13869
+ */
13870
+ __publicField(this, "pointers", /* @__PURE__ */ new Map());
13871
+ __publicField(this, "segments", []);
13872
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
13873
+ Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
13874
+ this.element.appendChild(canvas);
13875
+ }
13876
+ }
13877
+ /**
13878
+ * 开始触点装饰,脉冲动画以本次按下时间为起点。
13879
+ */
13880
+ renderBegan(points) {
13881
+ for (const point of points) {
13882
+ this.pointers.set(point.id, { point, startedAt: point.timestamp });
13883
+ }
13884
+ this.idleFadeFrames = 0;
13885
+ this.schedule();
13886
+ }
13887
+ /**
13888
+ * 只绘制新线段,旧像素自然衰减,不反复加亮整条历史轨迹。
13889
+ */
13890
+ renderMoved(points) {
13891
+ for (const point of points) {
13892
+ const pointer = this.pointers.get(point.id);
13893
+ if (pointer) {
13894
+ this.segments.push({ from: pointer.point, to: point });
13895
+ pointer.point = point;
13896
+ }
13897
+ }
13898
+ this.schedule();
13899
+ }
13900
+ /**
13901
+ * 释放触点后移除装饰,仅保留尾迹的渐隐动画。
13902
+ */
13903
+ renderEnded(identifiers) {
13904
+ for (const id of identifiers) {
13905
+ this.pointers.delete(id);
13906
+ }
13907
+ this.schedule();
13908
+ }
13909
+ /**
13910
+ * 清除画面、待绘制数据及动画,允许同一 Renderer 再次挂载。
13911
+ */
13912
+ reset() {
13913
+ if (this.animation !== void 0) {
13914
+ cancelAnimationFrame(this.animation);
13915
+ }
13916
+ this.animation = void 0;
13917
+ this.pointers.clear();
13918
+ this.segments.length = 0;
13919
+ this.hasTrailPixels = false;
13920
+ this.idleFadeFrames = 0;
13921
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
13922
+ canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
13923
+ }
13924
+ }
13925
+ /**
13926
+ * 仅在触点、待绘制线段或残留尾迹存在时申请下一帧。
13927
+ */
13928
+ schedule() {
13929
+ if (this.animation === void 0 && (this.pointers.size || this.segments.length || this.hasTrailPixels)) {
13930
+ this.animation = requestAnimationFrame((now) => this.draw(now));
13931
+ }
13932
+ }
13933
+ /**
13934
+ * 同步画布分辨率,分别更新尾迹和触点视觉图层。
13935
+ */
13936
+ draw(now) {
13937
+ this.animation = void 0;
13938
+ const trail = this.trailCanvas.getContext("2d");
13939
+ const effects = this.effectCanvas.getContext("2d");
13940
+ if (!trail || !effects) {
13941
+ this.reset();
13942
+ return;
13943
+ }
13944
+ const rect = this.element.getBoundingClientRect();
13945
+ const dpr = globalThis.devicePixelRatio || 1;
13946
+ const width = Math.max(1, Math.round(rect.width * dpr));
13947
+ const height = Math.max(1, Math.round(rect.height * dpr));
13948
+ for (const canvas of [this.trailCanvas, this.effectCanvas]) {
13949
+ if (canvas.width !== width || canvas.height !== height) {
13950
+ canvas.width = width;
13951
+ canvas.height = height;
13952
+ this.hasTrailPixels = false;
13953
+ }
13954
+ }
13955
+ if (this.hasTrailPixels) {
13956
+ trail.save();
13957
+ trail.globalCompositeOperation = "destination-out";
13958
+ trail.fillStyle = "rgba(0,0,0," + VISUAL.fadeAlpha + ")";
13959
+ trail.fillRect(0, 0, width, height);
13960
+ trail.restore();
13961
+ }
13962
+ if (this.segments.length) {
13963
+ this.drawSegments(trail, dpr);
13964
+ this.segments.length = 0;
13965
+ this.hasTrailPixels = true;
13966
+ this.idleFadeFrames = 0;
13967
+ }
13968
+ effects.clearRect(0, 0, width, height);
13969
+ effects.save();
13970
+ effects.globalCompositeOperation = "lighter";
13971
+ for (const { point, startedAt } of this.pointers.values()) {
13972
+ const x = point.location.x * dpr;
13973
+ const y = point.location.y * dpr;
13974
+ if (this.pointers.size === 1) {
13975
+ this.drawRings(effects, x, y, dpr, (now - startedAt) / 1e3);
13976
+ this.drawParticles(effects, x, y, dpr, (now - startedAt) / 1e3);
13977
+ }
13978
+ this.drawGlow(effects, x, y, VISUAL.headGlowRadius * dpr, VISUAL.headGlowAlpha, true);
13979
+ effects.fillStyle = "rgba(255,255,255,1)";
13980
+ effects.beginPath();
13981
+ effects.arc(x, y, VISUAL.headRadius * dpr, 0, Math.PI * 2);
13982
+ effects.fill();
13983
+ }
13984
+ effects.restore();
13985
+ if (this.pointers.size) {
13986
+ this.idleFadeFrames = 0;
13987
+ } else if (this.hasTrailPixels && ++this.idleFadeFrames > VISUAL.idleFadeFrames) {
13988
+ trail.clearRect(0, 0, width, height);
13989
+ this.hasTrailPixels = false;
13990
+ this.idleFadeFrames = 0;
13991
+ }
13992
+ this.schedule();
13993
+ }
13994
+ /**
13995
+ * 叠加绿色外晕、中层和白色线芯;多指时省略阴影降低绘制开销。
13996
+ */
13997
+ drawSegments(context, dpr) {
13998
+ const layers = [
13999
+ { width: VISUAL.outerWidth, blur: VISUAL.outerBlur, color: this.green(VISUAL.outerAlpha) },
14000
+ { width: VISUAL.middleWidth, blur: VISUAL.middleBlur, color: this.green(VISUAL.middleAlpha) },
14001
+ { width: VISUAL.coreWidth, blur: 0, color: "rgba(255,255,255,1)" }
14002
+ ];
14003
+ for (const layer of layers) {
14004
+ context.save();
14005
+ context.globalCompositeOperation = "lighter";
14006
+ context.lineCap = "round";
14007
+ context.lineJoin = "round";
14008
+ context.lineWidth = layer.width * dpr;
14009
+ context.strokeStyle = layer.color;
14010
+ context.shadowBlur = this.pointers.size > 1 ? 0 : layer.blur * dpr;
14011
+ context.shadowColor = context.shadowBlur ? layer.color : "transparent";
14012
+ context.beginPath();
14013
+ for (const { from, to } of this.segments) {
14014
+ context.moveTo(from.location.x * dpr, from.location.y * dpr);
14015
+ context.lineTo(to.location.x * dpr, to.location.y * dpr);
14016
+ }
14017
+ context.stroke();
14018
+ context.restore();
14019
+ }
14020
+ }
14021
+ /**
14022
+ * 绘制触点周围两层不同相位的脉冲圆环。
14023
+ */
14024
+ drawRings(context, x, y, dpr, elapsed) {
14025
+ context.lineWidth = VISUAL.ringLineWidth * dpr;
14026
+ for (let index = 0; index < VISUAL.ringCount; index++) {
14027
+ const pulse = (Math.sin((elapsed * VISUAL.ringPulseSpeed + index * 0.5) * Math.PI * 2) + 1) / 2;
14028
+ const radius = (VISUAL.ringStartRadius + index * VISUAL.ringSpacing + pulse * 6) * dpr;
14029
+ context.strokeStyle = this.green(VISUAL.ringMaxAlpha * (1 - index * 0.2) * (0.5 + pulse * 0.5));
14030
+ context.beginPath();
14031
+ context.arc(x, y, radius, 0, Math.PI * 2);
14032
+ context.stroke();
14033
+ }
14034
+ }
14035
+ /**
14036
+ * 绘制四个反向交错运动的柔和环绕粒子。
14037
+ */
14038
+ drawParticles(context, x, y, dpr, elapsed) {
14039
+ for (let index = 0; index < VISUAL.particleCount; index++) {
14040
+ const direction = index % 2 === 0 ? 1 : -1;
14041
+ const angle = index / VISUAL.particleCount * Math.PI * 2 + elapsed * VISUAL.particleSpeed * direction;
14042
+ const radius = VISUAL.particleOrbitRadius * dpr;
14043
+ const alpha = VISUAL.particleAlpha * (0.6 + Math.sin(elapsed * 3 + index) * 0.4);
14044
+ this.drawGlow(context, x + Math.cos(angle) * radius, y + Math.sin(angle) * radius, VISUAL.particleRadius * 2 * dpr, alpha);
14045
+ }
14046
+ }
14047
+ /**
14048
+ * 绘制径向绿色光晕,触点核心增加中间色阶。
14049
+ */
14050
+ drawGlow(context, x, y, radius, alpha, head = false) {
14051
+ const gradient = context.createRadialGradient(x, y, 0, x, y, radius);
14052
+ gradient.addColorStop(0, this.green(alpha));
14053
+ if (head) {
14054
+ gradient.addColorStop(0.4, this.green(alpha * 0.6));
14055
+ }
14056
+ gradient.addColorStop(1, this.green(0));
14057
+ context.fillStyle = gradient;
14058
+ context.beginPath();
14059
+ context.arc(x, y, radius, 0, Math.PI * 2);
14060
+ context.fill();
14061
+ }
14062
+ /**
14063
+ * 统一轨迹主题色,透明度由各视觉层指定。
14064
+ */
14065
+ green(alpha) {
14066
+ return "rgba(0,255,100," + alpha + ")";
14067
+ }
14068
+ };
14069
+
12149
14070
  // src/Render/Video/XmaxVideoView.ts
12150
14071
  var OBJECT_FIT = {
12151
14072
  ["fit" /* fit */]: "contain",
@@ -12203,7 +14124,14 @@ var XmaxVideoView = class {
12203
14124
  * DOM 挂载状态
12204
14125
  */
12205
14126
  __publicField(this, "detached", false);
14127
+ /**
14128
+ * 轨迹交互配置与懒加载视图
14129
+ */
14130
+ __publicField(this, "interactionEnabled", true);
14131
+ __publicField(this, "customTrajectoryRenderer");
14132
+ __publicField(this, "trajectoryOverlay");
12206
14133
  this.contentMode = options?.videoContentMode ?? "fill" /* fill */;
14134
+ this.interactionEnabled = options?.isInteractionEnabled ?? true;
12207
14135
  this.element = document.createElement("div");
12208
14136
  this.element.style.position = "relative";
12209
14137
  this.element.style.overflow = "hidden";
@@ -12220,6 +14148,36 @@ var XmaxVideoView = class {
12220
14148
  this.applyContentMode();
12221
14149
  this.element.appendChild(this.videoElement);
12222
14150
  }
14151
+ /**
14152
+ * 是否允许远端轨迹交互;本地轨道没有交互绑定,不受此开关影响。
14153
+ */
14154
+ get isInteractionEnabled() {
14155
+ return this.interactionEnabled;
14156
+ }
14157
+ /**
14158
+ * 关闭时立即取消当前手势,不影响视频播放。
14159
+ */
14160
+ set isInteractionEnabled(enabled) {
14161
+ this.interactionEnabled = enabled;
14162
+ this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, enabled);
14163
+ }
14164
+ /**
14165
+ * 自定义轨迹视觉效果;undefined 表示使用 SDK 默认效果。
14166
+ */
14167
+ get trajectoryRenderer() {
14168
+ return this.customTrajectoryRenderer;
14169
+ }
14170
+ /**
14171
+ * 替换效果前释放旧动画,采样及网络发送仍由 SDK 管理。
14172
+ */
14173
+ set trajectoryRenderer(renderer) {
14174
+ if (this.customTrajectoryRenderer === renderer) {
14175
+ return;
14176
+ }
14177
+ this.disposeTrajectory();
14178
+ this.customTrajectoryRenderer = renderer;
14179
+ this.attachTrajectory();
14180
+ }
12223
14181
  /**
12224
14182
  * 当前显示的视频轨道;切换时自动解绑旧轨道。
12225
14183
  */
@@ -12292,6 +14250,7 @@ var XmaxVideoView = class {
12292
14250
  attach(to) {
12293
14251
  this.detached = false;
12294
14252
  to.appendChild(this.element);
14253
+ this.attachTrajectory();
12295
14254
  if ((this.videoElement.srcObject || this.videoElement.getAttribute("src")) && this.videoElement.autoplay && !this.videoElement.ended && this.videoElement.paused && typeof this.videoElement.play === "function") {
12296
14255
  void this.videoElement.play().catch((error) => {
12297
14256
  XmaxLogger.render.warning(() => `\u6062\u590D\u89C6\u9891\u64AD\u653E\u5931\u8D25 (Failed to Resume Video Playback)
@@ -12309,6 +14268,7 @@ var XmaxVideoView = class {
12309
14268
  */
12310
14269
  detach() {
12311
14270
  this.detached = true;
14271
+ this.disposeTrajectory();
12312
14272
  this.statisticsController.stop();
12313
14273
  this.stopInterpolation();
12314
14274
  this.cancelFrameDisplayNotification();
@@ -12415,6 +14375,7 @@ var XmaxVideoView = class {
12415
14375
  const binding = VideoRenderRegistry.binding(track);
12416
14376
  if (binding) {
12417
14377
  binding.attachHandler(this, this.contentMode);
14378
+ this.attachTrajectory();
12418
14379
  return;
12419
14380
  }
12420
14381
  if (track.mediaStreamTrack) {
@@ -12429,6 +14390,7 @@ var XmaxVideoView = class {
12429
14390
  * 解绑当前轨道并清空渲染内容。
12430
14391
  */
12431
14392
  detachCurrentTrack() {
14393
+ this.disposeTrajectory();
12432
14394
  this.statisticsController.stop();
12433
14395
  const track = this.currentTrack;
12434
14396
  if (track) {
@@ -12488,6 +14450,7 @@ var XmaxVideoView = class {
12488
14450
  * 应用显示模式与镜像样式。
12489
14451
  */
12490
14452
  applyContentMode() {
14453
+ this.trajectoryOverlay?.configure(this.contentMode, this.mirrored, this.interactionEnabled);
12491
14454
  this.videoElement.style.objectFit = OBJECT_FIT[this.contentMode];
12492
14455
  this.videoElement.style.transform = this.mirrored ? "scaleX(-1)" : "";
12493
14456
  if (this.interpolationCanvas) {
@@ -12501,6 +14464,30 @@ var XmaxVideoView = class {
12501
14464
  startRenderStatistics() {
12502
14465
  if (!this.detached) this.statisticsController.start();
12503
14466
  }
14467
+ /**
14468
+ * 仅为具备交互能力的轨道创建覆盖层,普通视频和本地预览不分配 Canvas。
14469
+ */
14470
+ attachTrajectory() {
14471
+ if (this.detached || this.trajectoryOverlay || !this.currentTrack) {
14472
+ return;
14473
+ }
14474
+ const binding = TrajectoryRegistry.binding(this.currentTrack);
14475
+ if (!binding) {
14476
+ return;
14477
+ }
14478
+ const overlay = new TrajectoryOverlayView(this.customTrajectoryRenderer ?? new DefaultTrajectoryEffectRenderer());
14479
+ this.trajectoryOverlay = overlay;
14480
+ overlay.configure(this.contentMode, this.mirrored, this.interactionEnabled);
14481
+ this.element.appendChild(overlay.element);
14482
+ binding.attach(overlay);
14483
+ }
14484
+ /**
14485
+ * 换轨或卸载时移除全部交互资源;再次挂载时重新建立绑定。
14486
+ */
14487
+ disposeTrajectory() {
14488
+ this.trajectoryOverlay?.dispose();
14489
+ this.trajectoryOverlay = void 0;
14490
+ }
12504
14491
  };
12505
14492
 
12506
14493
  // src/Render/Video/XmaxRealtimeVideoView.ts
@@ -12539,8 +14526,8 @@ var XmaxRealtimeVideoView = class {
12539
14526
  this.element.style.position = "relative";
12540
14527
  this.element.style.overflow = "hidden";
12541
14528
  this.element.style.backgroundColor = "black";
12542
- this.localVideoView = new XmaxVideoView({ videoContentMode: this.contentMode });
12543
- this.remoteVideoView = new XmaxVideoView({ videoContentMode: this.contentMode });
14529
+ this.localVideoView = new XmaxVideoView({ videoContentMode: this.contentMode, isInteractionEnabled: false });
14530
+ this.remoteVideoView = new XmaxVideoView({ videoContentMode: this.contentMode, isInteractionEnabled: options?.isInteractionEnabled });
12544
14531
  for (const view of [this.localVideoView, this.remoteVideoView]) {
12545
14532
  view.element.style.position = "absolute";
12546
14533
  view.element.style.inset = "0";
@@ -12555,6 +14542,30 @@ var XmaxRealtimeVideoView = class {
12555
14542
  get localTrack() {
12556
14543
  return this.currentLocalTrack;
12557
14544
  }
14545
+ /**
14546
+ * 只控制远端视图的轨迹交互,本地预览始终不接管手势。
14547
+ */
14548
+ get isInteractionEnabled() {
14549
+ return this.remoteVideoView.isInteractionEnabled;
14550
+ }
14551
+ /**
14552
+ * 转交远端交互开关。
14553
+ */
14554
+ set isInteractionEnabled(enabled) {
14555
+ this.remoteVideoView.isInteractionEnabled = enabled;
14556
+ }
14557
+ /**
14558
+ * 远端视图的自定义轨迹效果。
14559
+ */
14560
+ get trajectoryRenderer() {
14561
+ return this.remoteVideoView.trajectoryRenderer;
14562
+ }
14563
+ /**
14564
+ * undefined 恢复默认效果,不改变轨迹发送逻辑。
14565
+ */
14566
+ set trajectoryRenderer(renderer) {
14567
+ this.remoteVideoView.trajectoryRenderer = renderer;
14568
+ }
12558
14569
  /**
12559
14570
  * 替换本地预览轨道;轨道未变化时保留当前绑定。
12560
14571
  */
@@ -12686,6 +14697,9 @@ export {
12686
14697
  RealtimeVideoTrack,
12687
14698
  RealtimeMediaStream,
12688
14699
  RealtimeVideoSampleMethod,
14700
+ VideoContentMode,
14701
+ RealtimeRecordingEventType,
14702
+ RealtimeRecordingFinishReason,
12689
14703
  RealtimeContext,
12690
14704
  RealtimeConnectionState,
12691
14705
  RealtimeReason,
@@ -12696,8 +14710,7 @@ export {
12696
14710
  NonRealtimeTaskError,
12697
14711
  XmaxClient,
12698
14712
  RealtimeConfiguration,
12699
- VideoContentMode,
12700
14713
  XmaxVideoView,
12701
14714
  XmaxRealtimeVideoView
12702
14715
  };
12703
- //# sourceMappingURL=chunk-4O5BSMUX.js.map
14716
+ //# sourceMappingURL=chunk-ILZL4W2G.js.map