@xmaxai/web-sdk 1.0.2 → 1.0.4

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.
@@ -3,10 +3,10 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
5
  // src/Foundation/Runtime/XmaxEnvironment.ts
6
- var XmaxEnvironment = /* @__PURE__ */ ((XmaxEnvironment2) => {
7
- XmaxEnvironment2["china"] = "china";
8
- XmaxEnvironment2["global"] = "global";
9
- return XmaxEnvironment2;
6
+ var XmaxEnvironment = /* @__PURE__ */ ((XmaxEnvironment3) => {
7
+ XmaxEnvironment3["china"] = "china";
8
+ XmaxEnvironment3["global"] = "global";
9
+ return XmaxEnvironment3;
10
10
  })(XmaxEnvironment || {});
11
11
  var API_BASE_URLS = {
12
12
  ["china" /* china */]: "https://cloud.xmax.22duck.cn/open/api/v1",
@@ -198,7 +198,7 @@ var XmaxError = class _XmaxError extends Error {
198
198
  };
199
199
 
200
200
  // src/Foundation/Runtime/RuntimeInfo.ts
201
- var XMAX_SDK_VERSION = "1.0.2";
201
+ var XMAX_SDK_VERSION = "1.0.4";
202
202
  var _RuntimeInfo = class _RuntimeInfo {
203
203
  /**
204
204
  * 序列化为接口传输使用的 snake_case 字段。
@@ -263,10 +263,12 @@ var _ApiService = class _ApiService {
263
263
  }
264
264
  /**
265
265
  * 发送请求并返回统一响应中的数据。
266
+ *
267
+ * @param options.keepalive 为 true 时请求可存活于页面卸载之后。
266
268
  */
267
- async request(method, path, body) {
269
+ async request(method, path, body, options) {
268
270
  this.validateConfiguration();
269
- const request = this.makeRequest(method, path, body);
271
+ const request = this.makeRequest(method, path, body, options);
270
272
  const startedAt = Date.now();
271
273
  let status;
272
274
  let text;
@@ -313,8 +315,8 @@ var _ApiService = class _ApiService {
313
315
  /**
314
316
  * 发送 DELETE 请求。
315
317
  */
316
- delete(path) {
317
- return this.request("DELETE" /* delete */, path);
318
+ delete(path, options) {
319
+ return this.request("DELETE" /* delete */, path, void 0, options);
318
320
  }
319
321
  /**
320
322
  * 校验 API Key 与 Base URL 配置。
@@ -344,7 +346,7 @@ var _ApiService = class _ApiService {
344
346
  /**
345
347
  * 构造请求地址、请求头与请求体;路径必须相对且与 Base URL 同源。
346
348
  */
347
- makeRequest(method, path, body) {
349
+ makeRequest(method, path, body, options) {
348
350
  const normalizedPath = path.trim();
349
351
  if (normalizedPath.length === 0 || normalizedPath.includes("://") || normalizedPath.startsWith("//")) {
350
352
  throw new XmaxError(
@@ -385,7 +387,8 @@ var _ApiService = class _ApiService {
385
387
  "X-SDK-Version": XMAX_SDK_VERSION
386
388
  },
387
389
  body: encodedBody,
388
- signal: AbortSignal.timeout(this.timeoutMs)
390
+ signal: AbortSignal.timeout(this.timeoutMs),
391
+ ..._optionalChain([options, 'optionalAccess', _4 => _4.keepalive]) ? { keepalive: true } : {}
389
392
  }
390
393
  };
391
394
  }
@@ -403,7 +406,7 @@ var _ApiService = class _ApiService {
403
406
  }
404
407
  const ok = status >= 200 && status < 300;
405
408
  if (!ok || envelope.success !== true) {
406
- const message = _optionalChain([envelope, 'access', _4 => _4.message, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]);
409
+ const message = _optionalChain([envelope, 'access', _5 => _5.message, 'optionalAccess', _6 => _6.trim, 'call', _7 => _7()]);
407
410
  throw new XmaxError(
408
411
  "API_ERROR" /* apiError */,
409
412
  message || "Xmax API request failed",
@@ -518,22 +521,22 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
518
521
  * @throws 尺寸、帧率或码率配置无效时抛出错误。
519
522
  */
520
523
  validate() {
521
- if (this.width <= 0 || this.height <= 0 || this.fps <= 0 || this.width % 2 !== 0 || this.height % 2 !== 0) {
524
+ if (this.width <= 0 || this.height <= 0 || !Number.isFinite(this.fps) || this.fps <= 0 || this.width % 2 !== 0 || this.height % 2 !== 0) {
522
525
  throw new XmaxError(
523
526
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
524
527
  "Realtime video width and height must be positive even numbers, and fps must be greater than zero"
525
528
  );
526
529
  }
527
- if (this.minimumBitrate !== void 0 && this.minimumBitrate < 0) {
530
+ if (this.minimumBitrate !== void 0 && (!Number.isFinite(this.minimumBitrate) || this.minimumBitrate < 0)) {
528
531
  throw new XmaxError(
529
532
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
530
- "Minimum bitrate must not be negative"
533
+ "Minimum bitrate must be finite and not negative"
531
534
  );
532
535
  }
533
- if (this.maximumBitrate !== void 0 && this.maximumBitrate <= 0) {
536
+ if (this.maximumBitrate !== void 0 && (!Number.isFinite(this.maximumBitrate) || this.maximumBitrate <= 0)) {
534
537
  throw new XmaxError(
535
538
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
536
- "Maximum bitrate must be greater than zero"
539
+ "Maximum bitrate must be finite and greater than zero"
537
540
  );
538
541
  }
539
542
  if (this.minimumBitrate !== void 0 && this.maximumBitrate !== void 0 && this.minimumBitrate > this.maximumBitrate) {
@@ -561,26 +564,39 @@ var RealtimeVideoFormat = class _RealtimeVideoFormat {
561
564
  // src/Service/Realtime/RealtimeModel.ts
562
565
  var RealtimeModel = /* @__PURE__ */ ((RealtimeModel2) => {
563
566
  RealtimeModel2["x2_0"] = "x2.0";
564
- RealtimeModel2["x2_0_pro"] = "x2.0-pro";
565
567
  RealtimeModel2["x2_0_trtc"] = "x2.0-trtc";
568
+ RealtimeModel2["x2_0_agora"] = "x2.0-agora";
569
+ RealtimeModel2["x2_1_preview"] = "x2.1-preview";
566
570
  return RealtimeModel2;
567
571
  })(RealtimeModel || {});
572
+ var MODEL_BASE_URLS = {
573
+ ["x2.0-agora" /* x2_0_agora */]: "https://dev.xmaxai.com/open/api/v1",
574
+ ["x2.1-preview" /* x2_1_preview */]: "https://dev.xmaxai.com/open/api/v1"
575
+ };
568
576
  var RESOLUTION_BUCKETS = {
569
- ["x2.0" /* x2_0 */]: [],
570
- ["x2.0-pro" /* x2_0_pro */]: [
577
+ ["x2.1-preview" /* x2_1_preview */]: [
571
578
  { width: 1024, height: 1920 },
572
579
  { width: 1920, height: 1024 }
573
580
  ],
581
+ ["x2.0-agora" /* x2_0_agora */]: [
582
+ { width: 1024, height: 1920 },
583
+ { width: 1920, height: 1024 }
584
+ ],
585
+ ["x2.0" /* x2_0 */]: [],
574
586
  ["x2.0-trtc" /* x2_0_trtc */]: [
575
587
  { width: 1024, height: 1920 },
576
588
  { width: 1920, height: 1024 }
577
589
  ]
578
590
  };
579
591
  var MAXIMUM_INPUT_PIXELS = {
592
+ ["x2.1-preview" /* x2_1_preview */]: 21e5,
593
+ ["x2.0-agora" /* x2_0_agora */]: 21e5,
580
594
  ["x2.0" /* x2_0 */]: 128e4,
581
- ["x2.0-pro" /* x2_0_pro */]: 21e5,
582
595
  ["x2.0-trtc" /* x2_0_trtc */]: 21e5
583
596
  };
597
+ function modelBaseURL(model) {
598
+ return MODEL_BASE_URLS[model];
599
+ }
584
600
  function resolutionBuckets(model) {
585
601
  return RESOLUTION_BUCKETS[model];
586
602
  }
@@ -609,7 +625,7 @@ async function frameInterpolationAdapter(size) {
609
625
  if (size && (!Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width <= 0 || size.height <= 0)) return null;
610
626
  try {
611
627
  const adapter = await navigator.gpu.requestAdapter();
612
- if (!_optionalChain([adapter, 'optionalAccess', _7 => _7.features, 'access', _8 => _8.has, 'call', _9 => _9("shader-f16")])) return null;
628
+ if (!_optionalChain([adapter, 'optionalAccess', _8 => _8.features, 'access', _9 => _9.has, 'call', _10 => _10("shader-f16")])) return null;
613
629
  if (size && (Math.ceil(size.width / 16) * 16 > adapter.limits.maxTextureDimension2D || Math.ceil(size.height / 16) * 16 > adapter.limits.maxTextureDimension2D)) return null;
614
630
  return adapter;
615
631
  } catch (e5) {
@@ -823,7 +839,7 @@ var StorageService = class _StorageService {
823
839
  const startedAt = Date.now();
824
840
  try {
825
841
  const safeName = _StorageService.validateFileName(options.fileName);
826
- const contentType = _optionalChain([options, 'access', _10 => _10.contentType, 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()]) || _StorageService.inferContentType(safeName);
842
+ const contentType = _optionalChain([options, 'access', _11 => _11.contentType, 'optionalAccess', _12 => _12.trim, 'call', _13 => _13()]) || _StorageService.inferContentType(safeName);
827
843
  const body = _StorageService.makeBody(options.data, contentType);
828
844
  if (body.size === 0) {
829
845
  throw new XmaxError(
@@ -845,7 +861,7 @@ var StorageService = class _StorageService {
845
861
  Key: objectKey,
846
862
  Body: body,
847
863
  ContentType: contentType,
848
- onProgress: options.onProgress ? (info) => _optionalChain([options, 'access', _13 => _13.onProgress, 'optionalCall', _14 => _14(info.loaded, info.total)]) : void 0
864
+ onProgress: options.onProgress ? (info) => _optionalChain([options, 'access', _14 => _14.onProgress, 'optionalCall', _15 => _15(info.loaded, info.total)]) : void 0
849
865
  });
850
866
  const url = _StorageService.resolveObjectURL(
851
867
  uploaded.Location,
@@ -877,9 +893,9 @@ var StorageService = class _StorageService {
877
893
  const bucket = _StorageService.nonEmpty(payload.bucket);
878
894
  const region = _StorageService.nonEmpty(payload.region);
879
895
  const prefix = _StorageService.nonEmpty(payload.prefix);
880
- const accessKeyID = _StorageService.nonEmpty(_optionalChain([payload, 'access', _15 => _15.credentials, 'optionalAccess', _16 => _16.accessKeyId]));
881
- const secretAccessKey = _StorageService.nonEmpty(_optionalChain([payload, 'access', _17 => _17.credentials, 'optionalAccess', _18 => _18.secretAccessKey]));
882
- const sessionToken = _StorageService.nonEmpty(_optionalChain([payload, 'access', _19 => _19.credentials, 'optionalAccess', _20 => _20.sessionToken]));
896
+ const accessKeyID = _StorageService.nonEmpty(_optionalChain([payload, 'access', _16 => _16.credentials, 'optionalAccess', _17 => _17.accessKeyId]));
897
+ const secretAccessKey = _StorageService.nonEmpty(_optionalChain([payload, 'access', _18 => _18.credentials, 'optionalAccess', _19 => _19.secretAccessKey]));
898
+ const sessionToken = _StorageService.nonEmpty(_optionalChain([payload, 'access', _20 => _20.credentials, 'optionalAccess', _21 => _21.sessionToken]));
883
899
  if (!bucket || !region || !prefix || !accessKeyID || !secretAccessKey || !sessionToken) {
884
900
  throw new XmaxError("API_ERROR" /* apiError */, message);
885
901
  }
@@ -920,7 +936,7 @@ var StorageService = class _StorageService {
920
936
  reject(
921
937
  new XmaxError(
922
938
  "UPLOAD_ERROR" /* uploadError */,
923
- _optionalChain([error, 'access', _21 => _21.message, 'optionalAccess', _22 => _22.trim, 'call', _23 => _23()]) || "Storage upload failed"
939
+ _optionalChain([error, 'access', _22 => _22.message, 'optionalAccess', _23 => _23.trim, 'call', _24 => _24()]) || "Storage upload failed"
924
940
  )
925
941
  );
926
942
  return;
@@ -934,14 +950,14 @@ var StorageService = class _StorageService {
934
950
  * 其次拼接自定义 endpoint,最后回退到默认 COS 域名。
935
951
  */
936
952
  static resolveObjectURL(location, configuration, objectKey) {
937
- const candidate = _nullishCoalesce(_optionalChain([location, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()]), () => ( ""));
953
+ const candidate = _nullishCoalesce(_optionalChain([location, 'optionalAccess', _25 => _25.trim, 'call', _26 => _26()]), () => ( ""));
938
954
  if (candidate.startsWith("//")) {
939
955
  return `https:${candidate}`;
940
956
  }
941
957
  if (/^https?:\/\//i.test(candidate)) {
942
958
  return candidate;
943
959
  }
944
- const endpoint = _optionalChain([configuration, 'access', _26 => _26.endpoint, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()]);
960
+ const endpoint = _optionalChain([configuration, 'access', _27 => _27.endpoint, 'optionalAccess', _28 => _28.trim, 'call', _29 => _29()]);
945
961
  if (endpoint) {
946
962
  const normalized = /^https?:\/\//i.test(endpoint) ? endpoint : `https://${endpoint}`;
947
963
  return `${normalized.replace(/\/+$/, "")}/${objectKey}`;
@@ -975,7 +991,7 @@ var StorageService = class _StorageService {
975
991
  * 按文件名后缀推断内容类型。
976
992
  */
977
993
  static inferContentType(fileName) {
978
- const extension = _nullishCoalesce(_optionalChain([fileName, 'access', _29 => _29.split, 'call', _30 => _30("."), 'access', _31 => _31.pop, 'call', _32 => _32(), 'optionalAccess', _33 => _33.toLowerCase, 'call', _34 => _34()]), () => ( ""));
994
+ const extension = _nullishCoalesce(_optionalChain([fileName, 'access', _30 => _30.split, 'call', _31 => _31("."), 'access', _32 => _32.pop, 'call', _33 => _33(), 'optionalAccess', _34 => _34.toLowerCase, 'call', _35 => _35()]), () => ( ""));
979
995
  return _nullishCoalesce(CONTENT_TYPES[extension], () => ( "application/octet-stream"));
980
996
  }
981
997
  /**
@@ -994,7 +1010,7 @@ var StorageService = class _StorageService {
994
1010
  * 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
995
1011
  */
996
1012
  static nonEmpty(value) {
997
- const normalized = _optionalChain([value, 'optionalAccess', _35 => _35.trim, 'call', _36 => _36()]);
1013
+ const normalized = _optionalChain([value, 'optionalAccess', _36 => _36.trim, 'call', _37 => _37()]);
998
1014
  return normalized || void 0;
999
1015
  }
1000
1016
  };
@@ -1006,7 +1022,780 @@ var CameraPosition = /* @__PURE__ */ ((CameraPosition3) => {
1006
1022
  return CameraPosition3;
1007
1023
  })(CameraPosition || {});
1008
1024
 
1009
- // src/Foundation/RTC/RtcEngineManager.ts
1025
+ // src/Foundation/Permissions/MediaPermissionWatch.ts
1026
+ var PERMISSION_POLL_INTERVAL_MS = 100;
1027
+ async function watchMediaPermissionPrompt(includeMicrophone) {
1028
+ if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _38 => _38.permissions, 'optionalAccess', _39 => _39.query])) {
1029
+ return void 0;
1030
+ }
1031
+ const names = ["camera", ...includeMicrophone ? ["microphone"] : []];
1032
+ const queryOnce = () => Promise.all(
1033
+ names.map(async (name) => {
1034
+ const status = await navigator.permissions.query({
1035
+ name
1036
+ });
1037
+ return status.state;
1038
+ })
1039
+ );
1040
+ const queryStates = () => Promise.all(
1041
+ names.map(async (name) => {
1042
+ try {
1043
+ const status = await navigator.permissions.query({
1044
+ name
1045
+ });
1046
+ return status.state;
1047
+ } catch (e6) {
1048
+ return "prompt";
1049
+ }
1050
+ })
1051
+ );
1052
+ let states;
1053
+ try {
1054
+ states = await queryOnce();
1055
+ } catch (e7) {
1056
+ return void 0;
1057
+ }
1058
+ if (states.every((state2) => state2 === "granted")) {
1059
+ return { decided: Promise.resolve(), dispose() {
1060
+ } };
1061
+ }
1062
+ if (!states.includes("prompt")) {
1063
+ return void 0;
1064
+ }
1065
+ let disposed = false;
1066
+ let resolveDecided;
1067
+ const decided = new Promise((resolve) => {
1068
+ resolveDecided = resolve;
1069
+ });
1070
+ const finishIfDecided = (current) => {
1071
+ if (disposed || current.includes("prompt")) {
1072
+ return;
1073
+ }
1074
+ dispose();
1075
+ resolveDecided();
1076
+ };
1077
+ const poll = setInterval(() => {
1078
+ void queryStates().then(finishIfDecided);
1079
+ }, PERMISSION_POLL_INTERVAL_MS);
1080
+ function dispose() {
1081
+ disposed = true;
1082
+ clearInterval(poll);
1083
+ }
1084
+ return { decided, dispose };
1085
+ }
1086
+
1087
+ // src/Foundation/RTC/Agora/AgoraRtcStatistics.ts
1088
+ var AgoraRtcStatistics = class _AgoraRtcStatistics {
1089
+ /**
1090
+ * 采集已发布的视频和已订阅的结果流统计,性能日志不控制回调采集。
1091
+ */
1092
+ static collect(client, listener) {
1093
+ const rtc = client.getRTCStats();
1094
+ const local = client.getLocalVideoStats();
1095
+ const valid = _AgoraRtcStatistics.valid;
1096
+ const loss = _AgoraRtcStatistics.loss;
1097
+ const localVideo = client.localTracks.some((track) => track.trackMediaType === "video") ? {
1098
+ width: valid(local.sendResolutionWidth, 1),
1099
+ height: valid(local.sendResolutionHeight, 1),
1100
+ frameRate: valid(local.sendFrameRate),
1101
+ bitrateKbps: valid(local.sendBitrate / 1e3),
1102
+ uplinkLossPercent: loss(local.currentPacketLossRate)
1103
+ } : void 0;
1104
+ const remote = Object.entries(client.getRemoteVideoStats()).map(([userID, stats]) => ({
1105
+ userID,
1106
+ width: valid(stats.receiveResolutionWidth, 1),
1107
+ height: valid(stats.receiveResolutionHeight, 1),
1108
+ frameRate: valid(stats.receiveFrameRate),
1109
+ bitrateKbps: valid(stats.receiveBitrate / 1e3),
1110
+ rttMs: valid(rtc.RTT),
1111
+ uplinkLossPercent: _optionalChain([localVideo, 'optionalAccess', _40 => _40.uplinkLossPercent]),
1112
+ downlinkLossPercent: loss(stats.currentPacketLossRate),
1113
+ endToEndDelayMs: valid(stats.end2EndDelay)
1114
+ }));
1115
+ _optionalChain([listener, 'optionalAccess', _41 => _41.onLocalVideoStatistics, 'optionalCall', _42 => _42(localVideo && Object.freeze(localVideo))]);
1116
+ _optionalChain([listener, 'optionalAccess', _43 => _43.onRemoteVideoStatistics, 'optionalCall', _44 => _44(Object.freeze(remote.map((item) => Object.freeze(item))))]);
1117
+ XmaxLogger.rtc.info(() => `Agora \u8FD0\u884C\u7EDF\u8BA1 (Agora RTC Statistics)
1118
+ ${JSON.stringify({
1119
+ rttMs: valid(rtc.RTT),
1120
+ sentBytes: valid(rtc.SendBytes),
1121
+ receivedBytes: valid(rtc.RecvBytes),
1122
+ localVideo,
1123
+ remoteVideo: remote,
1124
+ localAudio: client.getLocalAudioStats(),
1125
+ remoteAudio: client.getRemoteAudioStats()
1126
+ }, null, 2)}`, 2 /* performance */);
1127
+ }
1128
+ /**
1129
+ * 网络质量等级范围与公共枚举一致;不将整体 RTT 冒充上下行独立 RTT。
1130
+ */
1131
+ static network(stats, listener) {
1132
+ const quality = (value) => Number.isInteger(value) && value >= 0 && value <= 6 ? value : void 0;
1133
+ const result = Object.freeze({ uplinkQuality: quality(stats.uplinkNetworkQuality), downlinkQuality: quality(stats.downlinkNetworkQuality) });
1134
+ _optionalChain([listener, 'optionalAccess', _45 => _45.onNetworkStatistics, 'optionalCall', _46 => _46(result)]);
1135
+ XmaxLogger.rtc.info(() => `Agora \u7F51\u7EDC\u8D28\u91CF (Agora Network Quality)
1136
+ ${JSON.stringify(result, null, 2)}`, 2 /* performance */);
1137
+ }
1138
+ /**
1139
+ * 过滤缺失、非有限或不合理的统计值。
1140
+ */
1141
+ static valid(value, minimum = 0) {
1142
+ return typeof value === "number" && Number.isFinite(value) && value >= minimum ? value : void 0;
1143
+ }
1144
+ /**
1145
+ * 声网丢包率为 0–1 比例,转换为公共百分比。
1146
+ */
1147
+ static loss(value) {
1148
+ return Number.isFinite(value) && value >= 0 && value <= 1 ? value * 100 : void 0;
1149
+ }
1150
+ };
1151
+
1152
+ // src/Foundation/RTC/RtcVideoOrientation.ts
1153
+ function shouldTransposeRtcVideoSize(environment) {
1154
+ const userAgent = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _47 => _47.userAgent]), () => ( (typeof navigator === "undefined" ? "" : navigator.userAgent)));
1155
+ const maxTouchPoints = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _48 => _48.maxTouchPoints]), () => ( (typeof navigator === "undefined" ? 0 : _nullishCoalesce(navigator.maxTouchPoints, () => ( 0)))));
1156
+ const mobile = /Android|webOS|iPhone|iPad|iPod|Mobile/i.test(userAgent) || /Macintosh/i.test(userAgent) && maxTouchPoints > 1;
1157
+ if (!mobile) {
1158
+ return false;
1159
+ }
1160
+ const orientationType = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _49 => _49.orientationType]), () => ( (typeof window === "undefined" ? void 0 : _optionalChain([window, 'access', _50 => _50.screen, 'optionalAccess', _51 => _51.orientation, 'optionalAccess', _52 => _52.type]))));
1161
+ if (orientationType !== void 0) {
1162
+ return orientationType.includes("portrait");
1163
+ }
1164
+ const degrees = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _53 => _53.orientationDegrees]), () => ( (typeof window === "undefined" ? void 0 : window.orientation)));
1165
+ return degrees === 0 || degrees === 180;
1166
+ }
1167
+ function rtcOrientedVideoSize(size, environment) {
1168
+ return shouldTransposeRtcVideoSize(environment) ? { width: size.height, height: size.width } : size;
1169
+ }
1170
+
1171
+ // src/Foundation/RTC/Agora/AgoraRtcManager.ts
1172
+ var _AgoraRtcManager = class _AgoraRtcManager {
1173
+ /**
1174
+ * 保存服务环境与加载器,不触发浏览器依赖或设备权限请求。
1175
+ */
1176
+ constructor(options) {
1177
+ /**
1178
+ * 配置与 RTC 资源
1179
+ */
1180
+ __publicField(this, "environment");
1181
+ __publicField(this, "loadSDK");
1182
+ __publicField(this, "sdk");
1183
+ __publicField(this, "client");
1184
+ __publicField(this, "initialization");
1185
+ __publicField(this, "lifecycle", 0);
1186
+ __publicField(this, "camera");
1187
+ __publicField(this, "microphone");
1188
+ __publicField(this, "connection");
1189
+ __publicField(this, "roomVersion", 0);
1190
+ /**
1191
+ * 凭据恢复与发布意图;RTC 自动断开可能清空 localTracks,因此单独保留发布意图。
1192
+ */
1193
+ __publicField(this, "tokenExpired", false);
1194
+ __publicField(this, "rejoinOperation");
1195
+ __publicField(this, "rejoinTask");
1196
+ __publicField(this, "cancelRejoin");
1197
+ __publicField(this, "videoPublishRequested", false);
1198
+ __publicField(this, "audioPublishRequested", false);
1199
+ /**
1200
+ * 事件、统计与音频播放状态
1201
+ */
1202
+ __publicField(this, "listener");
1203
+ __publicField(this, "statisticsTimer");
1204
+ __publicField(this, "audioVolumes", /* @__PURE__ */ new Map());
1205
+ __publicField(this, "requestedAudio", /* @__PURE__ */ new Set());
1206
+ /**
1207
+ * DataStream 串行发送与限速状态;等待中的消息通过房间版本取消。
1208
+ */
1209
+ __publicField(this, "sendTail", Promise.resolve());
1210
+ __publicField(this, "sentPackets", []);
1211
+ __publicField(this, "cancelSendDelay");
1212
+ __publicField(this, "cancelActiveSend");
1213
+ this.environment = options.environment;
1214
+ this.loadSDK = _nullishCoalesce(options.loadSDK, () => ( (async () => (await Promise.resolve().then(() => _interopRequireWildcard(require("agora-rtc-sdk-ng")))).default)));
1215
+ }
1216
+ /**
1217
+ * 当前实例是否已创建声网 client。
1218
+ */
1219
+ get isInitialized() {
1220
+ return this.client !== void 0;
1221
+ }
1222
+ /**
1223
+ * 声网音频轨支持播放前设置音量;零音量额外停止播放以防浏览器差异。
1224
+ */
1225
+ get supportsRemoteAudioVolumeControl() {
1226
+ return true;
1227
+ }
1228
+ /**
1229
+ * 共享本实例的初始化任务,区域完全由接入方环境决定。
1230
+ */
1231
+ async initialize() {
1232
+ if (this.client) return;
1233
+ if (this.initialization) return this.initialization;
1234
+ const version = this.lifecycle;
1235
+ const operation = this.performInitialize(version);
1236
+ this.initialization = operation;
1237
+ try {
1238
+ await operation;
1239
+ } finally {
1240
+ if (this.initialization === operation) this.initialization = void 0;
1241
+ }
1242
+ }
1243
+ /**
1244
+ * 加载模块、锁定区域并注册事件;迟到的初始化不能复活已销毁实例。
1245
+ */
1246
+ async performInitialize(version) {
1247
+ const sdk = await this.loadSDK();
1248
+ if (version !== this.lifecycle) throw this.cancelled();
1249
+ const region = _AgoraRtcManager.regions.get(sdk);
1250
+ if (region && region.environment !== this.environment) {
1251
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Concurrent Agora clients must use the same XmaxEnvironment");
1252
+ }
1253
+ sdk.setLogLevel(4);
1254
+ if (!region) sdk.setArea([this.environment === "china" /* china */ ? "CHINA" : "GLOBAL"]);
1255
+ const client = sdk.createClient({ mode: "rtc", codec: "h264" });
1256
+ if (typeof client.sendStreamMessage !== "function") {
1257
+ throw new XmaxError("RTC_ERROR" /* rtcError */, "Agora DataStream is unavailable in this SDK version");
1258
+ }
1259
+ _AgoraRtcManager.regions.set(sdk, { environment: this.environment, count: (_nullishCoalesce(_optionalChain([region, 'optionalAccess', _54 => _54.count]), () => ( 0))) + 1 });
1260
+ this.sdk = sdk;
1261
+ this.client = client;
1262
+ this.registerEvents(client);
1263
+ }
1264
+ /**
1265
+ * 先失效全部异步工作,再释放房间、采集、监听和区域引用。
1266
+ */
1267
+ async destroy() {
1268
+ this.lifecycle++;
1269
+ const client = this.client;
1270
+ this.listener = void 0;
1271
+ this.clearRoom();
1272
+ this.client = void 0;
1273
+ _optionalChain([client, 'optionalAccess', _55 => _55.removeAllListeners, 'call', _56 => _56()]);
1274
+ _optionalChain([this, 'access', _57 => _57.camera, 'optionalAccess', _58 => _58.close, 'call', _59 => _59()]);
1275
+ _optionalChain([this, 'access', _60 => _60.microphone, 'optionalAccess', _61 => _61.close, 'call', _62 => _62()]);
1276
+ this.camera = void 0;
1277
+ this.microphone = void 0;
1278
+ const sdk = this.sdk;
1279
+ this.sdk = void 0;
1280
+ try {
1281
+ await _optionalChain([client, 'optionalAccess', _63 => _63.leave, 'call', _64 => _64()]);
1282
+ } catch (e8) {
1283
+ XmaxLogger.rtc.warning(() => "Agora \u9000\u623F\u5931\u8D25\uFF0C\u5DF2\u91CA\u653E\u672C\u5730\u8D44\u6E90 (Agora Leave Failed)");
1284
+ } finally {
1285
+ const region = sdk && _AgoraRtcManager.regions.get(sdk);
1286
+ if (sdk && region && --region.count === 0) _AgoraRtcManager.regions.delete(sdk);
1287
+ }
1288
+ }
1289
+ /**
1290
+ * 通过声网采集相机但不发布,返回原生轨道供公共渲染层使用。
1291
+ */
1292
+ async startCameraCapture(options) {
1293
+ const client = this.requireClient();
1294
+ if (this.camera) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Camera capture is already running");
1295
+ try {
1296
+ const captureSize = rtcOrientedVideoSize(options);
1297
+ const camera = await this.sdk.createCameraVideoTrack({
1298
+ facingMode: options.position === "front" /* front */ ? "user" : "environment",
1299
+ encoderConfig: { width: captureSize.width, height: captureSize.height, frameRate: options.frameRate }
1300
+ });
1301
+ if (this.client !== client) {
1302
+ camera.close();
1303
+ throw this.cancelled();
1304
+ }
1305
+ this.camera = camera;
1306
+ return camera.getMediaStreamTrack();
1307
+ } catch (error) {
1308
+ throw this.mapError(error, "CAMERA_PERMISSION_DENIED" /* cameraPermissionDenied */);
1309
+ }
1310
+ }
1311
+ /**
1312
+ * 切换声网相机设备并返回切换后的原生轨道。
1313
+ */
1314
+ async switchCameraCapture(to) {
1315
+ const camera = this.requireCamera();
1316
+ await this.run(() => camera.setDevice({ facingMode: to === "front" /* front */ ? "user" : "environment" }));
1317
+ if (this.camera !== camera) throw this.cancelled();
1318
+ return camera.getMediaStreamTrack();
1319
+ }
1320
+ /**
1321
+ * 停止采集并释放摄像头。
1322
+ */
1323
+ async stopCameraCapture() {
1324
+ const camera = this.camera;
1325
+ this.camera = void 0;
1326
+ _optionalChain([camera, 'optionalAccess', _65 => _65.close, 'call', _66 => _66()]);
1327
+ }
1328
+ /**
1329
+ * 应用公共编码配置,补偿移动端竖屏的宽高转置。
1330
+ */
1331
+ async configureVideoEncoding(config) {
1332
+ const camera = this.requireCamera();
1333
+ const encodingSize = rtcOrientedVideoSize(config);
1334
+ await this.run(async () => {
1335
+ await camera.setEncoderConfiguration({
1336
+ width: encodingSize.width,
1337
+ height: encodingSize.height,
1338
+ frameRate: config.frameRate,
1339
+ bitrateMin: config.minimumBitrate,
1340
+ bitrateMax: config.maximumBitrate
1341
+ });
1342
+ await camera.setOptimizationMode(config.encoderPreference === "maintainFramerate" /* maintainFramerate */ ? "motion" : "detail");
1343
+ });
1344
+ }
1345
+ /**
1346
+ * 使用会话下发的字符串频道与 UID 入房;不采信后端区域覆盖。
1347
+ */
1348
+ async joinRoom(configuration) {
1349
+ await _optionalChain([this, 'access', _67 => _67.rejoinTask, 'optionalAccess', _68 => _68.catch, 'call', _69 => _69(() => {
1350
+ })]);
1351
+ const client = this.requireClient();
1352
+ if (configuration.provider !== "agora" || this.connection) {
1353
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Agora credentials and an idle room are required");
1354
+ }
1355
+ const version = ++this.roomVersion;
1356
+ await this.run(() => client.join(configuration.appID, configuration.roomID, configuration.roomToken, configuration.userID));
1357
+ if (client !== this.client || version !== this.roomVersion) {
1358
+ await client.leave();
1359
+ throw this.cancelled();
1360
+ }
1361
+ this.connection = configuration;
1362
+ for (const user of client.remoteUsers) {
1363
+ if (user.hasVideo) _optionalChain([this, 'access', _70 => _70.listener, 'optionalAccess', _71 => _71.onRemoteVideoPublished, 'call', _72 => _72(String(user.uid), true)]);
1364
+ }
1365
+ this.statisticsTimer = setInterval(() => {
1366
+ if (this.client !== client || !this.connection || this.tokenExpired || this.rejoinOperation) return;
1367
+ try {
1368
+ AgoraRtcStatistics.collect(client, this.listener);
1369
+ } catch (e9) {
1370
+ XmaxLogger.rtc.warning(() => "Agora \u7EDF\u8BA1\u91C7\u6837\u5931\u8D25 (Agora Statistics Failed)");
1371
+ }
1372
+ }, 2e3);
1373
+ }
1374
+ /**
1375
+ * 退房前取消排队消息、统计与音频播放;本地相机预览保持运行。
1376
+ */
1377
+ async leaveRoom() {
1378
+ this.clearRoom();
1379
+ if (this.client) await this.run(() => this.client.leave());
1380
+ }
1381
+ /**
1382
+ * 应用同一频道的新 Token;即将过期时续签,已经过期时重新进房并恢复媒体。
1383
+ */
1384
+ async updateCredentials(config, signal) {
1385
+ if (_optionalChain([signal, 'optionalAccess', _73 => _73.aborted])) throw this.cancelled();
1386
+ const current = this.connection;
1387
+ const client = this.requireClient();
1388
+ if (!current || config.provider !== "agora" || config.appID !== current.appID || config.roomID !== current.roomID || config.userID !== current.userID) {
1389
+ throw new XmaxError("SESSION_ERROR" /* sessionError */, "Agora session binding changed");
1390
+ }
1391
+ const version = this.roomVersion;
1392
+ if (!this.tokenExpired) {
1393
+ try {
1394
+ await this.run(() => client.renewToken(config.roomToken));
1395
+ } catch (error) {
1396
+ this.ensureRoomCurrent(client, version);
1397
+ if (!this.tokenExpired) throw error;
1398
+ }
1399
+ }
1400
+ this.ensureRoomCurrent(client, version);
1401
+ if (_optionalChain([signal, 'optionalAccess', _74 => _74.aborted])) throw this.cancelled();
1402
+ if (this.tokenExpired) await this.rejoinRoom(client, config, signal);
1403
+ else this.connection = config;
1404
+ }
1405
+ /**
1406
+ * 串行恢复同一 RTC 房间,不创建业务会话或重新发送生成开始指令。
1407
+ */
1408
+ async rejoinRoom(client, config, signal) {
1409
+ if (this.rejoinOperation) return this.rejoinOperation;
1410
+ const version = ++this.roomVersion;
1411
+ const task = Promise.resolve().then(() => this.performRejoin(client, config, version));
1412
+ this.rejoinTask = task;
1413
+ void task.finally(() => {
1414
+ if (this.rejoinTask === task) this.rejoinTask = void 0;
1415
+ }).catch(() => {
1416
+ });
1417
+ let cancel;
1418
+ const operation = new Promise((resolve, reject) => {
1419
+ cancel = () => {
1420
+ if (this.isRoomCurrent(client, version)) this.roomVersion++;
1421
+ reject(this.cancelled());
1422
+ };
1423
+ this.cancelRejoin = cancel;
1424
+ _optionalChain([signal, 'optionalAccess', _75 => _75.addEventListener, 'call', _76 => _76("abort", cancel, { once: true })]);
1425
+ task.then(resolve, reject);
1426
+ if (_optionalChain([signal, 'optionalAccess', _77 => _77.aborted])) cancel();
1427
+ });
1428
+ this.rejoinOperation = operation;
1429
+ try {
1430
+ await operation;
1431
+ } finally {
1432
+ _optionalChain([signal, 'optionalAccess', _78 => _78.removeEventListener, 'call', _79 => _79("abort", cancel)]);
1433
+ if (this.cancelRejoin === cancel) this.cancelRejoin = void 0;
1434
+ if (this.rejoinOperation === operation) this.rejoinOperation = void 0;
1435
+ }
1436
+ this.ensureRoomCurrent(client, version);
1437
+ for (const user of client.remoteUsers) {
1438
+ if (user.hasVideo) _optionalChain([this, 'access', _80 => _80.listener, 'optionalAccess', _81 => _81.onRemoteVideoPublished, 'call', _82 => _82(String(user.uid), true)]);
1439
+ }
1440
+ }
1441
+ /**
1442
+ * 使用新 Token 重建 RTC 连接,复用采集轨道并恢复远端音频订阅。
1443
+ */
1444
+ async performRejoin(client, config, version) {
1445
+ this.ensureRoomCurrent(client, version);
1446
+ _optionalChain([this, 'access', _83 => _83.cancelSendDelay, 'optionalCall', _84 => _84()]);
1447
+ _optionalChain([this, 'access', _85 => _85.cancelActiveSend, 'optionalCall', _86 => _86()]);
1448
+ _optionalChain([this, 'access', _87 => _87.listener, 'optionalAccess', _88 => _88.onRoomRejoining, 'optionalCall', _89 => _89()]);
1449
+ for (const user of client.remoteUsers) _optionalChain([user, 'access', _90 => _90.audioTrack, 'optionalAccess', _91 => _91.stop, 'call', _92 => _92()]);
1450
+ await this.run(() => client.leave());
1451
+ this.ensureRoomCurrent(client, version);
1452
+ await this.run(() => client.join(config.appID, config.roomID, config.roomToken, config.userID));
1453
+ if (!this.isRoomCurrent(client, version)) {
1454
+ await this.run(() => client.leave());
1455
+ throw this.cancelled();
1456
+ }
1457
+ if (this.videoPublishRequested && this.camera) {
1458
+ await this.run(() => client.publish(this.camera));
1459
+ this.ensureRoomCurrent(client, version);
1460
+ }
1461
+ if (this.audioPublishRequested && this.microphone) {
1462
+ await this.run(() => client.publish(this.microphone));
1463
+ this.ensureRoomCurrent(client, version);
1464
+ }
1465
+ for (const user of client.remoteUsers) {
1466
+ if (!user.hasAudio || !this.requestedAudio.has(String(user.uid))) continue;
1467
+ const track = await this.run(() => client.subscribe(user, "audio"));
1468
+ if (!this.isRoomCurrent(client, version)) {
1469
+ track.stop();
1470
+ throw this.cancelled();
1471
+ }
1472
+ this.applyAudioVolume(user);
1473
+ }
1474
+ this.connection = config;
1475
+ this.tokenExpired = false;
1476
+ this.sentPackets = [];
1477
+ }
1478
+ /**
1479
+ * 发布已采集的视频轨道。
1480
+ */
1481
+ async publishLocalVideo() {
1482
+ const client = this.requireClient();
1483
+ const camera = this.requireCamera();
1484
+ this.videoPublishRequested = true;
1485
+ await this.rejoinOperation;
1486
+ if (client !== this.client || !this.videoPublishRequested) throw this.cancelled();
1487
+ if (client.localTracks.includes(camera)) return;
1488
+ await this.run(() => client.publish(camera));
1489
+ }
1490
+ /**
1491
+ * 停止视频发布但保留采集与预览。
1492
+ */
1493
+ async unpublishLocalVideo() {
1494
+ this.videoPublishRequested = false;
1495
+ if (this.rejoinTask || this.tokenExpired) return;
1496
+ if (this.client && this.camera) await this.run(() => this.client.unpublish(this.camera));
1497
+ }
1498
+ /**
1499
+ * 首次发布时申请麦克风;取消后的迟到采集立即释放。
1500
+ */
1501
+ async publishLocalAudio() {
1502
+ const client = this.requireClient();
1503
+ this.audioPublishRequested = true;
1504
+ await this.rejoinOperation;
1505
+ const version = this.roomVersion;
1506
+ if (client !== this.client || !this.audioPublishRequested) throw this.cancelled();
1507
+ try {
1508
+ if (!this.microphone) {
1509
+ const track = await this.sdk.createMicrophoneAudioTrack();
1510
+ if (client !== this.client || version !== this.roomVersion) {
1511
+ track.close();
1512
+ throw this.cancelled();
1513
+ }
1514
+ this.microphone = track;
1515
+ }
1516
+ if (!client.localTracks.includes(this.microphone)) await client.publish(this.microphone);
1517
+ } catch (error) {
1518
+ throw this.mapError(error, "MICROPHONE_PERMISSION_DENIED" /* microphonePermissionDenied */);
1519
+ }
1520
+ }
1521
+ /**
1522
+ * 停止音频发布,采集交给关闭流程释放。
1523
+ */
1524
+ async unpublishLocalAudio() {
1525
+ this.audioPublishRequested = false;
1526
+ if (this.rejoinTask || this.tokenExpired) return;
1527
+ if (this.client && this.microphone) await this.run(() => this.client.unpublish(this.microphone));
1528
+ }
1529
+ /**
1530
+ * 订阅远端主视频并返回原生轨道,不创建额外播放视图。
1531
+ */
1532
+ async subscribeRemoteVideo(userID, subscribe) {
1533
+ if (!subscribe && this.rejoinTask) return;
1534
+ await this.rejoinOperation;
1535
+ const client = this.requireClient();
1536
+ const user = client.remoteUsers.find((user2) => String(user2.uid) === userID);
1537
+ if (!user && !subscribe) return;
1538
+ if (!user) throw new XmaxError("RTC_ERROR" /* rtcError */, "Agora remote video user is unavailable");
1539
+ if (!subscribe) {
1540
+ await this.run(() => client.unsubscribe(user, "video"));
1541
+ return;
1542
+ }
1543
+ const version = this.roomVersion;
1544
+ const track = await this.run(() => client.subscribe(user, "video"));
1545
+ if (client !== this.client || version !== this.roomVersion) throw this.cancelled();
1546
+ return track.getMediaStreamTrack();
1547
+ }
1548
+ /**
1549
+ * 保存音频订阅意图;音频稍后发布时自动订阅,播放前应用音量。
1550
+ */
1551
+ async subscribeRemoteAudio(userID, subscribe) {
1552
+ const client = this.requireClient();
1553
+ const user = client.remoteUsers.find((user2) => String(user2.uid) === userID);
1554
+ if (!subscribe) {
1555
+ this.requestedAudio.delete(userID);
1556
+ _optionalChain([user, 'optionalAccess', _93 => _93.audioTrack, 'optionalAccess', _94 => _94.stop, 'call', _95 => _95()]);
1557
+ if (_optionalChain([user, 'optionalAccess', _96 => _96.audioTrack]) && !this.rejoinTask) await this.run(() => client.unsubscribe(user, "audio"));
1558
+ return;
1559
+ }
1560
+ this.requestedAudio.add(userID);
1561
+ if (this.rejoinOperation) {
1562
+ const version2 = this.roomVersion;
1563
+ await this.rejoinOperation;
1564
+ this.ensureRoomCurrent(client, version2);
1565
+ if (this.requestedAudio.has(userID)) await this.subscribeRemoteAudio(userID, true);
1566
+ return;
1567
+ }
1568
+ if (!_optionalChain([user, 'optionalAccess', _97 => _97.hasAudio])) return;
1569
+ const version = this.roomVersion;
1570
+ const track = await this.run(() => client.subscribe(user, "audio"));
1571
+ if (client !== this.client || version !== this.roomVersion || !this.requestedAudio.has(userID)) {
1572
+ track.stop();
1573
+ return;
1574
+ }
1575
+ this.applyAudioVolume(user);
1576
+ }
1577
+ /**
1578
+ * 缓存音量,订阅前可调用;零音量不调用 play,防止初始短暂漏声。
1579
+ */
1580
+ setRemoteAudioVolume(volume, userID) {
1581
+ this.audioVolumes.set(userID, Math.max(0, Math.min(100, Number.isFinite(volume) ? Math.round(volume) : 0)));
1582
+ const user = _optionalChain([this, 'access', _98 => _98.client, 'optionalAccess', _99 => _99.remoteUsers, 'access', _100 => _100.find, 'call', _101 => _101((user2) => String(user2.uid) === userID)]);
1583
+ if (this.rejoinTask || this.tokenExpired) {
1584
+ _optionalChain([user, 'optionalAccess', _102 => _102.audioTrack, 'optionalAccess', _103 => _103.stop, 'call', _104 => _104()]);
1585
+ return;
1586
+ }
1587
+ if (user) this.applyAudioVolume(user);
1588
+ }
1589
+ /**
1590
+ * 应用当前用户的播放状态,保留声网对浏览器自动播放限制的处理。
1591
+ */
1592
+ applyAudioVolume(user) {
1593
+ const track = user.audioTrack;
1594
+ if (!track) return;
1595
+ const volume = _nullishCoalesce(this.audioVolumes.get(String(user.uid)), () => ( 0));
1596
+ track.setVolume(volume);
1597
+ if (volume > 0 && this.requestedAudio.has(String(user.uid))) track.play();
1598
+ else track.stop();
1599
+ }
1600
+ /**
1601
+ * 按房间顺序发送消息,限制每秒包数与总字节;失败返回给原业务调用。
1602
+ */
1603
+ sendRoomMessage(message) {
1604
+ const version = this.roomVersion;
1605
+ const client = this.client;
1606
+ const bytes = new TextEncoder().encode(message).byteLength;
1607
+ const operation = this.sendTail.then(async () => {
1608
+ await this.rejoinOperation;
1609
+ if (!client || client !== this.client || !this.connection || version !== this.roomVersion || this.tokenExpired) throw this.cancelled();
1610
+ if (bytes > 1e3) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Room message exceeds the 1000-byte limit");
1611
+ await this.waitForSendBudget(bytes, version);
1612
+ await this.rejoinOperation;
1613
+ if (client !== this.client || version !== this.roomVersion || !this.connection || this.tokenExpired) throw this.cancelled();
1614
+ this.sentPackets.push({ time: Date.now(), bytes });
1615
+ await this.sendWithTimeout(client, message);
1616
+ });
1617
+ this.sendTail = operation.catch(() => {
1618
+ });
1619
+ return operation;
1620
+ }
1621
+ /**
1622
+ * 发送超过五秒视为失败;退房立即取消等待,底层迟到结果不影响新房间。
1623
+ */
1624
+ async sendWithTimeout(client, message) {
1625
+ await new Promise((resolve, reject) => {
1626
+ const finish = (error) => {
1627
+ clearTimeout(timer);
1628
+ if (this.cancelActiveSend === cancel) this.cancelActiveSend = void 0;
1629
+ if (error) reject(this.mapError(error));
1630
+ else resolve();
1631
+ };
1632
+ const cancel = () => finish(this.cancelled());
1633
+ const timer = setTimeout(() => finish(new XmaxError("TIMEOUT" /* timeout */, "Agora room message send timed out")), 5e3);
1634
+ this.cancelActiveSend = cancel;
1635
+ void this.run(() => client.sendStreamMessage(message, false)).then(() => finish(), finish);
1636
+ });
1637
+ }
1638
+ /**
1639
+ * 按滑动时间窗限速,退房立即唤醒等待并取消,不遗留计时器。
1640
+ */
1641
+ async waitForSendBudget(bytes, version) {
1642
+ while (version === this.roomVersion) {
1643
+ const now = Date.now();
1644
+ this.sentPackets = this.sentPackets.filter((packet) => now - packet.time < 1e3);
1645
+ if (this.sentPackets.length < 30 && this.sentPackets.reduce((sum, packet) => sum + packet.bytes, bytes) <= 6e3) return;
1646
+ await new Promise((resolve) => {
1647
+ const finish = () => {
1648
+ clearTimeout(timer);
1649
+ this.cancelSendDelay = void 0;
1650
+ resolve();
1651
+ };
1652
+ const timer = setTimeout(finish, Math.max(1, this.sentPackets[0].time + 1e3 - now));
1653
+ this.cancelSendDelay = finish;
1654
+ });
1655
+ }
1656
+ throw this.cancelled();
1657
+ }
1658
+ /**
1659
+ * 设置公共 RTC 监听器。
1660
+ */
1661
+ setEventListener(listener) {
1662
+ this.listener = listener;
1663
+ }
1664
+ /**
1665
+ * 将声网发布、DataStream、网络质量与 Token 事件桥接到公共传输层。
1666
+ */
1667
+ registerEvents(client) {
1668
+ const active = () => this.client === client && this.connection !== void 0 && !this.rejoinTask;
1669
+ const mediaActive = () => active() && !this.tokenExpired;
1670
+ const handleExpired = () => {
1671
+ if (!active() || this.tokenExpired) return;
1672
+ this.tokenExpired = true;
1673
+ _optionalChain([this, 'access', _105 => _105.listener, 'optionalAccess', _106 => _106.onTokenExpired, 'optionalCall', _107 => _107()]);
1674
+ };
1675
+ client.on("user-published", (user, kind) => {
1676
+ if (!mediaActive()) return;
1677
+ if (kind === "video") _optionalChain([this, 'access', _108 => _108.listener, 'optionalAccess', _109 => _109.onRemoteVideoPublished, 'call', _110 => _110(String(user.uid), true)]);
1678
+ if (kind === "audio" && this.requestedAudio.has(String(user.uid))) {
1679
+ void this.subscribeRemoteAudio(String(user.uid), true).catch((error) => {
1680
+ if (active()) _optionalChain([this, 'access', _111 => _111.listener, 'optionalAccess', _112 => _112.onError, 'optionalCall', _113 => _113(this.mapError(error))]);
1681
+ });
1682
+ }
1683
+ });
1684
+ client.on("user-unpublished", (user, kind) => {
1685
+ if (!mediaActive()) return;
1686
+ if (kind === "video") _optionalChain([this, 'access', _114 => _114.listener, 'optionalAccess', _115 => _115.onRemoteVideoPublished, 'call', _116 => _116(String(user.uid), false)]);
1687
+ if (kind === "audio") _optionalChain([user, 'access', _117 => _117.audioTrack, 'optionalAccess', _118 => _118.stop, 'call', _119 => _119()]);
1688
+ });
1689
+ client.on("user-left", (user) => {
1690
+ if (mediaActive()) _optionalChain([this, 'access', _120 => _120.listener, 'optionalAccess', _121 => _121.onRemoteVideoPublished, 'call', _122 => _122(String(user.uid), false)]);
1691
+ });
1692
+ client.on("stream-message", (uid, data) => {
1693
+ if (mediaActive()) _optionalChain([this, 'access', _123 => _123.listener, 'optionalAccess', _124 => _124.onCustomMessageReceived, 'call', _125 => _125(String(uid), typeof data === "string" ? data : new TextDecoder().decode(data))]);
1694
+ });
1695
+ client.on("network-quality", (quality) => {
1696
+ if (mediaActive()) AgoraRtcStatistics.network(quality, this.listener);
1697
+ });
1698
+ client.on("token-privilege-will-expire", () => {
1699
+ if (active() && !this.tokenExpired) _optionalChain([this, 'access', _126 => _126.listener, 'optionalAccess', _127 => _127.onTokenWillExpire, 'optionalCall', _128 => _128()]);
1700
+ });
1701
+ client.on("token-privilege-did-expire", handleExpired);
1702
+ client.on("connection-state-change", (state2, previous, reason) => {
1703
+ if (state2 === "DISCONNECTED" && reason === "TOKEN_EXPIRE") {
1704
+ handleExpired();
1705
+ return;
1706
+ }
1707
+ if (active() && !this.tokenExpired && state2 === "DISCONNECTED" && previous !== "DISCONNECTED") {
1708
+ _optionalChain([this, 'access', _129 => _129.listener, 'optionalAccess', _130 => _130.onError, 'optionalCall', _131 => _131(new XmaxError("RTC_ERROR" /* rtcError */, `Agora connection disconnected: ${_nullishCoalesce(reason, () => ( "unknown"))}`))]);
1709
+ }
1710
+ });
1711
+ }
1712
+ /**
1713
+ * 清理房间状态、播放和发送队列,不释放本地视频采集。
1714
+ */
1715
+ clearRoom() {
1716
+ this.roomVersion++;
1717
+ _optionalChain([this, 'access', _132 => _132.cancelRejoin, 'optionalCall', _133 => _133()]);
1718
+ this.connection = void 0;
1719
+ this.tokenExpired = false;
1720
+ this.videoPublishRequested = false;
1721
+ this.audioPublishRequested = false;
1722
+ clearInterval(this.statisticsTimer);
1723
+ this.statisticsTimer = void 0;
1724
+ _optionalChain([this, 'access', _134 => _134.cancelSendDelay, 'optionalCall', _135 => _135()]);
1725
+ _optionalChain([this, 'access', _136 => _136.cancelActiveSend, 'optionalCall', _137 => _137()]);
1726
+ this.sentPackets = [];
1727
+ this.sendTail = Promise.resolve();
1728
+ this.requestedAudio.clear();
1729
+ this.audioVolumes.clear();
1730
+ for (const user of _nullishCoalesce(_optionalChain([this, 'access', _138 => _138.client, 'optionalAccess', _139 => _139.remoteUsers]), () => ( []))) _optionalChain([user, 'access', _140 => _140.audioTrack, 'optionalAccess', _141 => _141.stop, 'call', _142 => _142()]);
1731
+ }
1732
+ /**
1733
+ * 判断异步恢复工作是否仍属于当前房间生命周期。
1734
+ */
1735
+ isRoomCurrent(client, version) {
1736
+ return this.client === client && this.connection !== void 0 && this.roomVersion === version;
1737
+ }
1738
+ /**
1739
+ * 退房、销毁或换房后停止后续恢复步骤。
1740
+ */
1741
+ ensureRoomCurrent(client, version) {
1742
+ if (!this.isRoomCurrent(client, version)) throw this.cancelled();
1743
+ }
1744
+ /**
1745
+ * 获取已初始化的 client。
1746
+ */
1747
+ requireClient() {
1748
+ if (!this.client) throw new XmaxError("RTC_ERROR" /* rtcError */, "Agora is not initialized");
1749
+ return this.client;
1750
+ }
1751
+ /**
1752
+ * 获取已启动的相机采集。
1753
+ */
1754
+ requireCamera() {
1755
+ if (!this.camera) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Camera capture is not running");
1756
+ return this.camera;
1757
+ }
1758
+ /**
1759
+ * 执行声网操作并统一错误类型。
1760
+ */
1761
+ async run(operation) {
1762
+ try {
1763
+ return await operation();
1764
+ } catch (error) {
1765
+ throw this.mapError(error);
1766
+ }
1767
+ }
1768
+ /**
1769
+ * 只传递错误码,不回显可能包含 Token 的厂商原始错误消息。
1770
+ */
1771
+ mapError(error, permissionCode) {
1772
+ if (error instanceof XmaxError) return error;
1773
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "UNKNOWN";
1774
+ const safeCode = /^[A-Z_0-9-]+$/.test(code) ? code : "UNKNOWN";
1775
+ return new XmaxError(permissionCode && safeCode === "PERMISSION_DENIED" ? permissionCode : "RTC_ERROR" /* rtcError */, `Agora operation failed (${safeCode})`);
1776
+ }
1777
+ /**
1778
+ * 构造异步操作失效错误。
1779
+ */
1780
+ cancelled() {
1781
+ return new XmaxError("CANCELLED" /* cancelled */, "Agora operation was cancelled");
1782
+ }
1783
+ };
1784
+ /**
1785
+ * 模块级区域约束:setArea 是全局设置,拒绝同时运行不同区域,避免静默覆盖。
1786
+ */
1787
+ __publicField(_AgoraRtcManager, "regions", /* @__PURE__ */ new Map());
1788
+ var AgoraRtcManager = _AgoraRtcManager;
1789
+
1790
+ // src/Foundation/RTC/RtcProvider.ts
1791
+ var RtcProvider = /* @__PURE__ */ ((RtcProvider2) => {
1792
+ RtcProvider2["trtc"] = "trtc";
1793
+ RtcProvider2["agora"] = "agora";
1794
+ RtcProvider2["vertc"] = "vertc";
1795
+ return RtcProvider2;
1796
+ })(RtcProvider || {});
1797
+
1798
+ // src/Foundation/RTC/TRTC/RtcEngineManager.ts
1010
1799
  var RtcEngineLease = class {
1011
1800
  /**
1012
1801
  * 为已创建的引擎生成独占租约标识,供引擎管理器核对释放请求。
@@ -1074,13 +1863,13 @@ var _RtcEngineManager = class _RtcEngineManager {
1074
1863
  * 释放有效租约、销毁 Engine,并把独占权交给下一个等待者。
1075
1864
  */
1076
1865
  release(lease) {
1077
- if (_optionalChain([this, 'access', _37 => _37.activeLease, 'optionalAccess', _38 => _38.id]) !== lease.id) {
1866
+ if (_optionalChain([this, 'access', _143 => _143.activeLease, 'optionalAccess', _144 => _144.id]) !== lease.id) {
1078
1867
  return;
1079
1868
  }
1080
1869
  this.activeLease = void 0;
1081
1870
  try {
1082
1871
  lease.engine.destroy();
1083
- } catch (e6) {
1872
+ } catch (e10) {
1084
1873
  }
1085
1874
  void this.fulfillNextRequest();
1086
1875
  }
@@ -1148,10 +1937,10 @@ var RtcStatsLogger = class {
1148
1937
  `${XmaxLogger.localized("\u7D2F\u8BA1\u63A5\u6536\uFF1A", "Bytes Received: ")}${this.value(stats.bytesReceived, "bytes")}`
1149
1938
  ])];
1150
1939
  const local = stats.localStatistics;
1151
- if (_optionalChain([local, 'optionalAccess', _39 => _39.audio])) {
1940
+ if (_optionalChain([local, 'optionalAccess', _145 => _145.audio])) {
1152
1941
  blocks.push(this.audioBlock("\u672C\u5730\u97F3\u9891\u53D1\u9001 (Local Audio Uplink)", local.audio, false));
1153
1942
  }
1154
- for (const video of _nullishCoalesce(_optionalChain([local, 'optionalAccess', _40 => _40.video]), () => ( []))) {
1943
+ for (const video of _nullishCoalesce(_optionalChain([local, 'optionalAccess', _146 => _146.video]), () => ( []))) {
1155
1944
  blocks.push(this.videoBlock("\u672C\u5730\u89C6\u9891\u53D1\u9001 (Local Video Uplink)", video, false));
1156
1945
  }
1157
1946
  for (const remote of _nullishCoalesce(stats.remoteStatistics, () => ( []))) {
@@ -1239,7 +2028,15 @@ var RtcStatsLogger = class {
1239
2028
  }
1240
2029
  };
1241
2030
 
1242
- // src/Foundation/RTC/RtcManager.ts
2031
+ // src/Foundation/RTC/TRTC/TrtcAudioVolumeSupport.ts
2032
+ function canControlTrtcRemoteAudioVolume(environment) {
2033
+ const userAgent = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _147 => _147.userAgent]), () => ( (typeof navigator === "undefined" ? "" : navigator.userAgent)));
2034
+ const maxTouchPoints = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _148 => _148.maxTouchPoints]), () => ( (typeof navigator === "undefined" ? 0 : _nullishCoalesce(navigator.maxTouchPoints, () => ( 0)))));
2035
+ const ios = /iPhone|iPad|iPod/i.test(userAgent) || /Macintosh/i.test(userAgent) && maxTouchPoints > 1;
2036
+ return !ios;
2037
+ }
2038
+
2039
+ // src/Foundation/RTC/TRTC/TrtcRtcManager.ts
1243
2040
  var RTC_EVENT = {
1244
2041
  remoteVideoAvailable: "remote-video-available",
1245
2042
  remoteVideoUnavailable: "remote-video-unavailable",
@@ -1255,7 +2052,7 @@ var QOS_PREFERENCE_MAP = {
1255
2052
  ["maintainFramerate" /* maintainFramerate */]: "smooth",
1256
2053
  ["maintainQuality" /* maintainQuality */]: "clear"
1257
2054
  };
1258
- var RtcManager = class {
2055
+ var TrtcRtcManager = class {
1259
2056
  /**
1260
2057
  * 创建 RTC 管理器。
1261
2058
  *
@@ -1313,18 +2110,18 @@ var RtcManager = class {
1313
2110
  this.isCapturing = false;
1314
2111
  this.isAudioCapturing = false;
1315
2112
  this.isInRoom = false;
1316
- _optionalChain([this, 'access', _41 => _41.removeStatsListeners, 'optionalCall', _42 => _42()]);
2113
+ _optionalChain([this, 'access', _149 => _149.removeStatsListeners, 'optionalCall', _150 => _150()]);
1317
2114
  this.removeStatsListeners = void 0;
1318
2115
  if (!lease) {
1319
2116
  return;
1320
2117
  }
1321
2118
  try {
1322
2119
  await lease.engine.stopLocalAudio();
1323
- } catch (e7) {
2120
+ } catch (e11) {
1324
2121
  }
1325
2122
  try {
1326
2123
  await lease.engine.stopLocalVideo();
1327
- } catch (e8) {
2124
+ } catch (e12) {
1328
2125
  }
1329
2126
  this.engineManager.release(lease);
1330
2127
  }
@@ -1343,13 +2140,17 @@ var RtcManager = class {
1343
2140
  );
1344
2141
  }
1345
2142
  try {
2143
+ const captureSize = rtcOrientedVideoSize({
2144
+ width: options.width,
2145
+ height: options.height
2146
+ });
1346
2147
  await engine.startLocalVideo({
1347
2148
  publish: false,
1348
2149
  option: {
1349
2150
  useFrontCamera: options.position === "front" /* front */,
1350
2151
  profile: {
1351
- width: options.width,
1352
- height: options.height,
2152
+ width: captureSize.width,
2153
+ height: captureSize.height,
1353
2154
  frameRate: options.frameRate,
1354
2155
  bitrate: CAPTURE_PLACEHOLDER_BITRATE
1355
2156
  }
@@ -1402,7 +2203,7 @@ var RtcManager = class {
1402
2203
  * 停止摄像头采集;引擎未初始化或采集未启动时不产生效果。
1403
2204
  */
1404
2205
  async stopCameraCapture() {
1405
- const engine = _optionalChain([this, 'access', _43 => _43.lease, 'optionalAccess', _44 => _44.engine]);
2206
+ const engine = _optionalChain([this, 'access', _151 => _151.lease, 'optionalAccess', _152 => _152.engine]);
1406
2207
  this.isCapturing = false;
1407
2208
  if (!engine) {
1408
2209
  return;
@@ -1419,6 +2220,9 @@ var RtcManager = class {
1419
2220
  * @throws 引擎未初始化、进房参数无效或进房失败时抛出错误。
1420
2221
  */
1421
2222
  async joinRoom(configuration) {
2223
+ if (configuration.provider !== "trtc") {
2224
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "TRTC credentials required");
2225
+ }
1422
2226
  const engine = this.requireEngine();
1423
2227
  const sdkAppId = Number(configuration.sdkAppID);
1424
2228
  if (!Number.isInteger(sdkAppId) || sdkAppId <= 0) {
@@ -1452,7 +2256,7 @@ var RtcManager = class {
1452
2256
  * 离开当前 RTC 房间;未在房间中或引擎未初始化时不产生效果,退房失败仅记录日志。
1453
2257
  */
1454
2258
  async leaveRoom() {
1455
- const engine = _optionalChain([this, 'access', _45 => _45.lease, 'optionalAccess', _46 => _46.engine]);
2259
+ const engine = _optionalChain([this, 'access', _153 => _153.lease, 'optionalAccess', _154 => _154.engine]);
1456
2260
  if (!engine || !this.isInRoom) {
1457
2261
  return;
1458
2262
  }
@@ -1469,7 +2273,7 @@ var RtcManager = class {
1469
2273
  /**
1470
2274
  * 配置本地视频编码参数。
1471
2275
  *
1472
- * TRTC 只接受单一目标码率,以 `maximumBitrate` 作为目标码率;
2276
+ * TRTC 只接受单一目标码率,以 `minimumBitrate`(流畅优先档)作为目标码率;
1473
2277
  * 编码策略偏好映射为 TRTC 弱网偏好。
1474
2278
  *
1475
2279
  * @throws 摄像头采集未启动或编码参数配置失败时抛出错误。
@@ -1483,13 +2287,17 @@ var RtcManager = class {
1483
2287
  );
1484
2288
  }
1485
2289
  try {
2290
+ const encodingSize = rtcOrientedVideoSize({
2291
+ width: configuration.width,
2292
+ height: configuration.height
2293
+ });
1486
2294
  await engine.updateLocalVideo({
1487
2295
  option: {
1488
2296
  profile: {
1489
- width: configuration.width,
1490
- height: configuration.height,
2297
+ width: encodingSize.width,
2298
+ height: encodingSize.height,
1491
2299
  frameRate: configuration.frameRate,
1492
- bitrate: configuration.maximumBitrate
2300
+ bitrate: configuration.minimumBitrate
1493
2301
  },
1494
2302
  qosPreference: QOS_PREFERENCE_MAP[configuration.encoderPreference]
1495
2303
  }
@@ -1521,7 +2329,7 @@ var RtcManager = class {
1521
2329
  * 停止发布本地视频流;采集保持运行,本地预览不受影响。
1522
2330
  */
1523
2331
  async unpublishLocalVideo() {
1524
- const engine = _optionalChain([this, 'access', _47 => _47.lease, 'optionalAccess', _48 => _48.engine]);
2332
+ const engine = _optionalChain([this, 'access', _155 => _155.lease, 'optionalAccess', _156 => _156.engine]);
1525
2333
  if (!engine || !this.isCapturing) {
1526
2334
  return;
1527
2335
  }
@@ -1553,7 +2361,7 @@ var RtcManager = class {
1553
2361
  * 停止发布本地音频流;麦克风采集保持运行。
1554
2362
  */
1555
2363
  async unpublishLocalAudio() {
1556
- const engine = _optionalChain([this, 'access', _49 => _49.lease, 'optionalAccess', _50 => _50.engine]);
2364
+ const engine = _optionalChain([this, 'access', _157 => _157.lease, 'optionalAccess', _158 => _158.engine]);
1557
2365
  if (!engine || !this.isAudioCapturing) {
1558
2366
  return;
1559
2367
  }
@@ -1607,14 +2415,34 @@ var RtcManager = class {
1607
2415
  throw this.mapError(error);
1608
2416
  }
1609
2417
  }
2418
+ /**
2419
+ * 当前平台是否支持远端音量控制(iOS Safari/WKWebView 不支持)。
2420
+ */
2421
+ get supportsRemoteAudioVolumeControl() {
2422
+ return canControlTrtcRemoteAudioVolume();
2423
+ }
1610
2424
  /**
1611
2425
  * 设置指定远端用户的音频播放音量。
1612
2426
  *
2427
+ * iOS Safari/WKWebView 不允许 JS 修改媒体元素音量(TRTC 官方标注
2428
+ * `setRemoteAudioVolume` 不支持 iOS Safari),该平台上降级为
2429
+ * 播放/静音二值控制:音量为 0 时停止播放该路音频,非 0 时恢复播放;
2430
+ * 恢复播放需要重新拉流,会有短暂缓冲。
2431
+ *
1613
2432
  * @param volume 音量,取值范围为 `0...100`,越界时收敛到边界。
1614
2433
  */
1615
2434
  setRemoteAudioVolume(volume, userID) {
1616
2435
  const engine = this.requireEngine();
1617
2436
  const normalized = Math.min(Math.max(Math.round(volume), 0), 100);
2437
+ if (!canControlTrtcRemoteAudioVolume()) {
2438
+ void engine.muteRemoteAudio(userID, normalized === 0).catch((error) => {
2439
+ XmaxLogger.rtc.warning(
2440
+ () => `\u8FDC\u7AEF\u97F3\u9891\u64AD\u653E\u72B6\u6001\u5207\u6362\u5931\u8D25 (Failed to Toggle Remote Audio Playback)
2441
+ \u2514\u2500 ${XmaxLogger.localized("\u539F\u56E0\uFF1A", "Reason: ")}${XmaxError.from(error).message}`
2442
+ );
2443
+ });
2444
+ return;
2445
+ }
1618
2446
  engine.setRemoteAudioVolume(userID, normalized);
1619
2447
  }
1620
2448
  /**
@@ -1622,7 +2450,7 @@ var RtcManager = class {
1622
2450
  *
1623
2451
  * @throws 未在房间中或消息编码后超过 1000 字节时抛出错误。
1624
2452
  */
1625
- sendRoomMessage(message) {
2453
+ async sendRoomMessage(message) {
1626
2454
  const engine = this.requireEngine();
1627
2455
  if (!this.isInRoom) {
1628
2456
  throw new XmaxError(
@@ -1642,6 +2470,11 @@ var RtcManager = class {
1642
2470
  data: encoded.buffer
1643
2471
  });
1644
2472
  }
2473
+ /**
2474
+ * TRTC 凭据由会话层缓存,保持现有进房鉴权行为。
2475
+ */
2476
+ async updateCredentials(_configuration) {
2477
+ }
1645
2478
  /**
1646
2479
  * 设置 RTC 事件监听器,传入空值时清除监听器。
1647
2480
  */
@@ -1654,19 +2487,19 @@ var RtcManager = class {
1654
2487
  registerEventBridge(engine) {
1655
2488
  const source = engine;
1656
2489
  const onStatistics = (stats) => {
1657
- if (this.isInRoom && _optionalChain([this, 'access', _51 => _51.lease, 'optionalAccess', _52 => _52.engine]) === engine) {
2490
+ if (this.isInRoom && _optionalChain([this, 'access', _159 => _159.lease, 'optionalAccess', _160 => _160.engine]) === engine) {
1658
2491
  RtcStatsLogger.logStatistics(stats);
1659
- const video = _optionalChain([stats, 'access', _53 => _53.localStatistics, 'optionalAccess', _54 => _54.video, 'optionalAccess', _55 => _55.find, 'call', _56 => _56((item) => item.videoType === "big")]);
2492
+ const video = _optionalChain([stats, 'access', _161 => _161.localStatistics, 'optionalAccess', _162 => _162.video, 'optionalAccess', _163 => _163.find, 'call', _164 => _164((item) => item.videoType === "big")]);
1660
2493
  const valid = (value, minimum) => typeof value === "number" && Number.isFinite(value) && value >= minimum ? value : void 0;
1661
- _optionalChain([this, 'access', _57 => _57.eventListener, 'optionalAccess', _58 => _58.onLocalVideoStatistics, 'optionalCall', _59 => _59(video ? Object.freeze({
2494
+ _optionalChain([this, 'access', _165 => _165.eventListener, 'optionalAccess', _166 => _166.onLocalVideoStatistics, 'optionalCall', _167 => _167(video ? Object.freeze({
1662
2495
  width: valid(video.width, 1),
1663
2496
  height: valid(video.height, 1),
1664
2497
  frameRate: valid(video.frameRate, 0),
1665
2498
  bitrateKbps: valid(video.bitrate, 0)
1666
2499
  }) : void 0)]);
1667
- _optionalChain([this, 'access', _60 => _60.eventListener, 'optionalAccess', _61 => _61.onRemoteVideoStatistics, 'optionalCall', _62 => _62(Object.freeze(
2500
+ _optionalChain([this, 'access', _168 => _168.eventListener, 'optionalAccess', _169 => _169.onRemoteVideoStatistics, 'optionalCall', _170 => _170(Object.freeze(
1668
2501
  (_nullishCoalesce(stats.remoteStatistics, () => ( []))).flatMap((remote) => {
1669
- const video2 = _optionalChain([remote, 'access', _63 => _63.video, 'optionalAccess', _64 => _64.find, 'call', _65 => _65((item) => item.videoType === "big")]);
2502
+ const video2 = _optionalChain([remote, 'access', _171 => _171.video, 'optionalAccess', _172 => _172.find, 'call', _173 => _173((item) => item.videoType === "big")]);
1670
2503
  if (!video2) {
1671
2504
  return [];
1672
2505
  }
@@ -1688,11 +2521,11 @@ var RtcManager = class {
1688
2521
  }
1689
2522
  };
1690
2523
  const onNetworkQuality = (stats) => {
1691
- if (this.isInRoom && _optionalChain([this, 'access', _66 => _66.lease, 'optionalAccess', _67 => _67.engine]) === engine) {
2524
+ if (this.isInRoom && _optionalChain([this, 'access', _174 => _174.lease, 'optionalAccess', _175 => _175.engine]) === engine) {
1692
2525
  RtcStatsLogger.logNetworkQuality(stats);
1693
2526
  const quality = (value) => Number.isInteger(value) && value >= 0 && value <= 6 ? value : void 0;
1694
2527
  const rtt = (value) => Number.isFinite(value) && value >= 0 ? value : void 0;
1695
- _optionalChain([this, 'access', _68 => _68.eventListener, 'optionalAccess', _69 => _69.onNetworkStatistics, 'optionalCall', _70 => _70(Object.freeze({
2528
+ _optionalChain([this, 'access', _176 => _176.eventListener, 'optionalAccess', _177 => _177.onNetworkStatistics, 'optionalCall', _178 => _178(Object.freeze({
1696
2529
  uplinkQuality: quality(stats.uplinkNetworkQuality),
1697
2530
  downlinkQuality: quality(stats.downlinkNetworkQuality),
1698
2531
  uplinkRttMs: rtt(stats.uplinkRTT),
@@ -1710,13 +2543,13 @@ var RtcManager = class {
1710
2543
  if (event.streamType !== STREAM_TYPE_MAIN) {
1711
2544
  return;
1712
2545
  }
1713
- _optionalChain([this, 'access', _71 => _71.eventListener, 'optionalAccess', _72 => _72.onRemoteVideoPublished, 'call', _73 => _73(event.userId, true)]);
2546
+ _optionalChain([this, 'access', _179 => _179.eventListener, 'optionalAccess', _180 => _180.onRemoteVideoPublished, 'call', _181 => _181(event.userId, true)]);
1714
2547
  });
1715
2548
  source.on(RTC_EVENT.remoteVideoUnavailable, (event) => {
1716
2549
  if (event.streamType !== STREAM_TYPE_MAIN) {
1717
2550
  return;
1718
2551
  }
1719
- _optionalChain([this, 'access', _74 => _74.eventListener, 'optionalAccess', _75 => _75.onRemoteVideoPublished, 'call', _76 => _76(event.userId, false)]);
2552
+ _optionalChain([this, 'access', _182 => _182.eventListener, 'optionalAccess', _183 => _183.onRemoteVideoPublished, 'call', _184 => _184(event.userId, false)]);
1720
2553
  });
1721
2554
  source.on(RTC_EVENT.customMessage, (event) => {
1722
2555
  if (event.cmdId !== CUSTOM_MESSAGE_CMD_ID) {
@@ -1725,18 +2558,18 @@ var RtcManager = class {
1725
2558
  let message;
1726
2559
  try {
1727
2560
  message = new TextDecoder().decode(event.data);
1728
- } catch (e9) {
2561
+ } catch (e13) {
1729
2562
  XmaxLogger.rtc.warning(() => "\u81EA\u5B9A\u4E49\u6D88\u606F\u89E3\u7801\u5931\u8D25 (Failed to Decode Custom Message)");
1730
2563
  return;
1731
2564
  }
1732
- _optionalChain([this, 'access', _77 => _77.eventListener, 'optionalAccess', _78 => _78.onCustomMessageReceived, 'call', _79 => _79(event.userId, message)]);
2565
+ _optionalChain([this, 'access', _185 => _185.eventListener, 'optionalAccess', _186 => _186.onCustomMessageReceived, 'call', _187 => _187(event.userId, message)]);
1733
2566
  });
1734
2567
  }
1735
2568
  /**
1736
2569
  * 返回当前引擎实例;未初始化时抛出错误。
1737
2570
  */
1738
2571
  requireEngine() {
1739
- const engine = _optionalChain([this, 'access', _80 => _80.lease, 'optionalAccess', _81 => _81.engine]);
2572
+ const engine = _optionalChain([this, 'access', _188 => _188.lease, 'optionalAccess', _189 => _189.engine]);
1740
2573
  if (!engine) {
1741
2574
  throw new XmaxError(
1742
2575
  "RTC_ERROR" /* rtcError */,
@@ -1745,95 +2578,672 @@ var RtcManager = class {
1745
2578
  }
1746
2579
  return engine;
1747
2580
  }
1748
- /**
1749
- * 将 TRTC 错误映射为 SDK 统一错误。
1750
- */
1751
- mapError(error) {
1752
- if (error instanceof XmaxError) {
1753
- return error;
1754
- }
1755
- const rtcError = error;
1756
- const originName = _nullishCoalesce(_optionalChain([rtcError, 'optionalAccess', _82 => _82.originError, 'optionalAccess', _83 => _83.name]), () => ( ""));
1757
- const message = typeof _optionalChain([rtcError, 'optionalAccess', _84 => _84.message]) === "string" && rtcError.message.trim().length > 0 ? rtcError.message.trim() : String(error);
1758
- if (originName === "NotAllowedError" || originName === "SecurityError" || /NotAllowed/i.test(message)) {
1759
- return new XmaxError("CAMERA_PERMISSION_DENIED" /* cameraPermissionDenied */, message);
1760
- }
1761
- if (originName) {
1762
- return new XmaxError("MEDIA_ERROR" /* mediaError */, message);
2581
+ /**
2582
+ * 将 TRTC 错误映射为 SDK 统一错误。
2583
+ */
2584
+ mapError(error) {
2585
+ if (error instanceof XmaxError) {
2586
+ return error;
2587
+ }
2588
+ const rtcError = error;
2589
+ const originName = _nullishCoalesce(_optionalChain([rtcError, 'optionalAccess', _190 => _190.originError, 'optionalAccess', _191 => _191.name]), () => ( ""));
2590
+ const message = typeof _optionalChain([rtcError, 'optionalAccess', _192 => _192.message]) === "string" && rtcError.message.trim().length > 0 ? rtcError.message.trim() : String(error);
2591
+ if (originName === "NotAllowedError" || originName === "SecurityError" || /NotAllowed/i.test(message)) {
2592
+ return new XmaxError("CAMERA_PERMISSION_DENIED" /* cameraPermissionDenied */, message);
2593
+ }
2594
+ if (originName) {
2595
+ return new XmaxError("MEDIA_ERROR" /* mediaError */, message);
2596
+ }
2597
+ return new XmaxError("RTC_ERROR" /* rtcError */, message);
2598
+ }
2599
+ };
2600
+
2601
+ // src/Foundation/RTC/VeRTC/VeRtcStatistics.ts
2602
+ var VeRtcStatistics = class {
2603
+ static local(stats) {
2604
+ return Object.freeze({
2605
+ width: this.valid(stats.encodedFrameWidth, 1),
2606
+ height: this.valid(stats.encodedFrameHeight, 1),
2607
+ frameRate: this.valid(stats.sentFrameRate),
2608
+ bitrateKbps: this.valid(stats.sentKBitrate),
2609
+ uplinkLossPercent: this.loss(stats.videoLossRate)
2610
+ });
2611
+ }
2612
+ static remote(userID, stats, uplinkLossPercent) {
2613
+ return Object.freeze({
2614
+ userID,
2615
+ width: this.valid(stats.width, 1),
2616
+ height: this.valid(stats.height, 1),
2617
+ frameRate: this.valid(stats.decoderOutputFrameRate),
2618
+ bitrateKbps: this.valid(stats.receivedKBitrate),
2619
+ rttMs: this.valid(stats.rtt),
2620
+ downlinkLossPercent: this.loss(stats.videoLossRate),
2621
+ endToEndDelayMs: this.valid(stats.e2eDelay),
2622
+ uplinkLossPercent
2623
+ });
2624
+ }
2625
+ static quality(value) {
2626
+ return Number.isInteger(value) && value >= 0 && value <= 6 ? value : void 0;
2627
+ }
2628
+ static valid(value, minimum = 0) {
2629
+ return typeof value === "number" && Number.isFinite(value) && value >= minimum ? value : void 0;
2630
+ }
2631
+ static loss(value) {
2632
+ return Number.isFinite(value) && value >= 0 && value <= 1 ? value * 100 : void 0;
2633
+ }
2634
+ };
2635
+
2636
+ // src/Foundation/RTC/VeRTC/VeRtcManager.ts
2637
+ var VERTC_APP_ID = "69a177e226e9b90176a86b96";
2638
+ var VeRtcManager = class {
2639
+ constructor(options = {}) {
2640
+ __publicField(this, "loadSDK");
2641
+ __publicField(this, "sdk");
2642
+ __publicField(this, "engine");
2643
+ __publicField(this, "initialization");
2644
+ __publicField(this, "lifecycle", 0);
2645
+ __publicField(this, "captureVersion", 0);
2646
+ __publicField(this, "camera");
2647
+ __publicField(this, "microphone", false);
2648
+ __publicField(this, "connection");
2649
+ __publicField(this, "joined", false);
2650
+ __publicField(this, "roomVersion", 0);
2651
+ __publicField(this, "roomAbort", new AbortController());
2652
+ __publicField(this, "recovery");
2653
+ __publicField(this, "joining");
2654
+ __publicField(this, "tokenExpired", false);
2655
+ __publicField(this, "publishPrivilegeExpired", false);
2656
+ __publicField(this, "subscribePrivilegeExpired", false);
2657
+ __publicField(this, "videoRequested", false);
2658
+ __publicField(this, "audioRequested", false);
2659
+ __publicField(this, "listener");
2660
+ __publicField(this, "publishedVideo", /* @__PURE__ */ new Set());
2661
+ __publicField(this, "publishedAudio", /* @__PURE__ */ new Set());
2662
+ __publicField(this, "requestedVideo", /* @__PURE__ */ new Set());
2663
+ __publicField(this, "requestedAudio", /* @__PURE__ */ new Set());
2664
+ __publicField(this, "audioVolumes", /* @__PURE__ */ new Map());
2665
+ __publicField(this, "audioPlayers", /* @__PURE__ */ new Map());
2666
+ __publicField(this, "audioSubscriptions", /* @__PURE__ */ new Map());
2667
+ __publicField(this, "remoteStatistics", /* @__PURE__ */ new Map());
2668
+ __publicField(this, "uplinkLossPercent");
2669
+ this.loadSDK = _nullishCoalesce(options.loadSDK, () => ( (() => Promise.resolve().then(() => _interopRequireWildcard(require("@volcengine/rtc"))))));
2670
+ }
2671
+ get isInitialized() {
2672
+ return this.engine !== void 0;
2673
+ }
2674
+ get supportsRemoteAudioVolumeControl() {
2675
+ if (typeof navigator === "undefined") return true;
2676
+ return !(/iPad|iPhone|iPod/.test(navigator.userAgent) || /Macintosh/.test(navigator.userAgent) && navigator.maxTouchPoints > 1);
2677
+ }
2678
+ async initialize() {
2679
+ if (this.engine) return;
2680
+ if (this.initialization) return this.initialization;
2681
+ const version = this.lifecycle;
2682
+ const operation = (async () => {
2683
+ const sdk = await this.loadSDK();
2684
+ if (version !== this.lifecycle) throw this.cancelled();
2685
+ sdk.default.setLogConfig({ logLevel: "none" });
2686
+ const engine = sdk.default.createEngine(VERTC_APP_ID, { autoPlayPolicy: sdk.RTCAutoPlayPolicy.PLAY_MANUALLY });
2687
+ this.sdk = sdk;
2688
+ this.engine = engine;
2689
+ this.registerEvents(engine);
2690
+ })();
2691
+ this.initialization = operation;
2692
+ try {
2693
+ await operation;
2694
+ } finally {
2695
+ if (this.initialization === operation) this.initialization = void 0;
2696
+ }
2697
+ }
2698
+ async destroy() {
2699
+ this.lifecycle++;
2700
+ this.captureVersion++;
2701
+ const engine = this.engine;
2702
+ const sdk = this.sdk;
2703
+ this.listener = void 0;
2704
+ this.clearRoom();
2705
+ this.engine = void 0;
2706
+ this.sdk = void 0;
2707
+ this.camera = void 0;
2708
+ this.microphone = false;
2709
+ _optionalChain([engine, 'optionalAccess', _193 => _193.removeAllListeners, 'call', _194 => _194()]);
2710
+ if (engine && sdk) sdk.default.destroyEngine(engine);
2711
+ }
2712
+ async startCameraCapture(options) {
2713
+ const engine = this.requireEngine();
2714
+ if (this.camera) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Camera capture is already running");
2715
+ const version = ++this.captureVersion;
2716
+ await this.run(() => engine.setVideoEncoderConfig({
2717
+ ...rtcOrientedVideoSize({ width: options.width, height: options.height }),
2718
+ frameRate: options.frameRate,
2719
+ maxKbps: 1e3,
2720
+ preferCodecName: this.sdk.VideoCodecType.H264,
2721
+ contentHint: "motion"
2722
+ }));
2723
+ if (engine !== this.engine || version !== this.captureVersion) throw this.cancelled();
2724
+ try {
2725
+ await engine.startVideoCapture(options.position === "front" /* front */ ? "user" : "environment");
2726
+ if (engine !== this.engine || version !== this.captureVersion) {
2727
+ await engine.stopVideoCapture().catch(() => {
2728
+ });
2729
+ throw this.cancelled();
2730
+ }
2731
+ const track = engine.getLocalStreamTrack(this.sdk.StreamIndex.STREAM_INDEX_MAIN, "video");
2732
+ if (!track) {
2733
+ await engine.stopVideoCapture();
2734
+ throw new XmaxError("RTC_ERROR" /* rtcError */, "VeRTC camera track is unavailable");
2735
+ }
2736
+ this.camera = track;
2737
+ return track;
2738
+ } catch (error) {
2739
+ throw this.mapError(error, "CAMERA_PERMISSION_DENIED" /* cameraPermissionDenied */);
2740
+ }
2741
+ }
2742
+ async switchCameraCapture(to) {
2743
+ const engine = this.requireEngine();
2744
+ this.requireCamera();
2745
+ const version = this.captureVersion;
2746
+ await this.run(() => engine.setVideoCaptureDevice(to === "front" /* front */ ? "user" : "environment"));
2747
+ if (engine !== this.engine || version !== this.captureVersion) throw this.cancelled();
2748
+ const track = engine.getLocalStreamTrack(this.sdk.StreamIndex.STREAM_INDEX_MAIN, "video");
2749
+ if (!track) throw new XmaxError("RTC_ERROR" /* rtcError */, "VeRTC camera track is unavailable");
2750
+ this.camera = track;
2751
+ return track;
2752
+ }
2753
+ async stopCameraCapture() {
2754
+ this.captureVersion++;
2755
+ this.camera = void 0;
2756
+ if (this.engine) await this.run(() => this.engine.stopVideoCapture());
2757
+ }
2758
+ async configureVideoEncoding(config) {
2759
+ const engine = this.requireEngine();
2760
+ this.requireCamera();
2761
+ await this.run(() => engine.setVideoEncoderConfig({
2762
+ ...rtcOrientedVideoSize({ width: config.width, height: config.height }),
2763
+ frameRate: config.frameRate,
2764
+ maxKbps: config.maximumBitrate,
2765
+ contentHint: config.encoderPreference === "maintainFramerate" /* maintainFramerate */ ? "motion" : "detail",
2766
+ preferCodecName: this.sdk.VideoCodecType.H264
2767
+ }));
2768
+ }
2769
+ async joinRoom(config) {
2770
+ await _optionalChain([this, 'access', _195 => _195.joining, 'optionalAccess', _196 => _196.catch, 'call', _197 => _197(() => {
2771
+ })]);
2772
+ await _optionalChain([this, 'access', _198 => _198.recovery, 'optionalAccess', _199 => _199.catch, 'call', _200 => _200(() => {
2773
+ })]);
2774
+ const engine = this.requireEngine();
2775
+ if (config.provider !== "vertc" || config.appID !== VERTC_APP_ID || this.connection) {
2776
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "VeRTC credentials for the configured AppID and an idle room are required");
2777
+ }
2778
+ this.clearRoom();
2779
+ this.connection = config;
2780
+ const version = this.roomVersion;
2781
+ const task = this.performJoin(engine, config, version);
2782
+ this.joining = task;
2783
+ try {
2784
+ await task;
2785
+ } finally {
2786
+ if (this.joining === task) this.joining = void 0;
2787
+ }
2788
+ }
2789
+ async performJoin(engine, config, version) {
2790
+ try {
2791
+ await this.enter(engine, config);
2792
+ if (!this.isCurrent(engine, version)) {
2793
+ await engine.leaveRoom().catch(() => {
2794
+ });
2795
+ throw this.cancelled();
2796
+ }
2797
+ this.joined = true;
2798
+ this.replayVideo();
2799
+ } catch (error) {
2800
+ if (this.isCurrent(engine, version)) this.clearRoom();
2801
+ throw this.mapError(error);
2802
+ }
2803
+ }
2804
+ enter(engine, config) {
2805
+ return this.run(() => engine.joinRoom(config.roomToken, config.roomID, { userId: config.userID }, {
2806
+ isAutoPublish: false,
2807
+ isAutoSubscribeAudio: false,
2808
+ isAutoSubscribeVideo: false,
2809
+ roomProfileType: this.sdk.RoomProfileType.communication
2810
+ }));
2811
+ }
2812
+ async leaveRoom() {
2813
+ const engine = this.engine;
2814
+ this.clearRoom();
2815
+ if (engine) await this.run(() => engine.leaveRoom());
2816
+ }
2817
+ /** 正常续签仅 updateToken;被移出房间后重进,恢复发布与订阅意图。 */
2818
+ async updateCredentials(config, signal) {
2819
+ const engine = this.requireEngine();
2820
+ const current = this.connection;
2821
+ if (_optionalChain([signal, 'optionalAccess', _201 => _201.aborted])) throw this.cancelled();
2822
+ if (!current || config.provider !== "vertc" || config.appID !== current.appID || config.roomID !== current.roomID || config.userID !== current.userID) {
2823
+ throw new XmaxError("SESSION_ERROR" /* sessionError */, "VeRTC session binding changed");
2824
+ }
2825
+ const version = this.roomVersion;
2826
+ if (this.recovery) await this.cancellable(this.recovery, signal);
2827
+ if (!this.isCurrent(engine, version)) throw this.cancelled();
2828
+ const task = this.renew(engine, config, version);
2829
+ this.recovery = task;
2830
+ void task.finally(() => {
2831
+ if (this.recovery === task) this.recovery = void 0;
2832
+ }).catch(() => {
2833
+ });
2834
+ await this.cancellable(task, signal);
2835
+ }
2836
+ async renew(engine, config, version) {
2837
+ if (!this.tokenExpired) {
2838
+ try {
2839
+ await this.run(() => engine.updateToken(config.roomToken));
2840
+ } catch (error) {
2841
+ this.ensureCurrent(engine, version);
2842
+ if (!this.tokenExpired) throw error;
2843
+ }
2844
+ }
2845
+ this.ensureCurrent(engine, version);
2846
+ const restore = this.tokenExpired || this.publishPrivilegeExpired || this.subscribePrivilegeExpired;
2847
+ if (restore) {
2848
+ this.joined = false;
2849
+ _optionalChain([this, 'access', _202 => _202.listener, 'optionalAccess', _203 => _203.onRoomRejoining, 'optionalCall', _204 => _204()]);
2850
+ this.clearPlayers();
2851
+ this.clearStatistics();
2852
+ if (this.tokenExpired) {
2853
+ this.publishedVideo.clear();
2854
+ this.publishedAudio.clear();
2855
+ await this.run(() => engine.leaveRoom());
2856
+ this.ensureCurrent(engine, version);
2857
+ await this.enter(engine, config);
2858
+ if (!this.isCurrent(engine, version)) {
2859
+ await engine.leaveRoom().catch(() => {
2860
+ });
2861
+ throw this.cancelled();
2862
+ }
2863
+ }
2864
+ if ((this.tokenExpired || this.publishPrivilegeExpired) && this.videoRequested && this.camera) {
2865
+ await this.run(() => engine.publishStream(this.sdk.MediaType.VIDEO));
2866
+ this.ensureCurrent(engine, version);
2867
+ if (!this.videoRequested) {
2868
+ await this.run(() => engine.unpublishStream(this.sdk.MediaType.VIDEO));
2869
+ this.ensureCurrent(engine, version);
2870
+ }
2871
+ }
2872
+ if ((this.tokenExpired || this.publishPrivilegeExpired) && this.audioRequested && this.microphone) {
2873
+ await this.run(() => engine.publishStream(this.sdk.MediaType.AUDIO));
2874
+ this.ensureCurrent(engine, version);
2875
+ if (!this.audioRequested) {
2876
+ await this.run(() => engine.unpublishStream(this.sdk.MediaType.AUDIO));
2877
+ this.ensureCurrent(engine, version);
2878
+ }
2879
+ }
2880
+ }
2881
+ this.connection = config;
2882
+ this.tokenExpired = false;
2883
+ this.publishPrivilegeExpired = false;
2884
+ this.subscribePrivilegeExpired = false;
2885
+ this.joined = true;
2886
+ if (restore) {
2887
+ this.replayVideo();
2888
+ for (const userID of this.requestedAudio) {
2889
+ if (this.publishedAudio.has(userID)) await this.subscribeRemoteAudio(userID, true);
2890
+ this.ensureCurrent(engine, version);
2891
+ }
2892
+ }
2893
+ }
2894
+ async publishLocalVideo() {
2895
+ const engine = this.requireEngine();
2896
+ this.requireCamera();
2897
+ this.videoRequested = true;
2898
+ const version = this.roomVersion;
2899
+ await this.recovery;
2900
+ this.ensureCurrent(engine, version);
2901
+ if (!this.videoRequested) throw this.cancelled();
2902
+ await this.run(() => engine.publishStream(this.sdk.MediaType.VIDEO));
2903
+ }
2904
+ async unpublishLocalVideo() {
2905
+ this.videoRequested = false;
2906
+ if (this.joined && !this.tokenExpired) await this.run(() => this.requireEngine().unpublishStream(this.sdk.MediaType.VIDEO));
2907
+ }
2908
+ async publishLocalAudio() {
2909
+ const engine = this.requireEngine();
2910
+ this.audioRequested = true;
2911
+ const version = this.roomVersion;
2912
+ await this.recovery;
2913
+ this.ensureCurrent(engine, version);
2914
+ try {
2915
+ if (!this.microphone) {
2916
+ await engine.startAudioCapture();
2917
+ if (!this.isCurrent(engine, version)) {
2918
+ await engine.stopAudioCapture().catch(() => {
2919
+ });
2920
+ throw this.cancelled();
2921
+ }
2922
+ this.microphone = true;
2923
+ }
2924
+ if (!this.audioRequested) throw this.cancelled();
2925
+ await engine.publishStream(this.sdk.MediaType.AUDIO);
2926
+ } catch (error) {
2927
+ throw this.mapError(error, "MICROPHONE_PERMISSION_DENIED" /* microphonePermissionDenied */);
2928
+ }
2929
+ }
2930
+ async unpublishLocalAudio() {
2931
+ this.audioRequested = false;
2932
+ if (this.joined && !this.tokenExpired) await this.run(() => this.requireEngine().unpublishStream(this.sdk.MediaType.AUDIO));
2933
+ }
2934
+ async subscribeRemoteVideo(userID, subscribe) {
2935
+ const engine = this.requireEngine();
2936
+ const version = this.roomVersion;
2937
+ if (!subscribe) {
2938
+ this.requestedVideo.delete(userID);
2939
+ this.remoteStatistics.delete(userID);
2940
+ this.emitRemoteStatistics();
2941
+ if (this.joined && !this.tokenExpired) await this.run(() => engine.unsubscribeStream(userID, this.sdk.MediaType.VIDEO));
2942
+ return;
2943
+ }
2944
+ this.requestedVideo.add(userID);
2945
+ await this.run(() => engine.subscribeStream(userID, this.sdk.MediaType.VIDEO));
2946
+ this.ensureCurrent(engine, version);
2947
+ if (!this.requestedVideo.has(userID)) return;
2948
+ const track = engine.getRemoteStreamTrack(userID, this.sdk.StreamIndex.STREAM_INDEX_MAIN, "video");
2949
+ if (!track) throw new XmaxError("RTC_ERROR" /* rtcError */, "VeRTC remote video track is unavailable");
2950
+ return track;
2951
+ }
2952
+ async subscribeRemoteAudio(userID, subscribe) {
2953
+ const engine = this.requireEngine();
2954
+ const version = this.roomVersion;
2955
+ if (!subscribe) {
2956
+ this.requestedAudio.delete(userID);
2957
+ this.clearPlayer(userID);
2958
+ if (this.joined && !this.tokenExpired && this.publishedAudio.has(userID)) await this.run(() => engine.unsubscribeStream(userID, this.sdk.MediaType.AUDIO));
2959
+ return;
2960
+ }
2961
+ this.requestedAudio.add(userID);
2962
+ if (!this.joined || this.tokenExpired || !this.publishedAudio.has(userID)) return;
2963
+ const subscription = {};
2964
+ this.audioSubscriptions.set(userID, subscription);
2965
+ await this.run(() => engine.subscribeStream(userID, this.sdk.MediaType.AUDIO));
2966
+ if (!this.isCurrent(engine, version) || !this.joined || this.tokenExpired || this.audioSubscriptions.get(userID) !== subscription || !this.requestedAudio.has(userID) || !this.publishedAudio.has(userID)) return;
2967
+ const track = engine.getRemoteStreamTrack(userID, this.sdk.StreamIndex.STREAM_INDEX_MAIN, "audio");
2968
+ if (!track) return;
2969
+ this.clearPlayer(userID);
2970
+ const player = document.createElement("audio");
2971
+ player.srcObject = new MediaStream([track]);
2972
+ this.audioPlayers.set(userID, player);
2973
+ this.applyAudioVolume(userID);
2974
+ }
2975
+ setRemoteAudioVolume(volume, userID) {
2976
+ this.audioVolumes.set(userID, Number.isFinite(volume) ? Math.max(0, Math.min(100, Math.round(volume))) : 0);
2977
+ this.applyAudioVolume(userID);
2978
+ }
2979
+ applyAudioVolume(userID) {
2980
+ const player = this.audioPlayers.get(userID);
2981
+ if (!player) return;
2982
+ const volume = _nullishCoalesce(this.audioVolumes.get(userID), () => ( 0));
2983
+ player.muted = volume === 0 || !this.requestedAudio.has(userID);
2984
+ if (this.supportsRemoteAudioVolumeControl) player.volume = volume / 100;
2985
+ if (player.muted) player.pause();
2986
+ else void player.play().catch(() => {
2987
+ XmaxLogger.rtc.warning(() => "VeRTC audio playback requires a user interaction");
2988
+ });
2989
+ }
2990
+ async sendRoomMessage(message) {
2991
+ const engine = this.requireEngine();
2992
+ const version = this.roomVersion;
2993
+ if (!this.joined || this.tokenExpired) throw this.cancelled();
2994
+ if (new TextEncoder().encode(message).byteLength > 64 * 1024) {
2995
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "VeRTC room message exceeds the 64-KiB limit");
2996
+ }
2997
+ await this.cancellable(this.run(async () => {
2998
+ const sent = engine.sendRoomMessage(message);
2999
+ if (!sent) throw new XmaxError("RTC_ERROR" /* rtcError */, "VeRTC room message was not sent");
3000
+ await sent;
3001
+ }), void 0, 5e3);
3002
+ this.ensureCurrent(engine, version);
3003
+ }
3004
+ setEventListener(listener) {
3005
+ this.listener = listener;
3006
+ }
3007
+ registerEvents(engine) {
3008
+ const events = this.sdk.default.events;
3009
+ const active = () => engine === this.engine && this.connection !== void 0;
3010
+ const mediaActive = () => active() && this.joined && !this.tokenExpired;
3011
+ const video = (mediaType) => (mediaType & this.sdk.MediaType.VIDEO) !== 0;
3012
+ const audio = (mediaType) => (mediaType & this.sdk.MediaType.AUDIO) !== 0;
3013
+ engine.on(events.onUserPublishStream, ({ userId, mediaType }) => {
3014
+ if (!active()) return;
3015
+ if (video(mediaType)) {
3016
+ this.publishedVideo.add(userId);
3017
+ if (mediaActive()) _optionalChain([this, 'access', _205 => _205.listener, 'optionalAccess', _206 => _206.onRemoteVideoPublished, 'call', _207 => _207(userId, true)]);
3018
+ }
3019
+ if (audio(mediaType)) {
3020
+ this.publishedAudio.add(userId);
3021
+ if (mediaActive() && this.requestedAudio.has(userId)) {
3022
+ void this.subscribeRemoteAudio(userId, true).catch((error) => {
3023
+ if (mediaActive()) _optionalChain([this, 'access', _208 => _208.listener, 'optionalAccess', _209 => _209.onError, 'optionalCall', _210 => _210(this.mapError(error))]);
3024
+ });
3025
+ }
3026
+ }
3027
+ });
3028
+ engine.on(events.onUserUnpublishStream, ({ userId, mediaType }) => {
3029
+ if (!active()) return;
3030
+ if (video(mediaType)) this.removeVideo(userId);
3031
+ if (audio(mediaType)) {
3032
+ this.publishedAudio.delete(userId);
3033
+ this.clearPlayer(userId);
3034
+ }
3035
+ });
3036
+ engine.on(events.onUserLeave, ({ userInfo }) => {
3037
+ if (!active()) return;
3038
+ this.removeVideo(userInfo.userId);
3039
+ this.publishedAudio.delete(userInfo.userId);
3040
+ this.clearPlayer(userInfo.userId);
3041
+ });
3042
+ engine.on(events.onRoomMessageReceived, ({ userId, message }) => {
3043
+ if (mediaActive() && typeof message === "string") _optionalChain([this, 'access', _211 => _211.listener, 'optionalAccess', _212 => _212.onCustomMessageReceived, 'call', _213 => _213(userId, message)]);
3044
+ });
3045
+ const willExpire = () => {
3046
+ if (active()) _optionalChain([this, 'access', _214 => _214.listener, 'optionalAccess', _215 => _215.onTokenWillExpire, 'optionalCall', _216 => _216()]);
3047
+ };
3048
+ engine.on(events.onTokenWillExpire, willExpire);
3049
+ engine.on(events.onTokenPublishPrivilegeWillExpire, willExpire);
3050
+ engine.on(events.onTokenSubscribePrivilegeWillExpire, willExpire);
3051
+ engine.on(events.onTokenPublishPrivilegeDidExpired, () => {
3052
+ if (!active() || this.publishPrivilegeExpired) return;
3053
+ this.publishPrivilegeExpired = true;
3054
+ _optionalChain([this, 'access', _217 => _217.listener, 'optionalAccess', _218 => _218.onTokenExpired, 'optionalCall', _219 => _219()]);
3055
+ });
3056
+ engine.on(events.onTokenSubscribePrivilegeDidExpired, () => {
3057
+ if (!active() || this.subscribePrivilegeExpired) return;
3058
+ this.subscribePrivilegeExpired = true;
3059
+ this.clearPlayers();
3060
+ _optionalChain([this, 'access', _220 => _220.listener, 'optionalAccess', _221 => _221.onTokenExpired, 'optionalCall', _222 => _222()]);
3061
+ });
3062
+ engine.on(events.onError, ({ errorCode }) => {
3063
+ if (!active()) return;
3064
+ if (errorCode === "TOKEN_EXPIRED") {
3065
+ if (this.tokenExpired) return;
3066
+ this.tokenExpired = true;
3067
+ this.clearPlayers();
3068
+ _optionalChain([this, 'access', _223 => _223.listener, 'optionalAccess', _224 => _224.onTokenExpired, 'optionalCall', _225 => _225()]);
3069
+ } else _optionalChain([this, 'access', _226 => _226.listener, 'optionalAccess', _227 => _227.onError, 'optionalCall', _228 => _228(this.mapError({ code: errorCode }))]);
3070
+ });
3071
+ engine.on(events.onConnectionStateChanged, ({ state: state2 }) => {
3072
+ if (!mediaActive() || state2 !== this.sdk.ConnectionState.CONNECTION_STATE_RECONNECTED) return;
3073
+ _optionalChain([this, 'access', _229 => _229.listener, 'optionalAccess', _230 => _230.onRoomRejoining, 'optionalCall', _231 => _231()]);
3074
+ this.clearPlayers();
3075
+ this.clearStatistics();
3076
+ this.replayVideo();
3077
+ for (const userID of this.requestedAudio) {
3078
+ void this.subscribeRemoteAudio(userID, true).catch((error) => {
3079
+ if (mediaActive()) _optionalChain([this, 'access', _232 => _232.listener, 'optionalAccess', _233 => _233.onError, 'optionalCall', _234 => _234(this.mapError(error))]);
3080
+ });
3081
+ }
3082
+ });
3083
+ engine.on(events.onNetworkQuality, (up, down) => {
3084
+ if (mediaActive()) _optionalChain([this, 'access', _235 => _235.listener, 'optionalAccess', _236 => _236.onNetworkStatistics, 'optionalCall', _237 => _237(Object.freeze({
3085
+ uplinkQuality: VeRtcStatistics.quality(up),
3086
+ downlinkQuality: VeRtcStatistics.quality(down)
3087
+ }))]);
3088
+ });
3089
+ engine.on(events.onLocalStreamStats, (stats) => {
3090
+ if (!mediaActive() || stats.isScreen || !this.videoRequested) return;
3091
+ const result = VeRtcStatistics.local(stats.videoStats);
3092
+ this.uplinkLossPercent = result.uplinkLossPercent;
3093
+ _optionalChain([this, 'access', _238 => _238.listener, 'optionalAccess', _239 => _239.onLocalVideoStatistics, 'optionalCall', _240 => _240(result)]);
3094
+ XmaxLogger.rtc.info(() => `VeRTC local video statistics: ${JSON.stringify(result)}`, 2 /* performance */);
3095
+ });
3096
+ engine.on(events.onRemoteStreamStats, (stats) => {
3097
+ if (!mediaActive() || stats.isScreen || !this.requestedVideo.has(stats.userId)) return;
3098
+ this.remoteStatistics.set(stats.userId, VeRtcStatistics.remote(stats.userId, stats.videoStats, this.uplinkLossPercent));
3099
+ this.emitRemoteStatistics();
3100
+ });
3101
+ }
3102
+ replayVideo() {
3103
+ for (const userID of this.publishedVideo) _optionalChain([this, 'access', _241 => _241.listener, 'optionalAccess', _242 => _242.onRemoteVideoPublished, 'call', _243 => _243(userID, true)]);
3104
+ }
3105
+ removeVideo(userID) {
3106
+ this.publishedVideo.delete(userID);
3107
+ this.remoteStatistics.delete(userID);
3108
+ this.emitRemoteStatistics();
3109
+ if (this.joined && !this.tokenExpired) _optionalChain([this, 'access', _244 => _244.listener, 'optionalAccess', _245 => _245.onRemoteVideoPublished, 'call', _246 => _246(userID, false)]);
3110
+ }
3111
+ emitRemoteStatistics() {
3112
+ _optionalChain([this, 'access', _247 => _247.listener, 'optionalAccess', _248 => _248.onRemoteVideoStatistics, 'optionalCall', _249 => _249(Object.freeze([...this.remoteStatistics.values()]))]);
3113
+ }
3114
+ clearStatistics() {
3115
+ this.remoteStatistics.clear();
3116
+ this.uplinkLossPercent = void 0;
3117
+ _optionalChain([this, 'access', _250 => _250.listener, 'optionalAccess', _251 => _251.onLocalVideoStatistics, 'optionalCall', _252 => _252(void 0)]);
3118
+ this.emitRemoteStatistics();
3119
+ }
3120
+ clearPlayer(userID) {
3121
+ this.audioSubscriptions.delete(userID);
3122
+ const player = this.audioPlayers.get(userID);
3123
+ if (player) {
3124
+ player.pause();
3125
+ player.srcObject = null;
3126
+ }
3127
+ this.audioPlayers.delete(userID);
3128
+ }
3129
+ clearPlayers() {
3130
+ this.audioSubscriptions.clear();
3131
+ for (const userID of this.audioPlayers.keys()) this.clearPlayer(userID);
3132
+ }
3133
+ clearRoom() {
3134
+ this.roomVersion++;
3135
+ this.roomAbort.abort();
3136
+ this.roomAbort = new AbortController();
3137
+ this.connection = void 0;
3138
+ this.joined = false;
3139
+ this.tokenExpired = false;
3140
+ this.publishPrivilegeExpired = false;
3141
+ this.subscribePrivilegeExpired = false;
3142
+ this.videoRequested = false;
3143
+ this.audioRequested = false;
3144
+ this.publishedVideo.clear();
3145
+ this.publishedAudio.clear();
3146
+ this.requestedVideo.clear();
3147
+ this.requestedAudio.clear();
3148
+ this.audioVolumes.clear();
3149
+ this.clearPlayers();
3150
+ this.clearStatistics();
3151
+ }
3152
+ /** 业务取消立即结束等待;底层迟到结果由版本检查清理。 */
3153
+ async cancellable(task, signal, timeoutMs) {
3154
+ const roomSignal = this.roomAbort.signal;
3155
+ return new Promise((resolve, reject) => {
3156
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
3157
+ cleanup();
3158
+ reject(new XmaxError("TIMEOUT" /* timeout */, "VeRTC room message send timed out"));
3159
+ }, timeoutMs);
3160
+ const cleanup = () => {
3161
+ clearTimeout(timer);
3162
+ roomSignal.removeEventListener("abort", cancel);
3163
+ _optionalChain([signal, 'optionalAccess', _253 => _253.removeEventListener, 'call', _254 => _254("abort", cancel)]);
3164
+ };
3165
+ const cancel = () => {
3166
+ if (_optionalChain([signal, 'optionalAccess', _255 => _255.aborted]) && !roomSignal.aborted) this.roomVersion++;
3167
+ cleanup();
3168
+ reject(this.cancelled());
3169
+ };
3170
+ roomSignal.addEventListener("abort", cancel, { once: true });
3171
+ _optionalChain([signal, 'optionalAccess', _256 => _256.addEventListener, 'call', _257 => _257("abort", cancel, { once: true })]);
3172
+ task.then((value) => {
3173
+ cleanup();
3174
+ resolve(value);
3175
+ }, (error) => {
3176
+ cleanup();
3177
+ reject(error);
3178
+ });
3179
+ if (roomSignal.aborted || _optionalChain([signal, 'optionalAccess', _258 => _258.aborted])) cancel();
3180
+ });
3181
+ }
3182
+ isCurrent(engine, version) {
3183
+ return this.engine === engine && this.connection !== void 0 && this.roomVersion === version;
3184
+ }
3185
+ ensureCurrent(engine, version) {
3186
+ if (!this.isCurrent(engine, version)) throw this.cancelled();
3187
+ }
3188
+ requireEngine() {
3189
+ if (!this.engine) throw new XmaxError("RTC_ERROR" /* rtcError */, "VeRTC is not initialized");
3190
+ return this.engine;
3191
+ }
3192
+ requireCamera() {
3193
+ if (!this.camera) throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Camera capture is not running");
3194
+ return this.camera;
3195
+ }
3196
+ async run(operation) {
3197
+ try {
3198
+ return await operation();
3199
+ } catch (error) {
3200
+ throw this.mapError(error);
1763
3201
  }
1764
- return new XmaxError("RTC_ERROR" /* rtcError */, message);
3202
+ }
3203
+ mapError(error, permissionCode) {
3204
+ if (error instanceof XmaxError) return error;
3205
+ const value = error;
3206
+ const code = typeof _optionalChain([value, 'optionalAccess', _259 => _259.code]) === "string" && /^[A-Z_0-9-]{1,64}$/.test(value.code) ? value.code : "UNKNOWN";
3207
+ const denied = _optionalChain([value, 'optionalAccess', _260 => _260.name]) === "NotAllowedError" || code === "PERMISSION_DENIED" || code === "DEVICE_PERMISSION_DENIED";
3208
+ return new XmaxError(permissionCode && denied ? permissionCode : "RTC_ERROR" /* rtcError */, `VeRTC operation failed (${code})`);
3209
+ }
3210
+ cancelled() {
3211
+ return new XmaxError("CANCELLED" /* cancelled */, "VeRTC operation was cancelled");
1765
3212
  }
1766
3213
  };
1767
3214
 
1768
- // src/Foundation/Media/Camera/CameraFrameValidator.ts
1769
- var FALLBACK_POLL_INTERVAL_MS = 16;
1770
- var TIMEOUT_MS = 2e3;
1771
- var CameraFrameValidator = class {
1772
- /**
1773
- * 单帧中央区域亮度达标即放行,不等待连续帧或曝光稳定。
1774
- */
1775
- accept(pixels) {
1776
- const size = pixels.length / 4;
1777
- if (!size || !Number.isInteger(size)) return false;
1778
- const histogram = new Uint32Array(256);
1779
- let total = 0;
1780
- let dark = 0;
1781
- for (let i = 0; i < pixels.length; i += 4) {
1782
- const luminance = Math.round(0.2126 * pixels[i] + 0.7152 * pixels[i + 1] + 0.0722 * pixels[i + 2]);
1783
- histogram[luminance] = histogram[luminance] + 1;
1784
- total += luminance;
1785
- if (luminance < 12) dark++;
1786
- }
1787
- let median = 0;
1788
- let accumulated = 0;
1789
- for (; median < 255; median++) {
1790
- accumulated += histogram[median];
1791
- if (accumulated >= size / 2) break;
1792
- }
1793
- const mean = total / size;
1794
- return mean >= 24 && median >= 18 && dark / size <= 0.75;
3215
+ // src/Foundation/RTC/RtcFactory.ts
3216
+ function createRtcManager(provider, environment) {
3217
+ switch (provider) {
3218
+ case "agora" /* agora */:
3219
+ return new AgoraRtcManager({ environment });
3220
+ case "vertc" /* vertc */:
3221
+ return new VeRtcManager();
3222
+ default:
3223
+ return new TrtcRtcManager();
1795
3224
  }
1796
- };
3225
+ }
3226
+
3227
+ // src/Foundation/Media/Camera/CameraFrameValidator.ts
3228
+ var CAMERA_WARMUP_MS = 200;
1797
3229
  function waitForValidCameraFrame(track, signal) {
1798
3230
  return new Promise((resolve, reject) => {
1799
- let video;
1800
- let timeout;
1801
- let poll;
1802
- let frameCallback;
1803
- let finished = false;
1804
- const started = performance.now();
1805
- const gate = new CameraFrameValidator();
1806
- const finish = (result = "valid") => {
1807
- if (finished) return;
1808
- finished = true;
1809
- clearTimeout(timeout);
1810
- clearTimeout(poll);
1811
- if (video) {
1812
- if (frameCallback !== void 0) video.cancelVideoFrameCallback(frameCallback);
1813
- video.pause();
1814
- video.srcObject = null;
1815
- video.remove();
1816
- }
3231
+ let timer;
3232
+ let settled = false;
3233
+ const finish = (error) => {
3234
+ if (settled) return;
3235
+ settled = true;
3236
+ clearTimeout(timer);
1817
3237
  signal.removeEventListener("abort", abort);
1818
3238
  track.removeEventListener("ended", ended);
1819
- if (result instanceof XmaxError) {
1820
- if (result.code !== "CANCELLED" /* cancelled */) XmaxLogger.media.warning(() => result.message);
1821
- reject(result);
3239
+ if (error) {
3240
+ reject(error);
1822
3241
  } else {
1823
- if (result === "timeout") {
1824
- XmaxLogger.media.warning(() => XmaxLogger.localized(
1825
- "\u76F8\u673A\u4EAE\u5EA6\u68C0\u67E5\u8D85\u65F6\uFF0C\u6309\u5F53\u524D\u753B\u9762\u7EE7\u7EED\u751F\u6210 (Camera Frame Validation Timed Out; Continuing)\n\u2514\u2500 \u672A\u83B7\u5F97\u4EAE\u5EA6\u5408\u683C\u5E27\uFF0C\u4E0D\u518D\u7B49\u5F85\u3002",
1826
- "Camera Frame Validation Timed Out; Continuing\n\u2514\u2500 No sufficiently bright frame received; proceeding without further waiting."
1827
- ));
1828
- } else {
1829
- XmaxLogger.media.info(() => `\u76F8\u673A\u66DD\u5149\u68C0\u67E5\u901A\u8FC7 (Camera Exposure Ready)
1830
- \u2514\u2500 ${Math.round(performance.now() - started)} ms`);
1831
- }
1832
3242
  resolve();
1833
3243
  }
1834
3244
  };
1835
- const abort = () => finish(new XmaxError("CANCELLED" /* cancelled */, "Camera exposure check cancelled"));
1836
- const ended = () => finish(new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera track ended before exposure was ready"));
3245
+ const abort = () => finish(new XmaxError("CANCELLED" /* cancelled */, "Camera warmup cancelled"));
3246
+ const ended = () => finish(new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera track ended before warmup completed"));
1837
3247
  signal.addEventListener("abort", abort, { once: true });
1838
3248
  track.addEventListener("ended", ended, { once: true });
1839
3249
  if (signal.aborted) {
@@ -1844,70 +3254,7 @@ function waitForValidCameraFrame(track, signal) {
1844
3254
  ended();
1845
3255
  return;
1846
3256
  }
1847
- try {
1848
- video = document.createElement("video");
1849
- video.muted = true;
1850
- video.autoplay = true;
1851
- video.playsInline = true;
1852
- video.setAttribute("aria-hidden", "true");
1853
- video.style.cssText = "position:fixed;left:0;top:0;width:1px;height:1px;opacity:0;pointer-events:none;";
1854
- const canvas = document.createElement("canvas");
1855
- canvas.width = 64;
1856
- canvas.height = 48;
1857
- const context = canvas.getContext("2d", { willReadFrequently: true });
1858
- if (!context) throw new Error("Camera exposure sampling requires a 2D canvas");
1859
- video.srcObject = new MediaStream([track]);
1860
- document.body.appendChild(video);
1861
- const source = video;
1862
- let lastFrameTime = -Infinity;
1863
- const sample = (mediaTime) => {
1864
- if (source.readyState < 2 || !source.videoWidth || !source.videoHeight || track.muted || !track.enabled) {
1865
- return;
1866
- }
1867
- if (mediaTime === lastFrameTime) return;
1868
- lastFrameTime = mediaTime;
1869
- const width = source.videoWidth;
1870
- const height = source.videoHeight;
1871
- context.drawImage(source, width * 0.2, height * 0.2, width * 0.6, height * 0.6, 0, 0, 64, 48);
1872
- if (gate.accept(context.getImageData(0, 0, 64, 48).data)) finish();
1873
- };
1874
- const fail = (error) => finish(new XmaxError(
1875
- "MEDIA_ERROR" /* mediaError */,
1876
- `Camera exposure check failed: ${error instanceof Error ? error.message : String(error)}`
1877
- ));
1878
- const schedule = () => {
1879
- if (finished) return;
1880
- if (typeof source.requestVideoFrameCallback === "function") {
1881
- frameCallback = source.requestVideoFrameCallback((_now, metadata) => {
1882
- frameCallback = void 0;
1883
- try {
1884
- sample(metadata.mediaTime);
1885
- schedule();
1886
- } catch (error) {
1887
- fail(error);
1888
- }
1889
- });
1890
- } else {
1891
- poll = setTimeout(() => {
1892
- try {
1893
- const frameTime = typeof source.getVideoPlaybackQuality === "function" ? source.getVideoPlaybackQuality().totalVideoFrames : source.currentTime;
1894
- sample(frameTime);
1895
- schedule();
1896
- } catch (error) {
1897
- fail(error);
1898
- }
1899
- }, FALLBACK_POLL_INTERVAL_MS);
1900
- }
1901
- };
1902
- timeout = setTimeout(() => finish("timeout"), TIMEOUT_MS);
1903
- XmaxLogger.media.info(() => "\u7B49\u5F85\u9996\u5F20\u4EAE\u5EA6\u5408\u683C\u5E27 (Waiting for the First Sufficiently Bright Frame)");
1904
- void source.play().then(schedule, fail);
1905
- } catch (error) {
1906
- finish(new XmaxError(
1907
- "MEDIA_ERROR" /* mediaError */,
1908
- `Camera exposure check failed: ${error instanceof Error ? error.message : String(error)}`
1909
- ));
1910
- }
3257
+ timer = setTimeout(() => finish(), CAMERA_WARMUP_MS);
1911
3258
  });
1912
3259
  }
1913
3260
 
@@ -1937,7 +3284,7 @@ var PermissionManager = class {
1937
3284
  * @param deniedCode 权限被拒绝时抛出的错误码。
1938
3285
  */
1939
3286
  async ensurePermission(name, deniedCode) {
1940
- if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _85 => _85.mediaDevices, 'optionalAccess', _86 => _86.getUserMedia])) {
3287
+ if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _261 => _261.mediaDevices, 'optionalAccess', _262 => _262.getUserMedia])) {
1941
3288
  throw new XmaxError(
1942
3289
  deniedCode,
1943
3290
  "This browser does not support media capture (getUserMedia)"
@@ -1945,7 +3292,7 @@ var PermissionManager = class {
1945
3292
  }
1946
3293
  try {
1947
3294
  const permissions = navigator.permissions;
1948
- if (!_optionalChain([permissions, 'optionalAccess', _87 => _87.query])) {
3295
+ if (!_optionalChain([permissions, 'optionalAccess', _263 => _263.query])) {
1949
3296
  return;
1950
3297
  }
1951
3298
  const status = await permissions.query({
@@ -2125,10 +3472,10 @@ var _CameraController = class _CameraController {
2125
3472
  */
2126
3473
  __publicField(this, "storedUseMicrophone", false);
2127
3474
  __publicField(this, "storedFrameValidationEnabled", true);
2128
- this.permissionManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _88 => _88.permissionManager]), () => ( new PermissionManager()));
2129
- this.rtcManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _89 => _89.rtcManager]), () => ( new RtcManager()));
2130
- this.mediaService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _90 => _90.mediaService]), () => ( new MediaService()));
2131
- this.errorListener = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _91 => _91.errorListener]), () => ( (() => {
3475
+ this.permissionManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _264 => _264.permissionManager]), () => ( new PermissionManager()));
3476
+ this.rtcManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _265 => _265.rtcManager]), () => ( new TrtcRtcManager()));
3477
+ this.mediaService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _266 => _266.mediaService]), () => ( new MediaService()));
3478
+ this.errorListener = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _267 => _267.errorListener]), () => ( (() => {
2132
3479
  })));
2133
3480
  }
2134
3481
  /**
@@ -2144,23 +3491,23 @@ var _CameraController = class _CameraController {
2144
3491
  return this.activeTrack !== void 0 && this.storedUseMicrophone;
2145
3492
  }
2146
3493
  /**
2147
- * 当前活动相机流是否启用发布前亮度检测;没有活动流时为 false。
3494
+ * 当前活动相机流是否启用发布前预热等待;没有活动流时为 false。
2148
3495
  */
2149
3496
  get isFrameValidationEnabled() {
2150
3497
  return this.activeTrack !== void 0 && this.storedFrameValidationEnabled;
2151
3498
  }
2152
3499
  /**
2153
- * 等待当前轨道首张合格帧或超时放行;禁用时跳过,取消或轨道被替换时拒绝。
3500
+ * 等待当前轨道预热完成(固定 200ms);禁用时跳过,取消或轨道被替换时拒绝。
2154
3501
  */
2155
3502
  async waitForValidCameraFrame(signal) {
2156
3503
  const track = this.activeTrack;
2157
- const mediaTrack = _optionalChain([track, 'optionalAccess', _92 => _92.mediaStreamTrack]);
3504
+ const mediaTrack = _optionalChain([track, 'optionalAccess', _268 => _268.mediaStreamTrack]);
2158
3505
  if (!mediaTrack) throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera capture is not running");
2159
3506
  if (signal.aborted) throw new XmaxError("CANCELLED" /* cancelled */, "Camera frame validation cancelled");
2160
3507
  if (!this.isFrameValidationEnabled) return;
2161
3508
  await waitForValidCameraFrame(mediaTrack, signal);
2162
3509
  if (this.activeTrack !== track || track.mediaStreamTrack !== mediaTrack) {
2163
- throw new XmaxError("CANCELLED" /* cancelled */, "Camera track changed during exposure check");
3510
+ throw new XmaxError("CANCELLED" /* cancelled */, "Camera track changed during warmup");
2164
3511
  }
2165
3512
  }
2166
3513
  /**
@@ -2179,7 +3526,7 @@ var _CameraController = class _CameraController {
2179
3526
  * @param options.videoFormat 期望的输出尺寸、帧率和编码配置;尺寸按模型规则调整。
2180
3527
  * @param options.position 首次启动时使用的摄像头位置。
2181
3528
  * @param options.useMicrophone 是否申请麦克风权限并允许实时连接时启动音频采集。
2182
- * @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
3529
+ * @param options.enableFrameValidation 是否在发布前等待相机预热(固定 200ms),默认 true;false 跳过等待。
2183
3530
  * @returns 包含本地相机视频轨道的媒体流。
2184
3531
  * @throws 已有活动相机流、格式无效、权限不足或采集启动失败时抛出错误。
2185
3532
  */
@@ -2254,7 +3601,7 @@ var _CameraController = class _CameraController {
2254
3601
  */
2255
3602
  async switchCamera() {
2256
3603
  const track = this.activeTrack;
2257
- const position = _optionalChain([track, 'optionalAccess', _93 => _93.position]);
3604
+ const position = _optionalChain([track, 'optionalAccess', _269 => _269.position]);
2258
3605
  if (!track || !position) {
2259
3606
  throw new XmaxError(
2260
3607
  "RTC_ERROR" /* rtcError */,
@@ -2272,7 +3619,7 @@ var _CameraController = class _CameraController {
2272
3619
  }
2273
3620
  this.previewStream.addTrack(mediaTrack);
2274
3621
  }
2275
- _optionalChain([this, 'access', _94 => _94.previewMirrorApplier, 'optionalCall', _95 => _95(nextPosition === "front" /* front */)]);
3622
+ _optionalChain([this, 'access', _270 => _270.previewMirrorApplier, 'optionalCall', _271 => _271(nextPosition === "front" /* front */)]);
2276
3623
  this.observeMediaTrack(track, mediaTrack);
2277
3624
  return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
2278
3625
  }
@@ -2417,6 +3764,53 @@ var RealtimeSession = class {
2417
3764
  }
2418
3765
  };
2419
3766
 
3767
+ // src/Foundation/RTC/RoomJoinConfiguration.ts
3768
+ var RoomJoinConfiguration = class {
3769
+ /**
3770
+ * 创建房间加入参数。
3771
+ *
3772
+ * @param init.roomID TRTC 房间号。
3773
+ * @param init.userID TRTC 登录身份。
3774
+ * @param init.sdkAppID TRTC SDKAppID(字符串形式)。
3775
+ * @param init.userSig TRTC 登录签名。
3776
+ * @param init.privateMapKey TRTC 进房鉴权字段。
3777
+ */
3778
+ constructor(init) {
3779
+ /**
3780
+ * RTC 提供方。
3781
+ */
3782
+ __publicField(this, "provider", "trtc");
3783
+ /**
3784
+ * 进房参数
3785
+ */
3786
+ /**
3787
+ * TRTC 房间号(字符串房间号)。
3788
+ */
3789
+ __publicField(this, "roomID");
3790
+ /**
3791
+ * TRTC 登录身份(`rtc_user_id`)。
3792
+ */
3793
+ __publicField(this, "userID");
3794
+ /**
3795
+ * TRTC SDKAppID(字符串形式,进房时转换为数值)。
3796
+ */
3797
+ __publicField(this, "sdkAppID");
3798
+ /**
3799
+ * TRTC 登录签名。
3800
+ */
3801
+ __publicField(this, "userSig");
3802
+ /**
3803
+ * TRTC 进房鉴权字段(字符串房间号对应的 privateMapKey)。
3804
+ */
3805
+ __publicField(this, "privateMapKey");
3806
+ this.roomID = init.roomID;
3807
+ this.userID = init.userID;
3808
+ this.sdkAppID = init.sdkAppID;
3809
+ this.userSig = init.userSig;
3810
+ this.privateMapKey = init.privateMapKey;
3811
+ }
3812
+ };
3813
+
2420
3814
  // src/Service/Realtime/RealtimeSessionConnection.ts
2421
3815
  var RealtimeSessionConnection = class {
2422
3816
  /**
@@ -2435,7 +3829,7 @@ var RealtimeSessionConnection = class {
2435
3829
  * RTC 提供方
2436
3830
  */
2437
3831
  /**
2438
- * RTC 提供方标识(当前仅支持 `trtc`)。
3832
+ * TRTC 凭据的提供方判别字段。
2439
3833
  */
2440
3834
  __publicField(this, "provider");
2441
3835
  /**
@@ -2477,6 +3871,12 @@ var RealtimeSessionConnection = class {
2477
3871
  this.botID = init.botID;
2478
3872
  }
2479
3873
  };
3874
+ function toRoomJoinConfiguration(connection) {
3875
+ return connection.provider === "trtc" ? new RoomJoinConfiguration(connection) : connection;
3876
+ }
3877
+ function connectionAppID(connection) {
3878
+ return connection.provider === "trtc" ? connection.sdkAppID : connection.appID;
3879
+ }
2480
3880
 
2481
3881
  // src/Service/Realtime/RealtimeSessionService.ts
2482
3882
  var RealtimeSessionService = class _RealtimeSessionService {
@@ -2492,6 +3892,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2492
3892
  * 服务层组件
2493
3893
  */
2494
3894
  __publicField(this, "apiService");
3895
+ __publicField(this, "provider");
2495
3896
  /**
2496
3897
  * 心跳配置
2497
3898
  */
@@ -2501,7 +3902,16 @@ var RealtimeSessionService = class _RealtimeSessionService {
2501
3902
  * 运行状态
2502
3903
  */
2503
3904
  __publicField(this, "heartbeatVersion", 0);
3905
+ /**
3906
+ * 会话凭证缓存,支持心跳仅返回变更字段;关闭后清除。
3907
+ */
3908
+ __publicField(this, "credentials", /* @__PURE__ */ new Map());
3909
+ __publicField(this, "refreshes", /* @__PURE__ */ new Map());
2504
3910
  this.apiService = options.apiService;
3911
+ this.provider = _nullishCoalesce(options.provider, () => ( "trtc")) /* trtc */;
3912
+ if (!Object.values(RtcProvider).includes(this.provider)) {
3913
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Unsupported RTC provider");
3914
+ }
2505
3915
  this.heartbeatIntervalMs = _nullishCoalesce(options.heartbeatIntervalMs, () => ( 1e4));
2506
3916
  this.sleep = _nullishCoalesce(options.sleep, () => ( ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))));
2507
3917
  }
@@ -2512,7 +3922,18 @@ var RealtimeSessionService = class _RealtimeSessionService {
2512
3922
  const payload = await this.apiService.post("/session", {
2513
3923
  model
2514
3924
  });
2515
- return _RealtimeSessionService.makeSession(payload, true);
3925
+ try {
3926
+ const session = this.makeSession(payload, true);
3927
+ _RealtimeSessionService.ensureSessionActive(session);
3928
+ const credentials = _RealtimeSessionService.decodeConnectionPayload(payload.modelExtra);
3929
+ this.credentials.set(session.id, this.provider === "vertc" /* vertc */ ? { ...credentials, user_id: session.connection.userID } : credentials);
3930
+ return session;
3931
+ } catch (error) {
3932
+ const sessionID = _RealtimeSessionService.nonEmpty(payload.sessionUid);
3933
+ if (sessionID) await this.closeSession(sessionID).catch(() => {
3934
+ });
3935
+ throw error;
3936
+ }
2516
3937
  }
2517
3938
  /**
2518
3939
  * 启动指定会话的周期心跳;重复启动会替换当前心跳。
@@ -2528,12 +3949,15 @@ var RealtimeSessionService = class _RealtimeSessionService {
2528
3949
  this.heartbeatVersion += 1;
2529
3950
  }
2530
3951
  /**
2531
- * 关闭指定实时会话。
3952
+ * 关闭指定实时会话;请求带 keepalive,页面卸载后仍能发出。
2532
3953
  */
2533
3954
  async closeSession(sessionID) {
3955
+ this.credentials.delete(sessionID);
3956
+ this.refreshes.delete(sessionID);
2534
3957
  try {
2535
3958
  await this.apiService.delete(
2536
- `/session/${sessionID}`
3959
+ `/session/${sessionID}`,
3960
+ { keepalive: true }
2537
3961
  );
2538
3962
  } catch (error) {
2539
3963
  throw XmaxError.from(error);
@@ -2554,7 +3978,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2554
3978
  return;
2555
3979
  }
2556
3980
  _RealtimeSessionService.ensureSessionActive(session);
2557
- _optionalChain([handlers, 'access', _96 => _96.onRefresh, 'optionalCall', _97 => _97(session)]);
3981
+ await _optionalChain([handlers, 'access', _272 => _272.onRefresh, 'optionalCall', _273 => _273(session)]);
2558
3982
  } catch (error) {
2559
3983
  if (this.heartbeatVersion !== version) {
2560
3984
  return;
@@ -2568,11 +3992,35 @@ var RealtimeSessionService = class _RealtimeSessionService {
2568
3992
  /**
2569
3993
  * 发送一次心跳请求并解析会话数据(不要求携带完整连接参数)。
2570
3994
  */
2571
- async heartbeatSession(sessionID) {
3995
+ heartbeatSession(sessionID) {
3996
+ const pending = this.refreshes.get(sessionID);
3997
+ if (pending) return pending;
3998
+ const operation = this.performHeartbeat(sessionID);
3999
+ this.refreshes.set(sessionID, operation);
4000
+ void operation.finally(() => {
4001
+ if (this.refreshes.get(sessionID) === operation) this.refreshes.delete(sessionID);
4002
+ }).catch(() => {
4003
+ });
4004
+ return operation;
4005
+ }
4006
+ /**
4007
+ * 合并心跳部分凭据;已关闭会话的迟到响应不重建缓存。
4008
+ */
4009
+ async performHeartbeat(sessionID) {
4010
+ const previous = this.credentials.get(sessionID);
2572
4011
  const payload = await this.apiService.put(
2573
4012
  `/session/${sessionID}/heartbeat`
2574
4013
  );
2575
- return _RealtimeSessionService.makeSession(payload, false);
4014
+ const incoming = _RealtimeSessionService.decodeConnectionPayload(payload.modelExtra);
4015
+ if (payload.modelExtra != null && !incoming) {
4016
+ throw new XmaxError("SESSION_ERROR" /* sessionError */, "Invalid heartbeat RTC credentials");
4017
+ }
4018
+ const merged = incoming ? { ...previous, ...incoming } : previous;
4019
+ const session = this.makeSession({ ...payload, modelExtra: merged }, merged !== void 0);
4020
+ if (session.id !== sessionID) throw new XmaxError("SESSION_ERROR" /* sessionError */, "Heartbeat session identity changed");
4021
+ _RealtimeSessionService.ensureSessionActive(session);
4022
+ if (previous && this.credentials.get(sessionID) === previous && merged) this.credentials.set(sessionID, merged);
4023
+ return session;
2576
4024
  }
2577
4025
  /**
2578
4026
  * 校验会话仍处于活跃状态;状态明确非 ACTIVE 时抛出会话错误。
@@ -2589,12 +4037,12 @@ var RealtimeSessionService = class _RealtimeSessionService {
2589
4037
  /**
2590
4038
  * 由会话接口数据构造会话模型;`requiresConnection` 要求携带完整 RTC 连接参数。
2591
4039
  */
2592
- static makeSession(payload, requiresConnection) {
4040
+ makeSession(payload, requiresConnection) {
2593
4041
  const sessionID = _RealtimeSessionService.nonEmpty(payload.sessionUid);
2594
4042
  if (!sessionID) {
2595
4043
  throw new XmaxError("SESSION_ERROR" /* sessionError */, "Invalid session response");
2596
4044
  }
2597
- const connection = _RealtimeSessionService.makeConnection(payload.modelExtra);
4045
+ const connection = this.makeConnection(payload.modelExtra, payload.userUid);
2598
4046
  if (requiresConnection && !connection) {
2599
4047
  throw new XmaxError(
2600
4048
  "SESSION_ERROR" /* sessionError */,
@@ -2610,19 +4058,30 @@ var RealtimeSessionService = class _RealtimeSessionService {
2610
4058
  });
2611
4059
  }
2612
4060
  /**
2613
- * 解析 `modelExtra` 中的 TRTC 连接参数;字段不完整或提供方不支持时返回空。
4061
+ * 解析 `modelExtra` 中的 RTC 连接参数;字段不完整或提供方不支持时返回空。
2614
4062
  */
2615
- static makeConnection(modelExtra) {
4063
+ makeConnection(modelExtra, fallbackUserID) {
2616
4064
  const payload = _RealtimeSessionService.decodeConnectionPayload(modelExtra);
2617
4065
  if (!payload) {
2618
4066
  return void 0;
2619
4067
  }
2620
- if (_RealtimeSessionService.nonEmpty(payload.provider) !== "trtc") {
2621
- return void 0;
2622
- }
4068
+ const provider = this.provider;
2623
4069
  const roomID = _RealtimeSessionService.nonEmpty(payload.room_id);
2624
4070
  const sdkAppID = _RealtimeSessionService.nonEmpty(payload.rtc_app_id);
2625
- const userID = _RealtimeSessionService.nonEmpty(payload.rtc_user_id);
4071
+ const userID = provider === "vertc" /* vertc */ ? _nullishCoalesce(_RealtimeSessionService.nonEmpty(payload.user_id), () => ( _RealtimeSessionService.nonEmpty(fallbackUserID))) : _RealtimeSessionService.nonEmpty(payload.rtc_user_id);
4072
+ if (!roomID || !sdkAppID || !userID) return void 0;
4073
+ if (provider === "agora" /* agora */ || provider === "vertc" /* vertc */) {
4074
+ const roomToken = _RealtimeSessionService.nonEmpty(payload.room_token);
4075
+ if (!roomToken) return void 0;
4076
+ return Object.freeze({
4077
+ provider,
4078
+ roomID,
4079
+ appID: sdkAppID,
4080
+ userID,
4081
+ roomToken,
4082
+ botID: _RealtimeSessionService.nonEmpty(provider === "vertc" /* vertc */ ? payload.bot_name : payload.rtc_bot_id)
4083
+ });
4084
+ }
2626
4085
  const userSig = _RealtimeSessionService.nonEmpty(payload.user_sig);
2627
4086
  const privateMapKey = _RealtimeSessionService.nonEmpty(
2628
4087
  payload.private_map_key_with_string_room_id
@@ -2646,12 +4105,12 @@ var RealtimeSessionService = class _RealtimeSessionService {
2646
4105
  static decodeConnectionPayload(modelExtra) {
2647
4106
  if (typeof modelExtra === "string") {
2648
4107
  try {
2649
- return JSON.parse(modelExtra);
2650
- } catch (e10) {
4108
+ return _RealtimeSessionService.decodeConnectionPayload(JSON.parse(modelExtra));
4109
+ } catch (e14) {
2651
4110
  return void 0;
2652
4111
  }
2653
4112
  }
2654
- if (typeof modelExtra === "object" && modelExtra !== null) {
4113
+ if (typeof modelExtra === "object" && modelExtra !== null && !Array.isArray(modelExtra)) {
2655
4114
  return modelExtra;
2656
4115
  }
2657
4116
  return void 0;
@@ -2660,7 +4119,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2660
4119
  * 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
2661
4120
  */
2662
4121
  static nonEmpty(value) {
2663
- const normalized = _optionalChain([value, 'optionalAccess', _98 => _98.trim, 'call', _99 => _99()]);
4122
+ const normalized = typeof value === "string" ? value.trim() : void 0;
2664
4123
  return normalized || void 0;
2665
4124
  }
2666
4125
  };
@@ -2732,7 +4191,7 @@ var RealtimeState = class {
2732
4191
  * @param other 待比较的状态。
2733
4192
  */
2734
4193
  equals(other) {
2735
- return this.connectionState === other.connectionState && this.sessionID === other.sessionID && this.taskID === other.taskID && _optionalChain([this, 'access', _100 => _100.reason, 'optionalAccess', _101 => _101.kind]) === _optionalChain([other, 'access', _102 => _102.reason, 'optionalAccess', _103 => _103.kind]);
4194
+ return this.connectionState === other.connectionState && this.sessionID === other.sessionID && this.taskID === other.taskID && _optionalChain([this, 'access', _274 => _274.reason, 'optionalAccess', _275 => _275.kind]) === _optionalChain([other, 'access', _276 => _276.reason, 'optionalAccess', _277 => _277.kind]);
2736
4195
  }
2737
4196
  };
2738
4197
 
@@ -2767,49 +4226,6 @@ var RemoteStream = class {
2767
4226
  }
2768
4227
  };
2769
4228
 
2770
- // src/Foundation/RTC/RoomJoinConfiguration.ts
2771
- var RoomJoinConfiguration = class {
2772
- /**
2773
- * 创建房间加入参数。
2774
- *
2775
- * @param init.roomID TRTC 房间号。
2776
- * @param init.userID TRTC 登录身份。
2777
- * @param init.sdkAppID TRTC SDKAppID(字符串形式)。
2778
- * @param init.userSig TRTC 登录签名。
2779
- * @param init.privateMapKey TRTC 进房鉴权字段。
2780
- */
2781
- constructor(init) {
2782
- /**
2783
- * 进房参数
2784
- */
2785
- /**
2786
- * TRTC 房间号(字符串房间号)。
2787
- */
2788
- __publicField(this, "roomID");
2789
- /**
2790
- * TRTC 登录身份(`rtc_user_id`)。
2791
- */
2792
- __publicField(this, "userID");
2793
- /**
2794
- * TRTC SDKAppID(字符串形式,进房时转换为数值)。
2795
- */
2796
- __publicField(this, "sdkAppID");
2797
- /**
2798
- * TRTC 登录签名。
2799
- */
2800
- __publicField(this, "userSig");
2801
- /**
2802
- * TRTC 进房鉴权字段(字符串房间号对应的 privateMapKey)。
2803
- */
2804
- __publicField(this, "privateMapKey");
2805
- this.roomID = init.roomID;
2806
- this.userID = init.userID;
2807
- this.sdkAppID = init.sdkAppID;
2808
- this.userSig = init.userSig;
2809
- this.privateMapKey = init.privateMapKey;
2810
- }
2811
- };
2812
-
2813
4229
  // src/Stream/Room/RoomEvent.ts
2814
4230
  var RoomEvent = class _RoomEvent {
2815
4231
  /**
@@ -2901,7 +4317,7 @@ var RoomEvent = class _RoomEvent {
2901
4317
  static encode(payload) {
2902
4318
  try {
2903
4319
  return JSON.stringify({ ...payload, runtime: RuntimeInfo.toJSON() });
2904
- } catch (e11) {
4320
+ } catch (e15) {
2905
4321
  throw new XmaxError(
2906
4322
  "INTERNAL_ERROR" /* internalError */,
2907
4323
  "Failed to encode RTC room event"
@@ -2960,7 +4376,7 @@ var RoomHeartbeat = class {
2960
4376
  return;
2961
4377
  }
2962
4378
  try {
2963
- this.rtcManager.sendRoomMessage(RoomEvent.heartbeat({ userID }));
4379
+ await this.rtcManager.sendRoomMessage(RoomEvent.heartbeat({ userID }));
2964
4380
  } catch (error) {
2965
4381
  if (this.cycleVersion !== version) {
2966
4382
  return;
@@ -2977,7 +4393,11 @@ var RoomHeartbeat = class {
2977
4393
  // src/Stream/Room/RoomMessageCodec.ts
2978
4394
  var CHUNK_EVENT = "__trtc_chunk__";
2979
4395
  var _RoomMessageCodec = class _RoomMessageCodec {
2980
- constructor() {
4396
+ /**
4397
+ * 创建厂商协议编解码器;TRTC 保持原事件名,Agora 使用 __agora_chunk__;null 表示不拆包。
4398
+ */
4399
+ constructor(chunkEvent = CHUNK_EVENT) {
4400
+ __publicField(this, "chunkEvent", chunkEvent);
2981
4401
  /**
2982
4402
  * 接收缓存
2983
4403
  */
@@ -2987,10 +4407,10 @@ var _RoomMessageCodec = class _RoomMessageCodec {
2987
4407
  * 编码出站消息。
2988
4408
  *
2989
4409
  * @returns 不超过阈值时返回仅含原消息的单元素数组;
2990
- * 否则返回按 `__trtc_chunk__` 协议包装的分片 JSON 数组。
4410
+ * 否则返回按当前提供方分片协议包装的 JSON 数组。
2991
4411
  */
2992
4412
  encodeOutgoing(message) {
2993
- if (_RoomMessageCodec.byteLength(message) <= _RoomMessageCodec.chunkThresholdBytes) {
4413
+ if (this.chunkEvent === null || _RoomMessageCodec.byteLength(message) <= _RoomMessageCodec.chunkThresholdBytes) {
2994
4414
  return [message];
2995
4415
  }
2996
4416
  const eventId = _RoomMessageCodec.makeEventId();
@@ -3005,7 +4425,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3005
4425
  );
3006
4426
  return slices.map(
3007
4427
  (data, index) => JSON.stringify({
3008
- event: CHUNK_EVENT,
4428
+ event: this.chunkEvent,
3009
4429
  eventId,
3010
4430
  index,
3011
4431
  count,
@@ -3023,11 +4443,11 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3023
4443
  let parsed;
3024
4444
  try {
3025
4445
  parsed = JSON.parse(message);
3026
- } catch (e12) {
4446
+ } catch (e16) {
3027
4447
  XmaxLogger.room.warning(() => "\u623F\u95F4\u6D88\u606F\u4E0D\u662F\u5408\u6CD5 JSON (Room Message Is Not Valid JSON)");
3028
4448
  return void 0;
3029
4449
  }
3030
- const chunk = _RoomMessageCodec.parseChunk(parsed);
4450
+ const chunk = this.parseChunk(parsed);
3031
4451
  if (!chunk) {
3032
4452
  return parsed;
3033
4453
  }
@@ -3054,7 +4474,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3054
4474
  const joined = parts.join("");
3055
4475
  try {
3056
4476
  return JSON.parse(joined);
3057
- } catch (e13) {
4477
+ } catch (e17) {
3058
4478
  XmaxLogger.room.warning(
3059
4479
  () => `\u623F\u95F4\u6D88\u606F\u7EC4\u5305\u540E\u4E0D\u662F\u5408\u6CD5 JSON (Reassembled Message Is Not Valid JSON)
3060
4480
  \u2514\u2500 eventId: ${chunk.eventId}`
@@ -3071,12 +4491,12 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3071
4491
  /**
3072
4492
  * 从已解析的值识别分片消息;字段不完整时按普通业务消息处理。
3073
4493
  */
3074
- static parseChunk(parsed) {
3075
- if (typeof parsed !== "object" || parsed === null) {
4494
+ parseChunk(parsed) {
4495
+ if (this.chunkEvent === null || typeof parsed !== "object" || parsed === null) {
3076
4496
  return void 0;
3077
4497
  }
3078
4498
  const record = parsed;
3079
- if (record.event !== CHUNK_EVENT || typeof record.eventId !== "string" || !Number.isInteger(record.index) || !Number.isInteger(record.count) || typeof record.data !== "string") {
4499
+ if (record.event !== this.chunkEvent || typeof record.eventId !== "string" || record.eventId.length === 0 || !Number.isInteger(record.index) || !Number.isInteger(record.count) || typeof record.data !== "string") {
3080
4500
  return void 0;
3081
4501
  }
3082
4502
  const index = record.index;
@@ -3106,7 +4526,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3106
4526
  let current = "";
3107
4527
  let currentBytes = 0;
3108
4528
  for (const char of message) {
3109
- const bytes = _RoomMessageCodec.byteLength(char);
4529
+ const bytes = _RoomMessageCodec.byteLength(JSON.stringify(char)) - 2;
3110
4530
  if (currentBytes + bytes > budget && current.length > 0) {
3111
4531
  slices.push(current);
3112
4532
  current = "";
@@ -3175,6 +4595,8 @@ var RoomController = class {
3175
4595
  */
3176
4596
  __publicField(this, "heartbeat");
3177
4597
  __publicField(this, "codec");
4598
+ __publicField(this, "customCodec");
4599
+ __publicField(this, "sendTail", Promise.resolve());
3178
4600
  /**
3179
4601
  * 事件监听
3180
4602
  */
@@ -3187,6 +4609,7 @@ var RoomController = class {
3187
4609
  this.rtcManager = options.rtcManager;
3188
4610
  this.heartbeat = _nullishCoalesce(options.heartbeat, () => ( new RoomHeartbeat({ rtcManager: options.rtcManager })));
3189
4611
  this.codec = _nullishCoalesce(options.codec, () => ( new RoomMessageCodec()));
4612
+ this.customCodec = options.codec !== void 0;
3190
4613
  }
3191
4614
  /**
3192
4615
  * 加入实时房间,并在异步边界前后确认连接操作仍然有效。
@@ -3209,15 +4632,10 @@ var RoomController = class {
3209
4632
  this.state = { kind: "joining", operationID };
3210
4633
  this.heartbeat.stop();
3211
4634
  try {
3212
- await this.rtcManager.joinRoom(
3213
- new RoomJoinConfiguration({
3214
- roomID: connection.roomID,
3215
- userID: connection.userID,
3216
- sdkAppID: connection.sdkAppID,
3217
- userSig: connection.userSig,
3218
- privateMapKey: connection.privateMapKey
3219
- })
4635
+ if (!this.customCodec) this.codec = new RoomMessageCodec(
4636
+ connection.provider === "vertc" ? null : connection.provider === "agora" ? "__agora_chunk__" : "__trtc_chunk__"
3220
4637
  );
4638
+ await this.rtcManager.joinRoom(toRoomJoinConfiguration(connection));
3221
4639
  ensureActive();
3222
4640
  if (this.state.kind !== "joining" || this.state.operationID !== operationID || this.leaveOperation) {
3223
4641
  throw new XmaxError("CANCELLED" /* cancelled */, "RTC room join was cancelled");
@@ -3257,7 +4675,7 @@ var RoomController = class {
3257
4675
  * @throws 房间未就绪或信令发送失败时抛出错误。
3258
4676
  */
3259
4677
  startGeneration(options) {
3260
- this.send(
4678
+ return this.send(
3261
4679
  RoomEvent.start({
3262
4680
  userID: this.requireUserID(),
3263
4681
  taskID: options.taskID,
@@ -3273,7 +4691,7 @@ var RoomController = class {
3273
4691
  * @throws 房间未就绪或信令发送失败时抛出错误。
3274
4692
  */
3275
4693
  changeGenerationCondition(options) {
3276
- this.send(
4694
+ return this.send(
3277
4695
  RoomEvent.changeCondition({
3278
4696
  userID: this.requireUserID(),
3279
4697
  taskID: options.taskID,
@@ -3290,7 +4708,7 @@ var RoomController = class {
3290
4708
  */
3291
4709
  changeTargetSize(options) {
3292
4710
  options.ensureActive();
3293
- this.send(
4711
+ return this.send(
3294
4712
  RoomEvent.changeTargetSize({
3295
4713
  userID: this.requireUserID(),
3296
4714
  taskID: options.taskID,
@@ -3303,18 +4721,18 @@ var RoomController = class {
3303
4721
  */
3304
4722
  stopGeneration(taskID) {
3305
4723
  if (!taskID || this.state.kind !== "joined") {
3306
- return;
4724
+ return Promise.resolve();
3307
4725
  }
3308
- this.send(RoomEvent.stop({ userID: this.state.userID, taskID }));
4726
+ return this.send(RoomEvent.stop({ userID: this.state.userID, taskID }));
3309
4727
  }
3310
4728
  /**
3311
4729
  * 发送生成任务的交互轨迹;任务标识或轨迹为空时忽略。
3312
4730
  */
3313
4731
  sendTracks(taskID, points) {
3314
4732
  if (!taskID || points.length === 0) {
3315
- return;
4733
+ return Promise.resolve();
3316
4734
  }
3317
- this.send(
4735
+ return this.send(
3318
4736
  RoomEvent.tracks({
3319
4737
  userID: this.requireUserID(),
3320
4738
  taskID,
@@ -3335,8 +4753,12 @@ var RoomController = class {
3335
4753
  this.state = { kind: "idle" };
3336
4754
  this.heartbeat.stop();
3337
4755
  this.codec.reset();
3338
- await this.rtcManager.leaveRoom();
3339
- this.leaveOperation = void 0;
4756
+ this.sendTail = Promise.resolve();
4757
+ try {
4758
+ await this.rtcManager.leaveRoom();
4759
+ } finally {
4760
+ this.leaveOperation = void 0;
4761
+ }
3340
4762
  }
3341
4763
  /**
3342
4764
  * 组包入站消息并按目标用户过滤后分发完整业务消息。
@@ -3352,24 +4774,32 @@ var RoomController = class {
3352
4774
  if (targetUserID && ownUserID && targetUserID !== ownUserID) {
3353
4775
  return;
3354
4776
  }
3355
- _optionalChain([this, 'access', _104 => _104.listener, 'optionalAccess', _105 => _105.onRoomMessage, 'call', _106 => _106(senderUserID, message)]);
4777
+ _optionalChain([this, 'access', _278 => _278.listener, 'optionalAccess', _279 => _279.onRoomMessage, 'call', _280 => _280(senderUserID, message)]);
3356
4778
  }
3357
4779
  /**
3358
4780
  * 发送房间信令并输出日志。
3359
4781
  */
3360
4782
  send(message) {
3361
- for (const packet of this.codec.encodeOutgoing(message)) {
3362
- try {
3363
- this.rtcManager.sendRoomMessage(packet);
3364
- } catch (error) {
3365
- throw XmaxError.from(error);
4783
+ const state2 = this.state;
4784
+ const packets = this.codec.encodeOutgoing(message);
4785
+ const operation = this.sendTail.then(async () => {
4786
+ for (const packet of packets) {
4787
+ if (state2 !== this.state || state2.kind !== "joined") {
4788
+ throw new XmaxError("CANCELLED" /* cancelled */, "Room message was cancelled");
4789
+ }
4790
+ await this.rtcManager.sendRoomMessage(packet);
3366
4791
  }
3367
- }
3368
- XmaxLogger.room.info(
3369
- () => `\u53D1\u9001\u623F\u95F4\u4FE1\u4EE4 (Outbound Room Signaling)
4792
+ XmaxLogger.room.info(
4793
+ () => `\u53D1\u9001\u623F\u95F4\u4FE1\u4EE4 (Outbound Room Signaling)
3370
4794
  \u2514\u2500 ${XmaxLogger.localized("\u5185\u5BB9\uFF1A", "Content:")}
3371
4795
  ${JSON.stringify(JSON.parse(message), null, 2)}`
3372
- );
4796
+ );
4797
+ }).catch((error) => {
4798
+ throw XmaxError.from(error);
4799
+ });
4800
+ this.sendTail = operation.catch(() => {
4801
+ });
4802
+ return operation;
3373
4803
  }
3374
4804
  /**
3375
4805
  * 返回当前房间中的用户标识;未进房时抛出错误。
@@ -3542,6 +4972,9 @@ function makeInitialState() {
3542
4972
  localVideoPublished: false,
3543
4973
  localAudioPublished: false,
3544
4974
  subscribedRemoteUserIDs: /* @__PURE__ */ new Set(),
4975
+ publishedRemoteUserIDs: /* @__PURE__ */ new Set(),
4976
+ remoteVideoTracks: /* @__PURE__ */ new Map(),
4977
+ remoteVideoSubscriptions: /* @__PURE__ */ new Map(),
3545
4978
  subscribedRemoteAudioUserIDs: /* @__PURE__ */ new Set()
3546
4979
  };
3547
4980
  }
@@ -3595,20 +5028,24 @@ var StreamController = class {
3595
5028
  })));
3596
5029
  this.generationTimeoutMs = _nullishCoalesce(options.generationTimeoutMs, () => ( 3e4));
3597
5030
  this.rtcManager.setEventListener({
5031
+ onTokenWillExpire: options.onCredentialsRequired,
5032
+ onTokenExpired: options.onCredentialsRequired,
5033
+ onRoomRejoining: () => this.handleRoomRejoining(),
5034
+ onError: (error) => this.errorListener(error),
3598
5035
  onNetworkStatistics: (statistics) => {
3599
5036
  if (this.state.localVideoPublished) {
3600
- _optionalChain([this, 'access', _107 => _107.networkStatisticsListener, 'optionalCall', _108 => _108(statistics)]);
5037
+ _optionalChain([this, 'access', _281 => _281.networkStatisticsListener, 'optionalCall', _282 => _282(statistics)]);
3601
5038
  }
3602
5039
  },
3603
5040
  onRemoteVideoStatistics: (statistics) => {
3604
- const userID = _optionalChain([this, 'access', _109 => _109.state, 'access', _110 => _110.activeRemoteStream, 'optionalAccess', _111 => _111.userID]);
3605
- _optionalChain([this, 'access', _112 => _112.remoteVideoStatisticsListener, 'optionalCall', _113 => _113(
5041
+ const userID = _optionalChain([this, 'access', _283 => _283.state, 'access', _284 => _284.activeRemoteStream, 'optionalAccess', _285 => _285.userID]);
5042
+ _optionalChain([this, 'access', _286 => _286.remoteVideoStatisticsListener, 'optionalCall', _287 => _287(
3606
5043
  userID ? statistics.find((item) => item.userID === userID) : void 0
3607
5044
  )]);
3608
5045
  },
3609
5046
  onLocalVideoStatistics: (statistics) => {
3610
5047
  if (this.state.localVideoPublished) {
3611
- _optionalChain([this, 'access', _114 => _114.localVideoStatisticsListener, 'optionalCall', _115 => _115(statistics)]);
5048
+ _optionalChain([this, 'access', _288 => _288.localVideoStatisticsListener, 'optionalCall', _289 => _289(statistics)]);
3612
5049
  }
3613
5050
  },
3614
5051
  onCustomMessageReceived: (senderUserID, message) => {
@@ -3673,14 +5110,14 @@ var StreamController = class {
3673
5110
  * @throws 连接已取消,或 RTC 进房、房间配置与本地流发布失败时抛出错误。
3674
5111
  */
3675
5112
  async connect(connection, includeLocalAudio, ensureActive, beforePublish) {
5113
+ this.configureRoom({ roomID: connection.roomID, botID: connection.botID });
3676
5114
  await this.roomController.join(connection, ensureActive);
3677
5115
  ensureActive();
3678
- this.configureRoom({ roomID: connection.roomID, botID: connection.botID });
3679
5116
  XmaxLogger.stream.info(
3680
5117
  () => `RTC \u623F\u95F4\u5DF2\u914D\u7F6E (RTC Room Configured)
3681
5118
  \u2514\u2500 roomID: ${connection.roomID}, botID: ${_nullishCoalesce(connection.botID, () => ( "(\u672A\u8BBE\u7F6E)"))}`
3682
5119
  );
3683
- await _optionalChain([beforePublish, 'optionalCall', _116 => _116()]);
5120
+ await _optionalChain([beforePublish, 'optionalCall', _290 => _290()]);
3684
5121
  ensureActive();
3685
5122
  await this.publishLocalStream(includeLocalAudio);
3686
5123
  }
@@ -3691,12 +5128,18 @@ var StreamController = class {
3691
5128
  await this.resetStream();
3692
5129
  await this.roomController.leave();
3693
5130
  }
5131
+ /**
5132
+ * 将会话刷新后的凭据应用到当前 RTC 连接。
5133
+ */
5134
+ async updateCredentials(connection, signal) {
5135
+ await this.rtcManager.updateCredentials(toRoomJoinConfiguration(connection), signal);
5136
+ }
3694
5137
  /**
3695
5138
  * 建立生成任务并发送开始信令。
3696
5139
  *
3697
5140
  * @returns 等待远端结果流确认的 Promise。
3698
- * @throws 任务标识无效、RTC 房间未就绪、已有生成任务,或开始信令
3699
- * 发送失败时同步抛出错误。
5141
+ * @throws 任务标识无效、RTC 房间未就绪或已有生成任务时同步抛错;
5142
+ * 发送失败或确认超时通过返回的 Promise 拒绝。
3700
5143
  */
3701
5144
  beginGeneration(options) {
3702
5145
  const taskID = options.taskID.trim();
@@ -3738,24 +5181,44 @@ var StreamController = class {
3738
5181
  };
3739
5182
  this.state.generationTaskID = taskID;
3740
5183
  this.state.generationWaiter = waiter;
5184
+ void confirmation.catch(() => {
5185
+ });
3741
5186
  try {
3742
- this.roomController.startGeneration({
5187
+ const sending = this.roomController.startGeneration({
3743
5188
  taskID,
3744
5189
  videoFormat: options.videoFormat,
3745
5190
  targetSize: options.targetSize,
3746
5191
  context: options.context
3747
5192
  });
5193
+ const state2 = this.state;
5194
+ const sent = Promise.resolve(sending).then(() => {
5195
+ if (this.state !== state2 || state2.generationTaskID !== taskID) return;
5196
+ for (const userID of state2.publishedRemoteUserIDs) {
5197
+ void this.handleRemoteVideoPublished(userID, true);
5198
+ }
5199
+ }).catch((error) => {
5200
+ const mapped = XmaxError.from(error);
5201
+ this.rejectGenerationStart(taskID, mapped);
5202
+ if (this.state.generationTaskID === taskID) {
5203
+ this.state.generationTaskID = void 0;
5204
+ void this.roomController.stopGeneration(taskID).catch(() => {
5205
+ });
5206
+ }
5207
+ throw mapped;
5208
+ });
5209
+ return Promise.all([sent, confirmation]).then(() => {
5210
+ });
3748
5211
  } catch (error) {
3749
5212
  const mapped = XmaxError.from(error);
3750
- this.state.generationTaskID = void 0;
3751
5213
  this.rejectGenerationStart(taskID, mapped);
5214
+ this.state.generationTaskID = void 0;
3752
5215
  try {
3753
- this.roomController.stopGeneration(taskID);
3754
- } catch (e14) {
5216
+ void this.roomController.stopGeneration(taskID).catch(() => {
5217
+ });
5218
+ } catch (e18) {
3755
5219
  }
3756
5220
  throw mapped;
3757
5221
  }
3758
- return confirmation;
3759
5222
  }
3760
5223
  /**
3761
5224
  * 在远端首帧已经可显示后订阅当前生成流的音频。
@@ -3777,9 +5240,9 @@ var StreamController = class {
3777
5240
  *
3778
5241
  * @throws RTC 房间未就绪或条件变更信令发送失败时抛出错误。
3779
5242
  */
3780
- updateGeneration(options) {
5243
+ async updateGeneration(options) {
3781
5244
  try {
3782
- this.roomController.changeGenerationCondition({
5245
+ await this.roomController.changeGenerationCondition({
3783
5246
  taskID: options.taskID,
3784
5247
  videoFormat: options.videoFormat,
3785
5248
  targetSize: options.targetSize,
@@ -3794,9 +5257,9 @@ var StreamController = class {
3794
5257
  *
3795
5258
  * @throws 操作已取消、房间未就绪或信令发送失败时抛出错误。
3796
5259
  */
3797
- changeTargetSize(taskID, targetSize, ensureActive) {
5260
+ async changeTargetSize(taskID, targetSize, ensureActive) {
3798
5261
  try {
3799
- this.roomController.changeTargetSize({ taskID, targetSize, ensureActive });
5262
+ await this.roomController.changeTargetSize({ taskID, targetSize, ensureActive });
3800
5263
  } catch (error) {
3801
5264
  throw XmaxError.from(error);
3802
5265
  }
@@ -3812,7 +5275,7 @@ var StreamController = class {
3812
5275
  return;
3813
5276
  }
3814
5277
  try {
3815
- this.roomController.stopGeneration(stoppedTaskID);
5278
+ await this.roomController.stopGeneration(stoppedTaskID);
3816
5279
  } catch (error) {
3817
5280
  throw XmaxError.from(error);
3818
5281
  }
@@ -3822,9 +5285,9 @@ var StreamController = class {
3822
5285
  *
3823
5286
  * @throws RTC 房间未就绪或轨迹信令发送失败时抛出错误。
3824
5287
  */
3825
- sendTracks(taskID, points) {
5288
+ async sendTracks(taskID, points) {
3826
5289
  try {
3827
- this.roomController.sendTracks(taskID, points);
5290
+ await this.roomController.sendTracks(taskID, points);
3828
5291
  } catch (error) {
3829
5292
  throw XmaxError.from(error);
3830
5293
  }
@@ -3840,7 +5303,7 @@ var StreamController = class {
3840
5303
  */
3841
5304
  configureRoom(options) {
3842
5305
  const roomID = options.roomID.trim();
3843
- const botID = _nullishCoalesce(_optionalChain([options, 'access', _117 => _117.botID, 'optionalAccess', _118 => _118.trim, 'call', _119 => _119()]), () => ( ""));
5306
+ const botID = _nullishCoalesce(_optionalChain([options, 'access', _291 => _291.botID, 'optionalAccess', _292 => _292.trim, 'call', _293 => _293()]), () => ( ""));
3844
5307
  if (!roomID) {
3845
5308
  throw new XmaxError(
3846
5309
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
@@ -3899,6 +5362,12 @@ var StreamController = class {
3899
5362
  if (!this.state.roomID) {
3900
5363
  return;
3901
5364
  }
5365
+ if (published) this.state.publishedRemoteUserIDs.add(trimmedUserID);
5366
+ else {
5367
+ this.state.publishedRemoteUserIDs.delete(trimmedUserID);
5368
+ this.state.remoteVideoSubscriptions.delete(trimmedUserID);
5369
+ this.state.remoteVideoTracks.delete(trimmedUserID);
5370
+ }
3902
5371
  if (this.state.botID && this.state.botID !== trimmedUserID) {
3903
5372
  if (this.state.generationTaskID === void 0) {
3904
5373
  XmaxLogger.stream.warning(
@@ -3917,37 +5386,55 @@ var StreamController = class {
3917
5386
  return;
3918
5387
  }
3919
5388
  this.state.subscribedRemoteUserIDs.delete(trimmedUserID);
3920
- if (_optionalChain([this, 'access', _120 => _120.state, 'access', _121 => _121.activeRemoteStream, 'optionalAccess', _122 => _122.userID]) === trimmedUserID) {
5389
+ if (_optionalChain([this, 'access', _294 => _294.state, 'access', _295 => _295.activeRemoteStream, 'optionalAccess', _296 => _296.userID]) === trimmedUserID) {
3921
5390
  await this.unsubscribeRemoteAudio(trimmedUserID);
3922
5391
  this.state.activeRemoteStream = void 0;
3923
5392
  this.clearRemoteStream();
3924
5393
  }
3925
5394
  }
5395
+ /**
5396
+ * RTC 重进房时废弃旧媒体绑定;业务任务、音频订阅意图和生成确认等待器继续保留。
5397
+ */
5398
+ handleRoomRejoining() {
5399
+ this.state.publishedRemoteUserIDs.clear();
5400
+ this.state.subscribedRemoteUserIDs.clear();
5401
+ this.state.remoteVideoSubscriptions.clear();
5402
+ this.state.remoteVideoTracks.clear();
5403
+ this.state.activeRemoteStream = void 0;
5404
+ this.clearRemoteStream();
5405
+ }
3926
5406
  /**
3927
5407
  * 订阅远端视频并在成功后就绪或确认生成任务。
3928
5408
  */
3929
5409
  async subscribeRemoteVideo(userID) {
3930
- if (this.state.subscribedRemoteUserIDs.has(userID)) {
5410
+ const state2 = this.state;
5411
+ const existing = state2.remoteVideoTracks.get(userID);
5412
+ if (existing) {
5413
+ if (state2.generationWaiter) this.confirmOrUpdateRemoteStream(userID, existing);
3931
5414
  return;
3932
5415
  }
5416
+ if (state2.remoteVideoSubscriptions.has(userID)) return;
5417
+ const subscription = {};
5418
+ state2.remoteVideoSubscriptions.set(userID, subscription);
5419
+ const isCurrent = () => this.state === state2 && state2.remoteVideoSubscriptions.get(userID) === subscription;
3933
5420
  try {
3934
5421
  const track = await this.rtcManager.subscribeRemoteVideo(userID, true);
5422
+ if (!isCurrent()) return;
3935
5423
  if (!track) {
3936
5424
  throw new XmaxError(
3937
5425
  "MEDIA_ERROR" /* mediaError */,
3938
5426
  "Remote video track is not available"
3939
5427
  );
3940
5428
  }
3941
- if (!this.state.roomID) {
3942
- return;
3943
- }
3944
- this.state.subscribedRemoteUserIDs.add(userID);
5429
+ state2.subscribedRemoteUserIDs.add(userID);
5430
+ state2.remoteVideoTracks.set(userID, track);
3945
5431
  XmaxLogger.stream.info(
3946
5432
  () => `\u8FDC\u7AEF\u89C6\u9891\u8BA2\u9605\u6210\u529F (Remote Video Subscribed)
3947
5433
  \u2514\u2500 userID: ${userID}`
3948
5434
  );
3949
5435
  this.confirmOrUpdateRemoteStream(userID, track);
3950
5436
  } catch (error) {
5437
+ if (!isCurrent()) return;
3951
5438
  const mapped = XmaxError.from(error);
3952
5439
  const pendingTaskID = this.state.generationWaiter ? this.state.generationTaskID : void 0;
3953
5440
  if (pendingTaskID) {
@@ -3955,6 +5442,8 @@ var StreamController = class {
3955
5442
  } else {
3956
5443
  this.errorListener(mapped);
3957
5444
  }
5445
+ } finally {
5446
+ if (isCurrent()) state2.remoteVideoSubscriptions.delete(userID);
3958
5447
  }
3959
5448
  }
3960
5449
  /**
@@ -3993,14 +5482,23 @@ var StreamController = class {
3993
5482
  }
3994
5483
  }
3995
5484
  /**
3996
- * 订阅远端音频:先应用当前音量再订阅。
5485
+ * 订阅远端音频并按平台能力应用当前音量。
5486
+ *
5487
+ * 支持音量控制的平台先设音量再订阅,避免以默认音量短暂出声;
5488
+ * iOS 上音量降级为播放/静音二值控制,必须先完成订阅(恢复播放)
5489
+ * 再按需静音,否则订阅的恢复播放会覆盖音量 0 的静音状态。
3997
5490
  */
3998
5491
  async subscribeRemoteAudio(userID) {
3999
5492
  if (this.state.subscribedRemoteAudioUserIDs.has(userID)) {
4000
5493
  return;
4001
5494
  }
4002
- this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
4003
- await this.rtcManager.subscribeRemoteAudio(userID, true);
5495
+ if (this.rtcManager.supportsRemoteAudioVolumeControl) {
5496
+ this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
5497
+ await this.rtcManager.subscribeRemoteAudio(userID, true);
5498
+ } else {
5499
+ await this.rtcManager.subscribeRemoteAudio(userID, true);
5500
+ this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
5501
+ }
4004
5502
  this.state.subscribedRemoteAudioUserIDs.add(userID);
4005
5503
  }
4006
5504
  /**
@@ -4077,7 +5575,7 @@ var StreamController = class {
4077
5575
  */
4078
5576
  resolveGenerationStart(taskID) {
4079
5577
  const waiter = this.state.generationWaiter;
4080
- if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _123 => _123.taskID]) !== taskID) {
5578
+ if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _297 => _297.taskID]) !== taskID) {
4081
5579
  return;
4082
5580
  }
4083
5581
  this.state.generationWaiter = void 0;
@@ -4089,7 +5587,7 @@ var StreamController = class {
4089
5587
  */
4090
5588
  rejectGenerationStart(taskID, error) {
4091
5589
  const waiter = this.state.generationWaiter;
4092
- if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _124 => _124.taskID]) !== taskID) {
5590
+ if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _298 => _298.taskID]) !== taskID) {
4093
5591
  return;
4094
5592
  }
4095
5593
  this.state.generationWaiter = void 0;
@@ -4100,7 +5598,7 @@ var StreamController = class {
4100
5598
  * 通知渲染层清理远端生成流;失败仅记录日志。
4101
5599
  */
4102
5600
  clearRemoteStream() {
4103
- _optionalChain([this, 'access', _125 => _125.remoteVideoStatisticsListener, 'optionalCall', _126 => _126(void 0)]);
5601
+ _optionalChain([this, 'access', _299 => _299.remoteVideoStatisticsListener, 'optionalCall', _300 => _300(void 0)]);
4104
5602
  try {
4105
5603
  this.remoteStreamListener(null);
4106
5604
  } catch (error) {
@@ -4371,7 +5869,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4371
5869
  }
4372
5870
  throw _RealtimeCoordinator.cancelledError();
4373
5871
  }
4374
- const terminationTask = await _asyncNullishCoalesce(await _asyncNullishCoalesce(operation.terminationTask, async () => ( _optionalChain([this, 'access', _127 => _127.termination, 'optionalAccess', _128 => _128.task]))), async () => ( await this.requestTermination(
5872
+ const terminationTask = await _asyncNullishCoalesce(await _asyncNullishCoalesce(operation.terminationTask, async () => ( _optionalChain([this, 'access', _301 => _301.termination, 'optionalAccess', _302 => _302.task]))), async () => ( await this.requestTermination(
4375
5873
  _nullishCoalesce(operation.lease.failureScope, () => ( 0)) /* connection */,
4376
5874
  void 0,
4377
5875
  RealtimeReason.normal,
@@ -4500,7 +5998,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4500
5998
  * 结束指定操作,释放操作准入场。
4501
5999
  */
4502
6000
  finish(operation) {
4503
- if (_optionalChain([this, 'access', _129 => _129.activeOperation, 'optionalAccess', _130 => _130.id]) === operation.id) {
6001
+ if (_optionalChain([this, 'access', _303 => _303.activeOperation, 'optionalAccess', _304 => _304.id]) === operation.id) {
4504
6002
  this.activeOperation = void 0;
4505
6003
  }
4506
6004
  }
@@ -4512,7 +6010,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4512
6010
  return;
4513
6011
  }
4514
6012
  this.state = nextState;
4515
- _optionalChain([this, 'access', _131 => _131.stateListener, 'optionalCall', _132 => _132(nextState)]);
6013
+ _optionalChain([this, 'access', _305 => _305.stateListener, 'optionalCall', _306 => _306(nextState)]);
4516
6014
  }
4517
6015
  /**
4518
6016
  * 构造统一的操作取消错误。
@@ -4588,17 +6086,25 @@ var RealtimeLaunchTimer = class {
4588
6086
  this.notify();
4589
6087
  return complete;
4590
6088
  }
6089
+ /**
6090
+ * 开始等待用户授权系统媒体权限,返回授权决定时调用的记录函数。
6091
+ * 仅在系统弹出授权请求时使用;已授权或未弹窗不记录该阶段。
6092
+ */
6093
+ startPermission() {
6094
+ return this.measure("permissionMs", performance.now());
6095
+ }
4591
6096
  /**
4592
6097
  * 开始会话与进房计时,返回连接阶段完成时调用的记录函数。
6098
+ * 相机预热等待计入连接耗时。
4593
6099
  */
4594
6100
  startConnection() {
4595
6101
  return this.measure("connectionMs", performance.now());
4596
6102
  }
4597
6103
  /**
4598
- * 开始亮度检测计时;合格帧到达或超时放行时调用返回的记录函数。
6104
+ * 开始发布本地流计时,返回发布完成时调用的记录函数。
4599
6105
  */
4600
- startFrameValidation() {
4601
- return this.measure("frameValidationMs", performance.now());
6106
+ startPublish() {
6107
+ return this.measure("publishMs", performance.now());
4602
6108
  }
4603
6109
  /**
4604
6110
  * 开始等待远端显示首帧;完成回调同时记录首帧耗时和实际总耗时。
@@ -4636,9 +6142,9 @@ var RealtimeLaunchTimer = class {
4636
6142
  */
4637
6143
  notify() {
4638
6144
  try {
4639
- void Promise.resolve(_optionalChain([this, 'access', _133 => _133.listener, 'optionalCall', _134 => _134(this.snapshot)])).catch(() => {
6145
+ void Promise.resolve(_optionalChain([this, 'access', _307 => _307.listener, 'optionalCall', _308 => _308(this.snapshot)])).catch(() => {
4640
6146
  });
4641
- } catch (e15) {
6147
+ } catch (e19) {
4642
6148
  }
4643
6149
  }
4644
6150
  };
@@ -4654,6 +6160,10 @@ var XmaxRealtimeConnectionManager = class {
4654
6160
  * 会话资源
4655
6161
  */
4656
6162
  __publicField(this, "activeSession");
6163
+ __publicField(this, "refreshVersion", 0);
6164
+ __publicField(this, "credentialRefresh");
6165
+ __publicField(this, "credentialApplication", Promise.resolve());
6166
+ __publicField(this, "credentialController");
4657
6167
  /**
4658
6168
  * 远端轨道与渲染绑定
4659
6169
  */
@@ -4664,7 +6174,7 @@ var XmaxRealtimeConnectionManager = class {
4664
6174
  * 当前活动会话标识;尚未连接或清理完成后为空。
4665
6175
  */
4666
6176
  get currentSessionID() {
4667
- return _optionalChain([this, 'access', _135 => _135.activeSession, 'optionalAccess', _136 => _136.id]);
6177
+ return _optionalChain([this, 'access', _309 => _309.activeSession, 'optionalAccess', _310 => _310.id]);
4668
6178
  }
4669
6179
  /**
4670
6180
  * 未配置 API 服务时在连接操作开始前报错,不影响本地预览。
@@ -4674,7 +6184,8 @@ var XmaxRealtimeConnectionManager = class {
4674
6184
  }
4675
6185
  /**
4676
6186
  * 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
4677
- * 进房结束即记录连接耗时;可选 beforePublish 检查只阻塞发布,不计入连接耗时。
6187
+ * 进房结束即记录连接耗时;可选 beforePublish 检查阻塞发布,计入连接耗时;
6188
+ * 发布本地流单独计时。
4678
6189
  * @throws 会话、进房、发布失败或操作租约已失效时抛出错误,由上层统一清理。
4679
6190
  */
4680
6191
  async connect(options) {
@@ -4692,6 +6203,11 @@ var XmaxRealtimeConnectionManager = class {
4692
6203
  "Session does not contain RTC join information"
4693
6204
  );
4694
6205
  }
6206
+ if (connection.provider !== (_nullishCoalesce(this.dependencies.provider, () => ( "trtc")) /* trtc */)) {
6207
+ throw new XmaxError("SESSION_ERROR" /* sessionError */, "RTC adapter configuration does not match the session service");
6208
+ }
6209
+ const refreshVersion = ++this.refreshVersion;
6210
+ this.credentialController = new AbortController();
4695
6211
  const videoFormat = options.localTrack.videoFormat;
4696
6212
  if (!videoFormat) {
4697
6213
  throw new XmaxError(
@@ -4701,27 +6217,29 @@ var XmaxRealtimeConnectionManager = class {
4701
6217
  }
4702
6218
  await streamController.setVideoEncoderConfig(videoFormat);
4703
6219
  options.ensureCurrent();
6220
+ let completePublish;
4704
6221
  await streamController.connect(connection, options.includeLocalAudio, () => {
4705
6222
  options.ensureCurrent();
4706
6223
  }, async () => {
4707
6224
  options.ensureCurrent();
6225
+ await _optionalChain([options, 'access', _311 => _311.beforePublish, 'optionalCall', _312 => _312()]);
4708
6226
  completeConnection();
4709
- options.ensureCurrent();
4710
- await _optionalChain([options, 'access', _137 => _137.beforePublish, 'optionalCall', _138 => _138()]);
6227
+ completePublish = this.dependencies.timing.startPublish();
4711
6228
  });
4712
6229
  options.ensureCurrent();
6230
+ _optionalChain([completePublish, 'optionalCall', _313 => _313()]);
4713
6231
  options.onPublished();
4714
6232
  sessionService.startHeartbeat(session.id, {
4715
6233
  onFailure: (sessionID, error) => {
4716
6234
  this.dependencies.onHeartbeatFailure(sessionID, error);
4717
6235
  },
4718
6236
  onRefresh: (refreshed) => {
4719
- this.handleSessionRefresh(refreshed);
6237
+ return this.handleSessionRefresh(refreshed, refreshVersion);
4720
6238
  }
4721
6239
  });
4722
6240
  const remoteTrack = new RealtimeVideoTrack({
4723
6241
  id: _nullishCoalesce(connection.botID, () => ( "video-remote")),
4724
- videoFormat: options.localTrack.videoFormat
6242
+ videoFormat: _nullishCoalesce(options.remoteVideoFormat, () => ( options.localTrack.videoFormat))
4725
6243
  });
4726
6244
  this.activeRemoteTrack = remoteTrack;
4727
6245
  this.registerRemoteBinding(remoteTrack);
@@ -4732,26 +6250,56 @@ var XmaxRealtimeConnectionManager = class {
4732
6250
  * 更新已绑定远端视图的插帧配置;传 undefined 停用插帧。
4733
6251
  */
4734
6252
  setFrameInterpolation(options) {
4735
- _optionalChain([this, 'access', _139 => _139.remoteTarget, 'optionalAccess', _140 => _140.setFrameInterpolation, 'optionalCall', _141 => _141(options)]);
6253
+ _optionalChain([this, 'access', _314 => _314.remoteTarget, 'optionalAccess', _315 => _315.setFrameInterpolation, 'optionalCall', _316 => _316(options)]);
4736
6254
  }
4737
6255
  /**
4738
6256
  * 清空远端视图的媒体流,保留轨道与视图绑定以供统一清理。
4739
6257
  */
4740
6258
  clearRemoteMedia() {
4741
- _optionalChain([this, 'access', _142 => _142.remoteTarget, 'optionalAccess', _143 => _143.setMediaStream, 'call', _144 => _144(null)]);
6259
+ _optionalChain([this, 'access', _317 => _317.remoteTarget, 'optionalAccess', _318 => _318.setMediaStream, 'call', _319 => _319(null)]);
4742
6260
  }
4743
6261
  /**
4744
6262
  * 停止会话心跳;未配置会话服务时不执行操作。
4745
6263
  */
4746
6264
  stopHeartbeat() {
4747
- _optionalChain([this, 'access', _145 => _145.dependencies, 'access', _146 => _146.sessionService, 'optionalAccess', _147 => _147.stopHeartbeat, 'call', _148 => _148()]);
6265
+ this.refreshVersion++;
6266
+ _optionalChain([this, 'access', _320 => _320.credentialController, 'optionalAccess', _321 => _321.abort, 'call', _322 => _322()]);
6267
+ this.credentialController = void 0;
6268
+ this.credentialRefresh = void 0;
6269
+ this.credentialApplication = Promise.resolve();
6270
+ _optionalChain([this, 'access', _323 => _323.dependencies, 'access', _324 => _324.sessionService, 'optionalAccess', _325 => _325.stopHeartbeat, 'call', _326 => _326()]);
6271
+ }
6272
+ /**
6273
+ * RTC Token 过期时复用会话心跳获取新凭据,并合并同一实例的并发请求。
6274
+ */
6275
+ async refreshCredentials() {
6276
+ if (this.credentialRefresh) return this.credentialRefresh;
6277
+ const current = this.activeSession;
6278
+ if (!_optionalChain([current, 'optionalAccess', _327 => _327.connection]) || current.connection.provider === "trtc") return;
6279
+ const version = this.refreshVersion;
6280
+ const operation = (async () => {
6281
+ try {
6282
+ const session = await this.requireSessionService().heartbeatSession(current.id);
6283
+ await this.handleSessionRefresh(session, version, true);
6284
+ } catch (error) {
6285
+ if (version === this.refreshVersion && _optionalChain([this, 'access', _328 => _328.activeSession, 'optionalAccess', _329 => _329.id]) === current.id) {
6286
+ this.dependencies.onHeartbeatFailure(current.id, XmaxError.from(error));
6287
+ }
6288
+ }
6289
+ })();
6290
+ this.credentialRefresh = operation;
6291
+ try {
6292
+ await operation;
6293
+ } finally {
6294
+ if (this.credentialRefresh === operation) this.credentialRefresh = void 0;
6295
+ }
4748
6296
  }
4749
6297
  /**
4750
6298
  * 等待接收轨解除静音;超时只警告,取消必须立即退出并移除监听。
4751
6299
  */
4752
6300
  async waitUntilRemoteTrackReady(timeoutMs, signal) {
4753
6301
  if (signal.aborted) throw RealtimeCoordinator.cancelledError();
4754
- const mediaTrack = _optionalChain([this, 'access', _149 => _149.activeRemoteTrack, 'optionalAccess', _150 => _150.mediaStreamTrack]);
6302
+ const mediaTrack = _optionalChain([this, 'access', _330 => _330.activeRemoteTrack, 'optionalAccess', _331 => _331.mediaStreamTrack]);
4755
6303
  if (!mediaTrack || !mediaTrack.muted) return;
4756
6304
  await new Promise((resolve, reject) => {
4757
6305
  const cleanup = () => {
@@ -4788,7 +6336,7 @@ var XmaxRealtimeConnectionManager = class {
4788
6336
  if (track) {
4789
6337
  track.mediaStreamTrack = void 0;
4790
6338
  }
4791
- _optionalChain([this, 'access', _151 => _151.remoteTarget, 'optionalAccess', _152 => _152.setMediaStream, 'call', _153 => _153(null)]);
6339
+ _optionalChain([this, 'access', _332 => _332.remoteTarget, 'optionalAccess', _333 => _333.setMediaStream, 'call', _334 => _334(null)]);
4792
6340
  return;
4793
6341
  }
4794
6342
  if (!track) {
@@ -4798,7 +6346,7 @@ var XmaxRealtimeConnectionManager = class {
4798
6346
  );
4799
6347
  }
4800
6348
  track.mediaStreamTrack = binding.videoTrack;
4801
- _optionalChain([this, 'access', _154 => _154.remoteTarget, 'optionalAccess', _155 => _155.setMediaStream, 'call', _156 => _156(new MediaStream([binding.videoTrack]))]);
6349
+ _optionalChain([this, 'access', _335 => _335.remoteTarget, 'optionalAccess', _336 => _336.setMediaStream, 'call', _337 => _337(new MediaStream([binding.videoTrack]))]);
4802
6350
  }
4803
6351
  /**
4804
6352
  * 为远端轨道注册渲染绑定:attach 时挂流占位,媒体轨到达后送入画面。
@@ -4811,7 +6359,7 @@ var XmaxRealtimeConnectionManager = class {
4811
6359
  }
4812
6360
  },
4813
6361
  attachHandler: (view) => {
4814
- _optionalChain([this, 'access', _157 => _157.remoteTarget, 'optionalAccess', _158 => _158.setFrameInterpolation, 'optionalCall', _159 => _159(void 0)]);
6362
+ _optionalChain([this, 'access', _338 => _338.remoteTarget, 'optionalAccess', _339 => _339.setFrameInterpolation, 'optionalCall', _340 => _340(void 0)]);
4815
6363
  view.isMirrored = this.dependencies.isMirrored();
4816
6364
  this.remoteTarget = view;
4817
6365
  this.dependencies.onRenderAttached();
@@ -4823,7 +6371,7 @@ var XmaxRealtimeConnectionManager = class {
4823
6371
  this.dependencies.onRenderDetached();
4824
6372
  this.remoteTarget = void 0;
4825
6373
  }
4826
- _optionalChain([view, 'access', _160 => _160.setFrameInterpolation, 'optionalCall', _161 => _161(void 0)]);
6374
+ _optionalChain([view, 'access', _341 => _341.setFrameInterpolation, 'optionalCall', _342 => _342(void 0)]);
4827
6375
  view.setMediaStream(null);
4828
6376
  }
4829
6377
  });
@@ -4853,9 +6401,18 @@ var XmaxRealtimeConnectionManager = class {
4853
6401
  * 心跳成功后的凭据刷新:仅凭据变化时覆盖本地缓存;房间绑定信息
4854
6402
  * (提供方、房间、应用、登录身份)变化时当前连接失效,按失败结束。
4855
6403
  */
4856
- handleSessionRefresh(session) {
6404
+ handleSessionRefresh(session, version, force = false) {
6405
+ const operation = this.credentialApplication.then(() => this.applySessionRefresh(session, version, force));
6406
+ this.credentialApplication = operation.catch(() => {
6407
+ });
6408
+ return operation;
6409
+ }
6410
+ /**
6411
+ * 校验绑定并应用最新凭据,异步刷新期间失效的房间不得恢复缓存。
6412
+ */
6413
+ async applySessionRefresh(session, version, force) {
4857
6414
  const current = this.activeSession;
4858
- if (!current || current.id !== session.id) {
6415
+ if (version !== this.refreshVersion || !current || current.id !== session.id) {
4859
6416
  return;
4860
6417
  }
4861
6418
  const next = session.connection;
@@ -4863,7 +6420,7 @@ var XmaxRealtimeConnectionManager = class {
4863
6420
  return;
4864
6421
  }
4865
6422
  const previous = current.connection;
4866
- const bindingChanged = previous !== void 0 && (next.provider !== previous.provider || next.roomID !== previous.roomID || next.sdkAppID !== previous.sdkAppID || next.userID !== previous.userID);
6423
+ const bindingChanged = previous !== void 0 && (next.provider !== previous.provider || next.roomID !== previous.roomID || connectionAppID(next) !== connectionAppID(previous) || next.botID !== previous.botID || next.userID !== previous.userID);
4867
6424
  if (bindingChanged) {
4868
6425
  this.dependencies.onHeartbeatFailure(
4869
6426
  session.id,
@@ -4871,6 +6428,10 @@ var XmaxRealtimeConnectionManager = class {
4871
6428
  );
4872
6429
  return;
4873
6430
  }
6431
+ if (next.provider !== "trtc" && (force || !previous || previous.provider === "trtc" || next.roomToken !== previous.roomToken)) {
6432
+ await this.dependencies.streamController.updateCredentials(next, _optionalChain([this, 'access', _343 => _343.credentialController, 'optionalAccess', _344 => _344.signal]));
6433
+ if (version !== this.refreshVersion || this.activeSession !== current) return;
6434
+ }
4874
6435
  this.activeSession = new RealtimeSession({
4875
6436
  id: session.id,
4876
6437
  userID: _nullishCoalesce(session.userID, () => ( current.userID)),
@@ -4883,7 +6444,7 @@ var XmaxRealtimeConnectionManager = class {
4883
6444
  * 尽力清理全部连接资源,即使退房失败也关闭服务端会话。
4884
6445
  */
4885
6446
  async disconnect() {
4886
- const sessionID = _optionalChain([this, 'access', _162 => _162.activeSession, 'optionalAccess', _163 => _163.id]);
6447
+ const sessionID = _optionalChain([this, 'access', _345 => _345.activeSession, 'optionalAccess', _346 => _346.id]);
4887
6448
  this.stopHeartbeat();
4888
6449
  this.clearRemoteMedia();
4889
6450
  try {
@@ -4983,16 +6544,17 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
4983
6544
  /**
4984
6545
  * 更新活动任务的生成条件;信令发送成功后才覆盖缓存,无任务或上下文时抛错。
4985
6546
  */
4986
- update(taskID, options) {
6547
+ async update(taskID, options) {
4987
6548
  const context = _nullishCoalesce(options.context, () => ( this.currentContext));
4988
6549
  if (!taskID || !context) {
4989
6550
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "A realtime context is required to update the current generation");
4990
6551
  }
4991
- this.streamController.updateGeneration({
6552
+ await this.streamController.updateGeneration({
4992
6553
  taskID,
4993
6554
  videoFormat: options.videoFormat,
4994
6555
  context
4995
6556
  });
6557
+ if (this.activeTaskID !== taskID) throw RealtimeCoordinator.cancelledError();
4996
6558
  this.currentContext = context;
4997
6559
  }
4998
6560
  /**
@@ -5016,7 +6578,7 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
5016
6578
  try {
5017
6579
  await completion;
5018
6580
  } finally {
5019
- if (_optionalChain([this, 'access', _164 => _164.pendingStop, 'optionalAccess', _165 => _165.completion]) === completion) this.pendingStop = void 0;
6581
+ if (_optionalChain([this, 'access', _347 => _347.pendingStop, 'optionalAccess', _348 => _348.completion]) === completion) this.pendingStop = void 0;
5020
6582
  }
5021
6583
  }
5022
6584
  /**
@@ -5080,6 +6642,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5080
6642
  */
5081
6643
  __publicField(this, "options");
5082
6644
  __publicField(this, "mediaService");
6645
+ /**
6646
+ * 本地流创建时的生成格式;上行编码调整不改变模型输入配置与远端插帧尺寸。
6647
+ */
6648
+ __publicField(this, "generationVideoFormat");
5083
6649
  /**
5084
6650
  * 插帧能力、用户配置与渲染版本
5085
6651
  */
@@ -5098,6 +6664,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5098
6664
  */
5099
6665
  __publicField(this, "launchTimer", new RealtimeLaunchTimer());
5100
6666
  __publicField(this, "remoteFrameDisplayHandler");
6667
+ /**
6668
+ * 媒体权限弹窗观察
6669
+ */
6670
+ __publicField(this, "permissionWatchDispose");
5101
6671
  /**
5102
6672
  * 本地、远端和网络统计快照及观察者
5103
6673
  */
@@ -5122,19 +6692,22 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5122
6692
  this.options = options;
5123
6693
  this.mediaService = new MediaService(options.model);
5124
6694
  this.interpolationRequested = options.isFrameInterpolationEnabled;
5125
- this.supportsInterpolation = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _166 => _166.supportsFrameInterpolation]), () => ( (async (size) => await frameInterpolationAdapter(size) !== null)));
6695
+ this.supportsInterpolation = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _349 => _349.supportsFrameInterpolation]), () => ( (async (size) => await frameInterpolationAdapter(size) !== null)));
5126
6696
  this.errorHandler = new RealtimeErrorHandler();
5127
- const rtcManager = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _167 => _167.rtcManager]), () => ( new RtcManager()));
5128
- this.cameraController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _168 => _168.cameraController]), () => ( new CameraController({
6697
+ const rtcManager = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _350 => _350.rtcManager]), () => ( createRtcManager(options.provider, _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _351 => _351.environment]), () => ( "china")) /* china */)));
6698
+ this.cameraController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _352 => _352.cameraController]), () => ( new CameraController({
5129
6699
  rtcManager,
5130
6700
  mediaService: this.mediaService,
5131
6701
  errorListener: (error) => {
5132
6702
  void this.errorHandler.report(error);
5133
6703
  }
5134
6704
  })));
5135
- const sessionService = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _169 => _169.sessionService]), () => ( (_optionalChain([dependencies, 'optionalAccess', _170 => _170.apiService]) ? new RealtimeSessionService({ apiService: dependencies.apiService }) : void 0)));
5136
- this.streamController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _171 => _171.streamController]), () => ( new StreamController({
6705
+ const sessionService = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _353 => _353.sessionService]), () => ( (_optionalChain([dependencies, 'optionalAccess', _354 => _354.apiService]) ? new RealtimeSessionService({ apiService: dependencies.apiService, provider: options.provider }) : void 0)));
6706
+ this.streamController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _355 => _355.streamController]), () => ( new StreamController({
5137
6707
  rtcManager,
6708
+ onCredentialsRequired: () => {
6709
+ void this.connectionManager.refreshCredentials();
6710
+ },
5138
6711
  errorListener: (error) => {
5139
6712
  void this.errorHandler.report(error);
5140
6713
  },
@@ -5144,15 +6717,16 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5144
6717
  }
5145
6718
  })));
5146
6719
  this.connectionManager = new XmaxRealtimeConnectionManager({
6720
+ provider: options.provider,
5147
6721
  sessionService,
5148
6722
  streamController: this.streamController,
5149
6723
  timing: this.launchTimer,
5150
- isMirrored: () => _optionalChain([this, 'access', _172 => _172.cameraController, 'access', _173 => _173.currentTrack, 'optionalAccess', _174 => _174.position]) === "front" /* front */,
6724
+ isMirrored: () => _optionalChain([this, 'access', _356 => _356.cameraController, 'access', _357 => _357.currentTrack, 'optionalAccess', _358 => _358.position]) === "front" /* front */,
5151
6725
  remoteAudioVolume: () => this.storedRemoteAudioVolume,
5152
6726
  onHeartbeatFailure: (sessionID, error) => {
5153
6727
  void this.handleHeartbeatFailure(sessionID, error);
5154
6728
  },
5155
- onFrameDisplayed: () => _optionalChain([this, 'access', _175 => _175.remoteFrameDisplayHandler, 'optionalCall', _176 => _176()]),
6729
+ onFrameDisplayed: () => _optionalChain([this, 'access', _359 => _359.remoteFrameDisplayHandler, 'optionalCall', _360 => _360()]),
5156
6730
  onRenderAttached: () => this.applyRemoteInterpolation(),
5157
6731
  onRenderDetached: () => {
5158
6732
  this.interpolationRevision++;
@@ -5199,7 +6773,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5199
6773
  */
5200
6774
  async setFrameInterpolationEnabled(enabled) {
5201
6775
  await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
5202
- const format = _optionalChain([this, 'access', _177 => _177.cameraController, 'access', _178 => _178.currentTrack, 'optionalAccess', _179 => _179.videoFormat]);
6776
+ const format = _nullishCoalesce(this.generationVideoFormat, () => ( _optionalChain([this, 'access', _361 => _361.cameraController, 'access', _362 => _362.currentTrack, 'optionalAccess', _363 => _363.videoFormat])));
5203
6777
  const size = format && enabled ? this.mediaService.resolveFrameInterpolationSize(format) : void 0;
5204
6778
  if (enabled && !await this.checkInterpolationSupport(size, token)) {
5205
6779
  token.ensureCurrent();
@@ -5325,9 +6899,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5325
6899
  */
5326
6900
  notifyRemoteVideoStatistics() {
5327
6901
  try {
5328
- void Promise.resolve(_optionalChain([this, 'access', _180 => _180.remoteVideoStatisticsListener, 'optionalCall', _181 => _181(this.remoteVideoStatistics)])).catch(() => {
6902
+ void Promise.resolve(_optionalChain([this, 'access', _364 => _364.remoteVideoStatisticsListener, 'optionalCall', _365 => _365(this.remoteVideoStatistics)])).catch(() => {
5329
6903
  });
5330
- } catch (e16) {
6904
+ } catch (e20) {
5331
6905
  }
5332
6906
  }
5333
6907
  /**
@@ -5342,9 +6916,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5342
6916
  */
5343
6917
  notifyNetworkStatistics() {
5344
6918
  try {
5345
- void Promise.resolve(_optionalChain([this, 'access', _182 => _182.networkStatisticsListener, 'optionalCall', _183 => _183(this.networkStatistics)])).catch(() => {
6919
+ void Promise.resolve(_optionalChain([this, 'access', _366 => _366.networkStatisticsListener, 'optionalCall', _367 => _367(this.networkStatistics)])).catch(() => {
5346
6920
  });
5347
- } catch (e17) {
6921
+ } catch (e21) {
5348
6922
  }
5349
6923
  }
5350
6924
  /**
@@ -5362,9 +6936,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5362
6936
  */
5363
6937
  notifyLocalVideoStatistics() {
5364
6938
  try {
5365
- void Promise.resolve(_optionalChain([this, 'access', _184 => _184.localVideoStatisticsListener, 'optionalCall', _185 => _185(this.localVideoStatistics)])).catch(() => {
6939
+ void Promise.resolve(_optionalChain([this, 'access', _368 => _368.localVideoStatisticsListener, 'optionalCall', _369 => _369(this.localVideoStatistics)])).catch(() => {
5366
6940
  });
5367
- } catch (e18) {
6941
+ } catch (e22) {
5368
6942
  }
5369
6943
  }
5370
6944
  /**
@@ -5416,7 +6990,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5416
6990
  *
5417
6991
  * 将返回的轨道绑定到预览视图;收到有效帧且视图已绑定后进入 `ready`。
5418
6992
  *
5419
- * @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
6993
+ * @param options.enableFrameValidation 是否在发布前等待相机预热(固定 200ms),默认 true;false 跳过等待。
5420
6994
  * @returns 包含本地相机视频轨道的媒体流。
5421
6995
  * @throws 模型不支持相机输入、配置无效、权限或采集启动失败时抛出错误。
5422
6996
  */
@@ -5434,15 +7008,25 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5434
7008
  token.ensureCurrent();
5435
7009
  const completeCamera = this.launchTimer.startCamera();
5436
7010
  this.clearVideoStatistics();
7011
+ this.disposePermissionWatch();
7012
+ const permissionWatch = await watchMediaPermissionPrompt(_nullishCoalesce(_optionalChain([options, 'optionalAccess', _370 => _370.useMicrophone]), () => ( false)));
7013
+ if (permissionWatch) {
7014
+ const completePermission = this.launchTimer.startPermission();
7015
+ void permissionWatch.decided.then(() => completePermission()).catch(() => {
7016
+ });
7017
+ this.permissionWatchDispose = permissionWatch.dispose;
7018
+ }
5437
7019
  let stream;
5438
7020
  try {
5439
7021
  token.ensureCurrent();
5440
7022
  stream = await this.cameraController.createLocalCameraStream(options);
5441
7023
  } catch (error) {
7024
+ this.disposePermissionWatch();
5442
7025
  this.launchTimer.cancel();
5443
7026
  throw error;
5444
7027
  }
5445
7028
  token.ensureCurrent();
7029
+ this.generationVideoFormat = _optionalChain([stream, 'access', _371 => _371.videoTrack, 'optionalAccess', _372 => _372.videoFormat]);
5446
7030
  completeCamera();
5447
7031
  this.cameraController.setPreviewReadyHandler((isCurrent) => {
5448
7032
  void this.coordinator.localPreviewDidBecomeReady(isCurrent);
@@ -5451,6 +7035,33 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5451
7035
  }
5452
7036
  );
5453
7037
  }
7038
+ /**
7039
+ * 移除媒体权限弹窗观察;授权决定已记录或不再需要观察时调用。
7040
+ */
7041
+ disposePermissionWatch() {
7042
+ _optionalChain([this, 'access', _373 => _373.permissionWatchDispose, 'optionalCall', _374 => _374()]);
7043
+ this.permissionWatchDispose = void 0;
7044
+ }
7045
+ /**
7046
+ * 应用完整的上行视频格式,成功后更新本地轨道元数据,不改变生成格式或生命周期。
7047
+ * 复用配置操作准入,避免与建连、切换摄像头或其他配置更新并发。
7048
+ *
7049
+ * @throws 无本地相机流、参数无效、操作冲突或底层编码更新失败时抛错。
7050
+ */
7051
+ async updateVideoFormat(videoFormat) {
7052
+ await this.coordinator.run("configuration" /* configuration */, void 0, async (token) => {
7053
+ const track = this.cameraController.currentTrack;
7054
+ const previousFormat = _optionalChain([track, 'optionalAccess', _375 => _375.videoFormat]);
7055
+ if (!track || !previousFormat) {
7056
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Local camera stream is not started");
7057
+ }
7058
+ videoFormat.validate();
7059
+ await this.streamController.setVideoEncoderConfig(videoFormat);
7060
+ token.ensureCurrent();
7061
+ _nullishCoalesce(this.generationVideoFormat, () => ( (this.generationVideoFormat = previousFormat)));
7062
+ track.updateVideoFormat(videoFormat);
7063
+ });
7064
+ }
5454
7065
  /**
5455
7066
  * 停止本地相机流并释放本地预览与 RTC 资源。
5456
7067
  */
@@ -5461,8 +7072,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5461
7072
  async (token) => {
5462
7073
  this.launchTimer.cancel();
5463
7074
  this.clearVideoStatistics();
7075
+ this.disposePermissionWatch();
5464
7076
  await this.cameraController.stopLocalCameraStream();
5465
7077
  token.ensureCurrent();
7078
+ this.generationVideoFormat = void 0;
5466
7079
  await this.coordinator.commit(
5467
7080
  new RealtimeState({ connectionState: "Idle" /* idle */ }),
5468
7081
  token
@@ -5494,8 +7107,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5494
7107
  * 使用当前 Manager 创建的本地流建立实时连接。
5495
7108
  *
5496
7109
  * 创建实时会话、加入 RTC 房间并发布本地流,成功后启动会话心跳。
5497
- * 启用帧检测时,发布前并行检查相机亮度,首张合格帧即放行;2 秒内未通过检查则
5498
- * 记录警告并按当前画面继续发布,不因环境较暗而阻断连接。
7110
+ * 启用帧检测时,发布前并行等待相机预热(固定 200ms),避免把黑帧推给 RTC。
5499
7111
  * 返回的远端媒体流在生成开始后承载远端生成画面。
5500
7112
  *
5501
7113
  * @param localStream 由 `createLocalCameraStream` 创建的本地媒体流。
@@ -5543,7 +7155,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5543
7155
  "The local stream must be created and started by this realtime manager"
5544
7156
  );
5545
7157
  }
5546
- const videoFormat = localTrack.videoFormat;
7158
+ const videoFormat = _nullishCoalesce(this.generationVideoFormat, () => ( localTrack.videoFormat));
5547
7159
  if (!videoFormat) {
5548
7160
  throw new XmaxError(
5549
7161
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
@@ -5552,10 +7164,11 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5552
7164
  }
5553
7165
  const currentState = this.coordinator.currentState;
5554
7166
  if (currentState.connectionState === "Generating" /* generating */) {
5555
- this.generationManager.update(currentState.taskID, {
7167
+ await this.generationManager.update(currentState.taskID, {
5556
7168
  videoFormat,
5557
7169
  context: options.context
5558
7170
  });
7171
+ token.ensureCurrent();
5559
7172
  return this.connectionManager.makeRemoteStream();
5560
7173
  }
5561
7174
  const context = this.generationManager.validateContext(options.context);
@@ -5604,17 +7217,15 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5604
7217
  new RealtimeState({ connectionState: "Connecting" /* connecting */ }),
5605
7218
  token
5606
7219
  );
5607
- const exposureController = new AbortController();
5608
- const abortExposure = () => exposureController.abort();
5609
- token.signal.addEventListener("abort", abortExposure, { once: true });
5610
- if (token.signal.aborted) abortExposure();
7220
+ const warmupController = new AbortController();
7221
+ const abortWarmup = () => warmupController.abort();
7222
+ token.signal.addEventListener("abort", abortWarmup, { once: true });
7223
+ if (token.signal.aborted) abortWarmup();
5611
7224
  try {
5612
7225
  let beforePublish;
5613
7226
  if (this.cameraController.isFrameValidationEnabled) {
5614
- const completeFrameValidation = this.launchTimer.startFrameValidation();
5615
- const cameraReady = this.cameraController.waitForValidCameraFrame(exposureController.signal).then(() => {
7227
+ const cameraReady = this.cameraController.waitForValidCameraFrame(warmupController.signal).then(() => {
5616
7228
  token.ensureCurrent();
5617
- completeFrameValidation();
5618
7229
  });
5619
7230
  void cameraReady.catch(() => {
5620
7231
  });
@@ -5622,6 +7233,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5622
7233
  }
5623
7234
  const remote = await this.connectionManager.connect({
5624
7235
  localTrack,
7236
+ remoteVideoFormat: this.generationVideoFormat,
5625
7237
  model: this.options.model,
5626
7238
  includeLocalAudio: this.cameraController.useMicrophone,
5627
7239
  ensureCurrent: () => token.ensureCurrent(),
@@ -5639,8 +7251,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5639
7251
  );
5640
7252
  return remote;
5641
7253
  } finally {
5642
- token.signal.removeEventListener("abort", abortExposure);
5643
- abortExposure();
7254
+ token.signal.removeEventListener("abort", abortWarmup);
7255
+ abortWarmup();
5644
7256
  }
5645
7257
  }
5646
7258
  /**
@@ -5697,6 +7309,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5697
7309
  await this.generationManager.reset(taskID);
5698
7310
  const sessionID = await this.connectionManager.disconnect();
5699
7311
  if (scope === 1 /* all */) {
7312
+ this.generationVideoFormat = void 0;
7313
+ this.disposePermissionWatch();
5700
7314
  try {
5701
7315
  await this.cameraController.stopLocalCameraStream();
5702
7316
  } catch (error) {
@@ -5802,12 +7416,18 @@ var XmaxClient = class {
5802
7416
  }
5803
7417
  /**
5804
7418
  * 创建实时媒体 Manager。
7419
+ * 模型专用地址优先于环境默认地址,并用于该 Manager 的整个会话生命周期。
5805
7420
  *
5806
7421
  * @param options 实时生成模型等业务配置。
5807
7422
  * @returns 可用于本地相机预览、实时连接与生成的实时 Manager。
5808
7423
  */
5809
7424
  createRealtimeManager(options) {
5810
- return new XmaxRealtimeManager(options, { apiService: this.apiService });
7425
+ const baseURL = modelBaseURL(options.model);
7426
+ const apiService = baseURL ? new ApiService({ apiKey: this.configuration.apiKey, baseURL }) : this.apiService;
7427
+ return new XmaxRealtimeManager(options, {
7428
+ apiService,
7429
+ environment: this.configuration.environment
7430
+ });
5811
7431
  }
5812
7432
  /**
5813
7433
  * 创建媒体处理与能力查询 Service。
@@ -5833,10 +7453,15 @@ var RealtimeConfiguration = class {
5833
7453
  * 创建实时业务配置。
5834
7454
  *
5835
7455
  * @param init.model 实时生成业务使用的模型。
7456
+ * @param init.provider RTC 提供方,默认 TRTC;创建后不可切换。
5836
7457
  * @param init.isFrameInterpolationEnabled 是否默认开启远端生成画面的插帧。
5837
7458
  * @param init.frameInterpolation 插帧开关和显示帧率上限的详细配置。
5838
7459
  */
5839
7460
  constructor(init) {
7461
+ /**
7462
+ * RTC 配置
7463
+ */
7464
+ __publicField(this, "provider");
5840
7465
  /**
5841
7466
  * 模型配置
5842
7467
  */
@@ -5855,12 +7480,16 @@ var RealtimeConfiguration = class {
5855
7480
  * 已补全默认值的只读插帧配置。
5856
7481
  */
5857
7482
  __publicField(this, "frameInterpolation");
7483
+ this.provider = _nullishCoalesce(init.provider, () => ( "trtc")) /* trtc */;
7484
+ if (!Object.values(RtcProvider).includes(this.provider)) {
7485
+ throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Unsupported RTC provider");
7486
+ }
5858
7487
  this.model = init.model;
5859
- const targetFrameRate = _nullishCoalesce(_optionalChain([init, 'access', _186 => _186.frameInterpolation, 'optionalAccess', _187 => _187.targetFrameRate]), () => ( 60));
7488
+ const targetFrameRate = _nullishCoalesce(_optionalChain([init, 'access', _376 => _376.frameInterpolation, 'optionalAccess', _377 => _377.targetFrameRate]), () => ( 60));
5860
7489
  if (!Number.isFinite(targetFrameRate) || targetFrameRate < 1 || targetFrameRate > 60) {
5861
7490
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Interpolation targetFrameRate must be between 1 and 60");
5862
7491
  }
5863
- this.isFrameInterpolationEnabled = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'access', _188 => _188.frameInterpolation, 'optionalAccess', _189 => _189.enabled]), () => ( init.isFrameInterpolationEnabled)), () => ( false));
7492
+ this.isFrameInterpolationEnabled = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'access', _378 => _378.frameInterpolation, 'optionalAccess', _379 => _379.enabled]), () => ( init.isFrameInterpolationEnabled)), () => ( false));
5864
7493
  this.frameInterpolation = Object.freeze({ enabled: this.isFrameInterpolationEnabled, targetFrameRate });
5865
7494
  }
5866
7495
  };
@@ -5890,7 +7519,7 @@ var RealtimeContext = class {
5890
7519
  */
5891
7520
  __publicField(this, "referencePath");
5892
7521
  this.prompt = init.prompt.trim();
5893
- const normalizedReferencePath = _optionalChain([init, 'access', _190 => _190.referencePath, 'optionalAccess', _191 => _191.trim, 'call', _192 => _192()]);
7522
+ const normalizedReferencePath = _optionalChain([init, 'access', _380 => _380.referencePath, 'optionalAccess', _381 => _381.trim, 'call', _382 => _382()]);
5894
7523
  this.referencePath = normalizedReferencePath || void 0;
5895
7524
  }
5896
7525
  };
@@ -8017,11 +9646,11 @@ var _FrameInterpolationManager = class _FrameInterpolationManager {
8017
9646
  if (this.destroyed) return;
8018
9647
  this.destroyed = true;
8019
9648
  this.device.removeEventListener("uncapturederror", this.onGPUError);
8020
- _optionalChain([this, 'access', _193 => _193.runtime, 'optionalAccess', _194 => _194.destroy, 'call', _195 => _195()]);
9649
+ _optionalChain([this, 'access', _383 => _383.runtime, 'optionalAccess', _384 => _384.destroy, 'call', _385 => _385()]);
8021
9650
  this.runtime = void 0;
8022
9651
  this.textures.forEach((texture) => texture.destroy());
8023
9652
  this.textures = [];
8024
- _optionalChain([this, 'access', _196 => _196.context, 'optionalAccess', _197 => _197.unconfigure, 'call', _198 => _198()]);
9653
+ _optionalChain([this, 'access', _386 => _386.context, 'optionalAccess', _387 => _387.unconfigure, 'call', _388 => _388()]);
8025
9654
  this.device.destroy();
8026
9655
  }
8027
9656
  };
@@ -8200,7 +9829,7 @@ var RemoteVideoFramePipeline = class {
8200
9829
  setActive(active) {
8201
9830
  if (active === this.active) return;
8202
9831
  this.active = active;
8203
- _optionalChain([this, 'access', _199 => _199.options, 'access', _200 => _200.onActiveChange, 'optionalCall', _201 => _201(active)]);
9832
+ _optionalChain([this, 'access', _389 => _389.options, 'access', _390 => _390.onActiveChange, 'optionalCall', _391 => _391(active)]);
8204
9833
  }
8205
9834
  resetTimeline() {
8206
9835
  this.generation++;
@@ -8231,7 +9860,7 @@ var RemoteVideoFramePipeline = class {
8231
9860
  this.callback = void 0;
8232
9861
  clearTimeout(this.initializationTimer);
8233
9862
  this.resetTimeline();
8234
- _optionalChain([this, 'access', _202 => _202.processor, 'optionalAccess', _203 => _203.destroy, 'call', _204 => _204()]);
9863
+ _optionalChain([this, 'access', _392 => _392.processor, 'optionalAccess', _393 => _393.destroy, 'call', _394 => _394()]);
8235
9864
  this.processor = void 0;
8236
9865
  }
8237
9866
  };
@@ -8289,7 +9918,7 @@ var XmaxVideoView = class {
8289
9918
  * DOM 挂载状态
8290
9919
  */
8291
9920
  __publicField(this, "detached", false);
8292
- this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _205 => _205.videoContentMode]), () => ( "fill")) /* fill */;
9921
+ this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _395 => _395.videoContentMode]), () => ( "fill")) /* fill */;
8293
9922
  this.element = document.createElement("div");
8294
9923
  this.element.style.position = "relative";
8295
9924
  this.element.style.overflow = "hidden";
@@ -8436,8 +10065,8 @@ var XmaxVideoView = class {
8436
10065
  stopInterpolation() {
8437
10066
  const pipeline = this.interpolationPipeline;
8438
10067
  this.interpolationPipeline = void 0;
8439
- _optionalChain([pipeline, 'optionalAccess', _206 => _206.stop, 'call', _207 => _207()]);
8440
- _optionalChain([this, 'access', _208 => _208.interpolationCanvas, 'optionalAccess', _209 => _209.remove, 'call', _210 => _210()]);
10068
+ _optionalChain([pipeline, 'optionalAccess', _396 => _396.stop, 'call', _397 => _397()]);
10069
+ _optionalChain([this, 'access', _398 => _398.interpolationCanvas, 'optionalAccess', _399 => _399.remove, 'call', _400 => _400()]);
8441
10070
  this.interpolationCanvas = void 0;
8442
10071
  }
8443
10072
  /**
@@ -8463,7 +10092,7 @@ var XmaxVideoView = class {
8463
10092
  detachCurrentTrack() {
8464
10093
  const track = this.currentTrack;
8465
10094
  if (track) {
8466
- _optionalChain([VideoRenderRegistry, 'access', _211 => _211.binding, 'call', _212 => _212(track), 'optionalAccess', _213 => _213.detachHandler, 'call', _214 => _214(this)]);
10095
+ _optionalChain([VideoRenderRegistry, 'access', _401 => _401.binding, 'call', _402 => _402(track), 'optionalAccess', _403 => _403.detachHandler, 'call', _404 => _404(this)]);
8467
10096
  }
8468
10097
  this.setFrameInterpolation(void 0);
8469
10098
  this.cancelFrameDisplayNotification();
@@ -8483,8 +10112,8 @@ var XmaxVideoView = class {
8483
10112
  }
8484
10113
  this.hasNotifiedFrameDisplay = true;
8485
10114
  this.cancelFrameDisplayNotification();
8486
- _optionalChain([binding, 'optionalAccess', _215 => _215.frameDisplayHandler, 'optionalCall', _216 => _216()]);
8487
- _optionalChain([this, 'access', _217 => _217.frameDisplayHandler, 'optionalCall', _218 => _218()]);
10115
+ _optionalChain([binding, 'optionalAccess', _405 => _405.frameDisplayHandler, 'optionalCall', _406 => _406()]);
10116
+ _optionalChain([this, 'access', _407 => _407.frameDisplayHandler, 'optionalCall', _408 => _408()]);
8488
10117
  };
8489
10118
  if (typeof this.videoElement.requestVideoFrameCallback === "function") {
8490
10119
  this.videoFrameCallbackID = this.videoElement.requestVideoFrameCallback(notify);
@@ -8554,9 +10183,9 @@ var XmaxRealtimeVideoView = class {
8554
10183
  __publicField(this, "currentLocalTrack");
8555
10184
  __publicField(this, "currentRemoteTrack");
8556
10185
  __publicField(this, "contentMode");
8557
- this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _219 => _219.videoContentMode]), () => ( "fill")) /* fill */;
8558
- this.currentLocalTrack = _optionalChain([options, 'optionalAccess', _220 => _220.localTrack]);
8559
- this.currentRemoteTrack = _optionalChain([options, 'optionalAccess', _221 => _221.remoteTrack]);
10186
+ this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _409 => _409.videoContentMode]), () => ( "fill")) /* fill */;
10187
+ this.currentLocalTrack = _optionalChain([options, 'optionalAccess', _410 => _410.localTrack]);
10188
+ this.currentRemoteTrack = _optionalChain([options, 'optionalAccess', _411 => _411.remoteTrack]);
8560
10189
  this.element = document.createElement("div");
8561
10190
  this.element.style.position = "relative";
8562
10191
  this.element.style.overflow = "hidden";
@@ -8714,5 +10343,6 @@ var XmaxRealtimeVideoView = class {
8714
10343
 
8715
10344
 
8716
10345
 
8717
- exports.XmaxEnvironment = XmaxEnvironment; exports.apiBaseURL = apiBaseURL; exports.XmaxLoggerOption = XmaxLoggerOption; exports.XmaxLogger = XmaxLogger; exports.XmaxErrorCode = XmaxErrorCode; exports.XmaxError = XmaxError; exports.RealtimeVideoEncoderPreference = RealtimeVideoEncoderPreference; exports.RealtimeVideoFormat = RealtimeVideoFormat; exports.RealtimeModel = RealtimeModel; exports.defaultFrameRate = defaultFrameRate; exports.defaultCameraVideoFormat = defaultCameraVideoFormat; exports.MediaService = MediaService; exports.StoredFile = StoredFile; exports.StorageService = StorageService; exports.CameraPosition = CameraPosition; exports.RealtimeVideoTrack = RealtimeVideoTrack; exports.RealtimeMediaStream = RealtimeMediaStream; exports.RealtimeConnectionState = RealtimeConnectionState; exports.RealtimeReason = RealtimeReason; exports.RealtimeState = RealtimeState; exports.XmaxConfiguration = XmaxConfiguration; exports.XmaxClient = XmaxClient; exports.RealtimeConfiguration = RealtimeConfiguration; exports.VideoContentMode = VideoContentMode; exports.RealtimeContext = RealtimeContext; exports.XmaxVideoView = XmaxVideoView; exports.XmaxRealtimeVideoView = XmaxRealtimeVideoView;
8718
- //# sourceMappingURL=chunk-5NIEZJP5.cjs.map
10346
+
10347
+ exports.XmaxEnvironment = XmaxEnvironment; exports.apiBaseURL = apiBaseURL; exports.XmaxLoggerOption = XmaxLoggerOption; exports.XmaxLogger = XmaxLogger; exports.XmaxErrorCode = XmaxErrorCode; exports.XmaxError = XmaxError; exports.RealtimeVideoEncoderPreference = RealtimeVideoEncoderPreference; exports.RealtimeVideoFormat = RealtimeVideoFormat; exports.RealtimeModel = RealtimeModel; exports.defaultFrameRate = defaultFrameRate; exports.defaultCameraVideoFormat = defaultCameraVideoFormat; exports.MediaService = MediaService; exports.StoredFile = StoredFile; exports.StorageService = StorageService; exports.CameraPosition = CameraPosition; exports.RtcProvider = RtcProvider; exports.RealtimeVideoTrack = RealtimeVideoTrack; exports.RealtimeMediaStream = RealtimeMediaStream; exports.RealtimeConnectionState = RealtimeConnectionState; exports.RealtimeReason = RealtimeReason; exports.RealtimeState = RealtimeState; exports.XmaxConfiguration = XmaxConfiguration; exports.XmaxClient = XmaxClient; exports.RealtimeConfiguration = RealtimeConfiguration; exports.VideoContentMode = VideoContentMode; exports.RealtimeContext = RealtimeContext; exports.XmaxVideoView = XmaxVideoView; exports.XmaxRealtimeVideoView = XmaxRealtimeVideoView;
10348
+ //# sourceMappingURL=chunk-FEL5MJMJ.cjs.map