@xmaxai/web-sdk 1.0.2 → 1.0.3

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.
@@ -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.3";
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",
@@ -609,7 +612,7 @@ async function frameInterpolationAdapter(size) {
609
612
  if (size && (!Number.isSafeInteger(size.width) || !Number.isSafeInteger(size.height) || size.width <= 0 || size.height <= 0)) return null;
610
613
  try {
611
614
  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;
615
+ if (!_optionalChain([adapter, 'optionalAccess', _8 => _8.features, 'access', _9 => _9.has, 'call', _10 => _10("shader-f16")])) return null;
613
616
  if (size && (Math.ceil(size.width / 16) * 16 > adapter.limits.maxTextureDimension2D || Math.ceil(size.height / 16) * 16 > adapter.limits.maxTextureDimension2D)) return null;
614
617
  return adapter;
615
618
  } catch (e5) {
@@ -823,7 +826,7 @@ var StorageService = class _StorageService {
823
826
  const startedAt = Date.now();
824
827
  try {
825
828
  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);
829
+ const contentType = _optionalChain([options, 'access', _11 => _11.contentType, 'optionalAccess', _12 => _12.trim, 'call', _13 => _13()]) || _StorageService.inferContentType(safeName);
827
830
  const body = _StorageService.makeBody(options.data, contentType);
828
831
  if (body.size === 0) {
829
832
  throw new XmaxError(
@@ -845,7 +848,7 @@ var StorageService = class _StorageService {
845
848
  Key: objectKey,
846
849
  Body: body,
847
850
  ContentType: contentType,
848
- onProgress: options.onProgress ? (info) => _optionalChain([options, 'access', _13 => _13.onProgress, 'optionalCall', _14 => _14(info.loaded, info.total)]) : void 0
851
+ onProgress: options.onProgress ? (info) => _optionalChain([options, 'access', _14 => _14.onProgress, 'optionalCall', _15 => _15(info.loaded, info.total)]) : void 0
849
852
  });
850
853
  const url = _StorageService.resolveObjectURL(
851
854
  uploaded.Location,
@@ -877,9 +880,9 @@ var StorageService = class _StorageService {
877
880
  const bucket = _StorageService.nonEmpty(payload.bucket);
878
881
  const region = _StorageService.nonEmpty(payload.region);
879
882
  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]));
883
+ const accessKeyID = _StorageService.nonEmpty(_optionalChain([payload, 'access', _16 => _16.credentials, 'optionalAccess', _17 => _17.accessKeyId]));
884
+ const secretAccessKey = _StorageService.nonEmpty(_optionalChain([payload, 'access', _18 => _18.credentials, 'optionalAccess', _19 => _19.secretAccessKey]));
885
+ const sessionToken = _StorageService.nonEmpty(_optionalChain([payload, 'access', _20 => _20.credentials, 'optionalAccess', _21 => _21.sessionToken]));
883
886
  if (!bucket || !region || !prefix || !accessKeyID || !secretAccessKey || !sessionToken) {
884
887
  throw new XmaxError("API_ERROR" /* apiError */, message);
885
888
  }
@@ -920,7 +923,7 @@ var StorageService = class _StorageService {
920
923
  reject(
921
924
  new XmaxError(
922
925
  "UPLOAD_ERROR" /* uploadError */,
923
- _optionalChain([error, 'access', _21 => _21.message, 'optionalAccess', _22 => _22.trim, 'call', _23 => _23()]) || "Storage upload failed"
926
+ _optionalChain([error, 'access', _22 => _22.message, 'optionalAccess', _23 => _23.trim, 'call', _24 => _24()]) || "Storage upload failed"
924
927
  )
925
928
  );
926
929
  return;
@@ -934,14 +937,14 @@ var StorageService = class _StorageService {
934
937
  * 其次拼接自定义 endpoint,最后回退到默认 COS 域名。
935
938
  */
936
939
  static resolveObjectURL(location, configuration, objectKey) {
937
- const candidate = _nullishCoalesce(_optionalChain([location, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()]), () => ( ""));
940
+ const candidate = _nullishCoalesce(_optionalChain([location, 'optionalAccess', _25 => _25.trim, 'call', _26 => _26()]), () => ( ""));
938
941
  if (candidate.startsWith("//")) {
939
942
  return `https:${candidate}`;
940
943
  }
941
944
  if (/^https?:\/\//i.test(candidate)) {
942
945
  return candidate;
943
946
  }
944
- const endpoint = _optionalChain([configuration, 'access', _26 => _26.endpoint, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()]);
947
+ const endpoint = _optionalChain([configuration, 'access', _27 => _27.endpoint, 'optionalAccess', _28 => _28.trim, 'call', _29 => _29()]);
945
948
  if (endpoint) {
946
949
  const normalized = /^https?:\/\//i.test(endpoint) ? endpoint : `https://${endpoint}`;
947
950
  return `${normalized.replace(/\/+$/, "")}/${objectKey}`;
@@ -975,7 +978,7 @@ var StorageService = class _StorageService {
975
978
  * 按文件名后缀推断内容类型。
976
979
  */
977
980
  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()]), () => ( ""));
981
+ 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
982
  return _nullishCoalesce(CONTENT_TYPES[extension], () => ( "application/octet-stream"));
980
983
  }
981
984
  /**
@@ -994,7 +997,7 @@ var StorageService = class _StorageService {
994
997
  * 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
995
998
  */
996
999
  static nonEmpty(value) {
997
- const normalized = _optionalChain([value, 'optionalAccess', _35 => _35.trim, 'call', _36 => _36()]);
1000
+ const normalized = _optionalChain([value, 'optionalAccess', _36 => _36.trim, 'call', _37 => _37()]);
998
1001
  return normalized || void 0;
999
1002
  }
1000
1003
  };
@@ -1006,6 +1009,68 @@ var CameraPosition = /* @__PURE__ */ ((CameraPosition3) => {
1006
1009
  return CameraPosition3;
1007
1010
  })(CameraPosition || {});
1008
1011
 
1012
+ // src/Foundation/Permissions/MediaPermissionWatch.ts
1013
+ var PERMISSION_POLL_INTERVAL_MS = 100;
1014
+ async function watchMediaPermissionPrompt(includeMicrophone) {
1015
+ if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _38 => _38.permissions, 'optionalAccess', _39 => _39.query])) {
1016
+ return void 0;
1017
+ }
1018
+ const names = ["camera", ...includeMicrophone ? ["microphone"] : []];
1019
+ const queryOnce = () => Promise.all(
1020
+ names.map(async (name) => {
1021
+ const status = await navigator.permissions.query({
1022
+ name
1023
+ });
1024
+ return status.state;
1025
+ })
1026
+ );
1027
+ const queryStates = () => Promise.all(
1028
+ names.map(async (name) => {
1029
+ try {
1030
+ const status = await navigator.permissions.query({
1031
+ name
1032
+ });
1033
+ return status.state;
1034
+ } catch (e6) {
1035
+ return "prompt";
1036
+ }
1037
+ })
1038
+ );
1039
+ let states;
1040
+ try {
1041
+ states = await queryOnce();
1042
+ } catch (e7) {
1043
+ return void 0;
1044
+ }
1045
+ if (states.every((state2) => state2 === "granted")) {
1046
+ return { decided: Promise.resolve(), dispose() {
1047
+ } };
1048
+ }
1049
+ if (!states.includes("prompt")) {
1050
+ return void 0;
1051
+ }
1052
+ let disposed = false;
1053
+ let resolveDecided;
1054
+ const decided = new Promise((resolve) => {
1055
+ resolveDecided = resolve;
1056
+ });
1057
+ const finishIfDecided = (current) => {
1058
+ if (disposed || current.includes("prompt")) {
1059
+ return;
1060
+ }
1061
+ dispose();
1062
+ resolveDecided();
1063
+ };
1064
+ const poll = setInterval(() => {
1065
+ void queryStates().then(finishIfDecided);
1066
+ }, PERMISSION_POLL_INTERVAL_MS);
1067
+ function dispose() {
1068
+ disposed = true;
1069
+ clearInterval(poll);
1070
+ }
1071
+ return { decided, dispose };
1072
+ }
1073
+
1009
1074
  // src/Foundation/RTC/RtcEngineManager.ts
1010
1075
  var RtcEngineLease = class {
1011
1076
  /**
@@ -1074,13 +1139,13 @@ var _RtcEngineManager = class _RtcEngineManager {
1074
1139
  * 释放有效租约、销毁 Engine,并把独占权交给下一个等待者。
1075
1140
  */
1076
1141
  release(lease) {
1077
- if (_optionalChain([this, 'access', _37 => _37.activeLease, 'optionalAccess', _38 => _38.id]) !== lease.id) {
1142
+ if (_optionalChain([this, 'access', _40 => _40.activeLease, 'optionalAccess', _41 => _41.id]) !== lease.id) {
1078
1143
  return;
1079
1144
  }
1080
1145
  this.activeLease = void 0;
1081
1146
  try {
1082
1147
  lease.engine.destroy();
1083
- } catch (e6) {
1148
+ } catch (e8) {
1084
1149
  }
1085
1150
  void this.fulfillNextRequest();
1086
1151
  }
@@ -1148,10 +1213,10 @@ var RtcStatsLogger = class {
1148
1213
  `${XmaxLogger.localized("\u7D2F\u8BA1\u63A5\u6536\uFF1A", "Bytes Received: ")}${this.value(stats.bytesReceived, "bytes")}`
1149
1214
  ])];
1150
1215
  const local = stats.localStatistics;
1151
- if (_optionalChain([local, 'optionalAccess', _39 => _39.audio])) {
1216
+ if (_optionalChain([local, 'optionalAccess', _42 => _42.audio])) {
1152
1217
  blocks.push(this.audioBlock("\u672C\u5730\u97F3\u9891\u53D1\u9001 (Local Audio Uplink)", local.audio, false));
1153
1218
  }
1154
- for (const video of _nullishCoalesce(_optionalChain([local, 'optionalAccess', _40 => _40.video]), () => ( []))) {
1219
+ for (const video of _nullishCoalesce(_optionalChain([local, 'optionalAccess', _43 => _43.video]), () => ( []))) {
1155
1220
  blocks.push(this.videoBlock("\u672C\u5730\u89C6\u9891\u53D1\u9001 (Local Video Uplink)", video, false));
1156
1221
  }
1157
1222
  for (const remote of _nullishCoalesce(stats.remoteStatistics, () => ( []))) {
@@ -1239,6 +1304,33 @@ var RtcStatsLogger = class {
1239
1304
  }
1240
1305
  };
1241
1306
 
1307
+ // src/Foundation/RTC/RtcVideoOrientation.ts
1308
+ function shouldTransposeRtcVideoSize(environment) {
1309
+ const userAgent = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _44 => _44.userAgent]), () => ( (typeof navigator === "undefined" ? "" : navigator.userAgent)));
1310
+ const maxTouchPoints = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _45 => _45.maxTouchPoints]), () => ( (typeof navigator === "undefined" ? 0 : _nullishCoalesce(navigator.maxTouchPoints, () => ( 0)))));
1311
+ const mobile = /Android|webOS|iPhone|iPad|iPod|Mobile/i.test(userAgent) || /Macintosh/i.test(userAgent) && maxTouchPoints > 1;
1312
+ if (!mobile) {
1313
+ return false;
1314
+ }
1315
+ const orientationType = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _46 => _46.orientationType]), () => ( (typeof window === "undefined" ? void 0 : _optionalChain([window, 'access', _47 => _47.screen, 'optionalAccess', _48 => _48.orientation, 'optionalAccess', _49 => _49.type]))));
1316
+ if (orientationType !== void 0) {
1317
+ return orientationType.includes("portrait");
1318
+ }
1319
+ const degrees = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _50 => _50.orientationDegrees]), () => ( (typeof window === "undefined" ? void 0 : window.orientation)));
1320
+ return degrees === 0 || degrees === 180;
1321
+ }
1322
+ function rtcOrientedVideoSize(size, environment) {
1323
+ return shouldTransposeRtcVideoSize(environment) ? { width: size.height, height: size.width } : size;
1324
+ }
1325
+
1326
+ // src/Foundation/RTC/RemoteAudioVolumeControl.ts
1327
+ function canControlRemoteAudioVolume(environment) {
1328
+ const userAgent = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _51 => _51.userAgent]), () => ( (typeof navigator === "undefined" ? "" : navigator.userAgent)));
1329
+ const maxTouchPoints = _nullishCoalesce(_optionalChain([environment, 'optionalAccess', _52 => _52.maxTouchPoints]), () => ( (typeof navigator === "undefined" ? 0 : _nullishCoalesce(navigator.maxTouchPoints, () => ( 0)))));
1330
+ const ios = /iPhone|iPad|iPod/i.test(userAgent) || /Macintosh/i.test(userAgent) && maxTouchPoints > 1;
1331
+ return !ios;
1332
+ }
1333
+
1242
1334
  // src/Foundation/RTC/RtcManager.ts
1243
1335
  var RTC_EVENT = {
1244
1336
  remoteVideoAvailable: "remote-video-available",
@@ -1313,18 +1405,18 @@ var RtcManager = class {
1313
1405
  this.isCapturing = false;
1314
1406
  this.isAudioCapturing = false;
1315
1407
  this.isInRoom = false;
1316
- _optionalChain([this, 'access', _41 => _41.removeStatsListeners, 'optionalCall', _42 => _42()]);
1408
+ _optionalChain([this, 'access', _53 => _53.removeStatsListeners, 'optionalCall', _54 => _54()]);
1317
1409
  this.removeStatsListeners = void 0;
1318
1410
  if (!lease) {
1319
1411
  return;
1320
1412
  }
1321
1413
  try {
1322
1414
  await lease.engine.stopLocalAudio();
1323
- } catch (e7) {
1415
+ } catch (e9) {
1324
1416
  }
1325
1417
  try {
1326
1418
  await lease.engine.stopLocalVideo();
1327
- } catch (e8) {
1419
+ } catch (e10) {
1328
1420
  }
1329
1421
  this.engineManager.release(lease);
1330
1422
  }
@@ -1343,13 +1435,17 @@ var RtcManager = class {
1343
1435
  );
1344
1436
  }
1345
1437
  try {
1438
+ const captureSize = rtcOrientedVideoSize({
1439
+ width: options.width,
1440
+ height: options.height
1441
+ });
1346
1442
  await engine.startLocalVideo({
1347
1443
  publish: false,
1348
1444
  option: {
1349
1445
  useFrontCamera: options.position === "front" /* front */,
1350
1446
  profile: {
1351
- width: options.width,
1352
- height: options.height,
1447
+ width: captureSize.width,
1448
+ height: captureSize.height,
1353
1449
  frameRate: options.frameRate,
1354
1450
  bitrate: CAPTURE_PLACEHOLDER_BITRATE
1355
1451
  }
@@ -1402,7 +1498,7 @@ var RtcManager = class {
1402
1498
  * 停止摄像头采集;引擎未初始化或采集未启动时不产生效果。
1403
1499
  */
1404
1500
  async stopCameraCapture() {
1405
- const engine = _optionalChain([this, 'access', _43 => _43.lease, 'optionalAccess', _44 => _44.engine]);
1501
+ const engine = _optionalChain([this, 'access', _55 => _55.lease, 'optionalAccess', _56 => _56.engine]);
1406
1502
  this.isCapturing = false;
1407
1503
  if (!engine) {
1408
1504
  return;
@@ -1452,7 +1548,7 @@ var RtcManager = class {
1452
1548
  * 离开当前 RTC 房间;未在房间中或引擎未初始化时不产生效果,退房失败仅记录日志。
1453
1549
  */
1454
1550
  async leaveRoom() {
1455
- const engine = _optionalChain([this, 'access', _45 => _45.lease, 'optionalAccess', _46 => _46.engine]);
1551
+ const engine = _optionalChain([this, 'access', _57 => _57.lease, 'optionalAccess', _58 => _58.engine]);
1456
1552
  if (!engine || !this.isInRoom) {
1457
1553
  return;
1458
1554
  }
@@ -1469,7 +1565,7 @@ var RtcManager = class {
1469
1565
  /**
1470
1566
  * 配置本地视频编码参数。
1471
1567
  *
1472
- * TRTC 只接受单一目标码率,以 `maximumBitrate` 作为目标码率;
1568
+ * TRTC 只接受单一目标码率,以 `minimumBitrate`(流畅优先档)作为目标码率;
1473
1569
  * 编码策略偏好映射为 TRTC 弱网偏好。
1474
1570
  *
1475
1571
  * @throws 摄像头采集未启动或编码参数配置失败时抛出错误。
@@ -1483,13 +1579,17 @@ var RtcManager = class {
1483
1579
  );
1484
1580
  }
1485
1581
  try {
1582
+ const encodingSize = rtcOrientedVideoSize({
1583
+ width: configuration.width,
1584
+ height: configuration.height
1585
+ });
1486
1586
  await engine.updateLocalVideo({
1487
1587
  option: {
1488
1588
  profile: {
1489
- width: configuration.width,
1490
- height: configuration.height,
1589
+ width: encodingSize.width,
1590
+ height: encodingSize.height,
1491
1591
  frameRate: configuration.frameRate,
1492
- bitrate: configuration.maximumBitrate
1592
+ bitrate: configuration.minimumBitrate
1493
1593
  },
1494
1594
  qosPreference: QOS_PREFERENCE_MAP[configuration.encoderPreference]
1495
1595
  }
@@ -1521,7 +1621,7 @@ var RtcManager = class {
1521
1621
  * 停止发布本地视频流;采集保持运行,本地预览不受影响。
1522
1622
  */
1523
1623
  async unpublishLocalVideo() {
1524
- const engine = _optionalChain([this, 'access', _47 => _47.lease, 'optionalAccess', _48 => _48.engine]);
1624
+ const engine = _optionalChain([this, 'access', _59 => _59.lease, 'optionalAccess', _60 => _60.engine]);
1525
1625
  if (!engine || !this.isCapturing) {
1526
1626
  return;
1527
1627
  }
@@ -1553,7 +1653,7 @@ var RtcManager = class {
1553
1653
  * 停止发布本地音频流;麦克风采集保持运行。
1554
1654
  */
1555
1655
  async unpublishLocalAudio() {
1556
- const engine = _optionalChain([this, 'access', _49 => _49.lease, 'optionalAccess', _50 => _50.engine]);
1656
+ const engine = _optionalChain([this, 'access', _61 => _61.lease, 'optionalAccess', _62 => _62.engine]);
1557
1657
  if (!engine || !this.isAudioCapturing) {
1558
1658
  return;
1559
1659
  }
@@ -1607,14 +1707,34 @@ var RtcManager = class {
1607
1707
  throw this.mapError(error);
1608
1708
  }
1609
1709
  }
1710
+ /**
1711
+ * 当前平台是否支持远端音量控制(iOS Safari/WKWebView 不支持)。
1712
+ */
1713
+ get supportsRemoteAudioVolumeControl() {
1714
+ return canControlRemoteAudioVolume();
1715
+ }
1610
1716
  /**
1611
1717
  * 设置指定远端用户的音频播放音量。
1612
1718
  *
1719
+ * iOS Safari/WKWebView 不允许 JS 修改媒体元素音量(TRTC 官方标注
1720
+ * `setRemoteAudioVolume` 不支持 iOS Safari),该平台上降级为
1721
+ * 播放/静音二值控制:音量为 0 时停止播放该路音频,非 0 时恢复播放;
1722
+ * 恢复播放需要重新拉流,会有短暂缓冲。
1723
+ *
1613
1724
  * @param volume 音量,取值范围为 `0...100`,越界时收敛到边界。
1614
1725
  */
1615
1726
  setRemoteAudioVolume(volume, userID) {
1616
1727
  const engine = this.requireEngine();
1617
1728
  const normalized = Math.min(Math.max(Math.round(volume), 0), 100);
1729
+ if (!canControlRemoteAudioVolume()) {
1730
+ void engine.muteRemoteAudio(userID, normalized === 0).catch((error) => {
1731
+ XmaxLogger.rtc.warning(
1732
+ () => `\u8FDC\u7AEF\u97F3\u9891\u64AD\u653E\u72B6\u6001\u5207\u6362\u5931\u8D25 (Failed to Toggle Remote Audio Playback)
1733
+ \u2514\u2500 ${XmaxLogger.localized("\u539F\u56E0\uFF1A", "Reason: ")}${XmaxError.from(error).message}`
1734
+ );
1735
+ });
1736
+ return;
1737
+ }
1618
1738
  engine.setRemoteAudioVolume(userID, normalized);
1619
1739
  }
1620
1740
  /**
@@ -1654,19 +1774,19 @@ var RtcManager = class {
1654
1774
  registerEventBridge(engine) {
1655
1775
  const source = engine;
1656
1776
  const onStatistics = (stats) => {
1657
- if (this.isInRoom && _optionalChain([this, 'access', _51 => _51.lease, 'optionalAccess', _52 => _52.engine]) === engine) {
1777
+ if (this.isInRoom && _optionalChain([this, 'access', _63 => _63.lease, 'optionalAccess', _64 => _64.engine]) === engine) {
1658
1778
  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")]);
1779
+ const video = _optionalChain([stats, 'access', _65 => _65.localStatistics, 'optionalAccess', _66 => _66.video, 'optionalAccess', _67 => _67.find, 'call', _68 => _68((item) => item.videoType === "big")]);
1660
1780
  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({
1781
+ _optionalChain([this, 'access', _69 => _69.eventListener, 'optionalAccess', _70 => _70.onLocalVideoStatistics, 'optionalCall', _71 => _71(video ? Object.freeze({
1662
1782
  width: valid(video.width, 1),
1663
1783
  height: valid(video.height, 1),
1664
1784
  frameRate: valid(video.frameRate, 0),
1665
1785
  bitrateKbps: valid(video.bitrate, 0)
1666
1786
  }) : void 0)]);
1667
- _optionalChain([this, 'access', _60 => _60.eventListener, 'optionalAccess', _61 => _61.onRemoteVideoStatistics, 'optionalCall', _62 => _62(Object.freeze(
1787
+ _optionalChain([this, 'access', _72 => _72.eventListener, 'optionalAccess', _73 => _73.onRemoteVideoStatistics, 'optionalCall', _74 => _74(Object.freeze(
1668
1788
  (_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")]);
1789
+ const video2 = _optionalChain([remote, 'access', _75 => _75.video, 'optionalAccess', _76 => _76.find, 'call', _77 => _77((item) => item.videoType === "big")]);
1670
1790
  if (!video2) {
1671
1791
  return [];
1672
1792
  }
@@ -1688,11 +1808,11 @@ var RtcManager = class {
1688
1808
  }
1689
1809
  };
1690
1810
  const onNetworkQuality = (stats) => {
1691
- if (this.isInRoom && _optionalChain([this, 'access', _66 => _66.lease, 'optionalAccess', _67 => _67.engine]) === engine) {
1811
+ if (this.isInRoom && _optionalChain([this, 'access', _78 => _78.lease, 'optionalAccess', _79 => _79.engine]) === engine) {
1692
1812
  RtcStatsLogger.logNetworkQuality(stats);
1693
1813
  const quality = (value) => Number.isInteger(value) && value >= 0 && value <= 6 ? value : void 0;
1694
1814
  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({
1815
+ _optionalChain([this, 'access', _80 => _80.eventListener, 'optionalAccess', _81 => _81.onNetworkStatistics, 'optionalCall', _82 => _82(Object.freeze({
1696
1816
  uplinkQuality: quality(stats.uplinkNetworkQuality),
1697
1817
  downlinkQuality: quality(stats.downlinkNetworkQuality),
1698
1818
  uplinkRttMs: rtt(stats.uplinkRTT),
@@ -1710,13 +1830,13 @@ var RtcManager = class {
1710
1830
  if (event.streamType !== STREAM_TYPE_MAIN) {
1711
1831
  return;
1712
1832
  }
1713
- _optionalChain([this, 'access', _71 => _71.eventListener, 'optionalAccess', _72 => _72.onRemoteVideoPublished, 'call', _73 => _73(event.userId, true)]);
1833
+ _optionalChain([this, 'access', _83 => _83.eventListener, 'optionalAccess', _84 => _84.onRemoteVideoPublished, 'call', _85 => _85(event.userId, true)]);
1714
1834
  });
1715
1835
  source.on(RTC_EVENT.remoteVideoUnavailable, (event) => {
1716
1836
  if (event.streamType !== STREAM_TYPE_MAIN) {
1717
1837
  return;
1718
1838
  }
1719
- _optionalChain([this, 'access', _74 => _74.eventListener, 'optionalAccess', _75 => _75.onRemoteVideoPublished, 'call', _76 => _76(event.userId, false)]);
1839
+ _optionalChain([this, 'access', _86 => _86.eventListener, 'optionalAccess', _87 => _87.onRemoteVideoPublished, 'call', _88 => _88(event.userId, false)]);
1720
1840
  });
1721
1841
  source.on(RTC_EVENT.customMessage, (event) => {
1722
1842
  if (event.cmdId !== CUSTOM_MESSAGE_CMD_ID) {
@@ -1725,18 +1845,18 @@ var RtcManager = class {
1725
1845
  let message;
1726
1846
  try {
1727
1847
  message = new TextDecoder().decode(event.data);
1728
- } catch (e9) {
1848
+ } catch (e11) {
1729
1849
  XmaxLogger.rtc.warning(() => "\u81EA\u5B9A\u4E49\u6D88\u606F\u89E3\u7801\u5931\u8D25 (Failed to Decode Custom Message)");
1730
1850
  return;
1731
1851
  }
1732
- _optionalChain([this, 'access', _77 => _77.eventListener, 'optionalAccess', _78 => _78.onCustomMessageReceived, 'call', _79 => _79(event.userId, message)]);
1852
+ _optionalChain([this, 'access', _89 => _89.eventListener, 'optionalAccess', _90 => _90.onCustomMessageReceived, 'call', _91 => _91(event.userId, message)]);
1733
1853
  });
1734
1854
  }
1735
1855
  /**
1736
1856
  * 返回当前引擎实例;未初始化时抛出错误。
1737
1857
  */
1738
1858
  requireEngine() {
1739
- const engine = _optionalChain([this, 'access', _80 => _80.lease, 'optionalAccess', _81 => _81.engine]);
1859
+ const engine = _optionalChain([this, 'access', _92 => _92.lease, 'optionalAccess', _93 => _93.engine]);
1740
1860
  if (!engine) {
1741
1861
  throw new XmaxError(
1742
1862
  "RTC_ERROR" /* rtcError */,
@@ -1753,8 +1873,8 @@ var RtcManager = class {
1753
1873
  return error;
1754
1874
  }
1755
1875
  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);
1876
+ const originName = _nullishCoalesce(_optionalChain([rtcError, 'optionalAccess', _94 => _94.originError, 'optionalAccess', _95 => _95.name]), () => ( ""));
1877
+ const message = typeof _optionalChain([rtcError, 'optionalAccess', _96 => _96.message]) === "string" && rtcError.message.trim().length > 0 ? rtcError.message.trim() : String(error);
1758
1878
  if (originName === "NotAllowedError" || originName === "SecurityError" || /NotAllowed/i.test(message)) {
1759
1879
  return new XmaxError("CAMERA_PERMISSION_DENIED" /* cameraPermissionDenied */, message);
1760
1880
  }
@@ -1766,74 +1886,25 @@ var RtcManager = class {
1766
1886
  };
1767
1887
 
1768
1888
  // 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;
1795
- }
1796
- };
1889
+ var CAMERA_WARMUP_MS = 200;
1797
1890
  function waitForValidCameraFrame(track, signal) {
1798
1891
  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
- }
1892
+ let timer;
1893
+ let settled = false;
1894
+ const finish = (error) => {
1895
+ if (settled) return;
1896
+ settled = true;
1897
+ clearTimeout(timer);
1817
1898
  signal.removeEventListener("abort", abort);
1818
1899
  track.removeEventListener("ended", ended);
1819
- if (result instanceof XmaxError) {
1820
- if (result.code !== "CANCELLED" /* cancelled */) XmaxLogger.media.warning(() => result.message);
1821
- reject(result);
1900
+ if (error) {
1901
+ reject(error);
1822
1902
  } 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
1903
  resolve();
1833
1904
  }
1834
1905
  };
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"));
1906
+ const abort = () => finish(new XmaxError("CANCELLED" /* cancelled */, "Camera warmup cancelled"));
1907
+ const ended = () => finish(new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera track ended before warmup completed"));
1837
1908
  signal.addEventListener("abort", abort, { once: true });
1838
1909
  track.addEventListener("ended", ended, { once: true });
1839
1910
  if (signal.aborted) {
@@ -1844,70 +1915,7 @@ function waitForValidCameraFrame(track, signal) {
1844
1915
  ended();
1845
1916
  return;
1846
1917
  }
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
- }
1918
+ timer = setTimeout(() => finish(), CAMERA_WARMUP_MS);
1911
1919
  });
1912
1920
  }
1913
1921
 
@@ -1937,7 +1945,7 @@ var PermissionManager = class {
1937
1945
  * @param deniedCode 权限被拒绝时抛出的错误码。
1938
1946
  */
1939
1947
  async ensurePermission(name, deniedCode) {
1940
- if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _85 => _85.mediaDevices, 'optionalAccess', _86 => _86.getUserMedia])) {
1948
+ if (typeof navigator === "undefined" || !_optionalChain([navigator, 'access', _97 => _97.mediaDevices, 'optionalAccess', _98 => _98.getUserMedia])) {
1941
1949
  throw new XmaxError(
1942
1950
  deniedCode,
1943
1951
  "This browser does not support media capture (getUserMedia)"
@@ -1945,7 +1953,7 @@ var PermissionManager = class {
1945
1953
  }
1946
1954
  try {
1947
1955
  const permissions = navigator.permissions;
1948
- if (!_optionalChain([permissions, 'optionalAccess', _87 => _87.query])) {
1956
+ if (!_optionalChain([permissions, 'optionalAccess', _99 => _99.query])) {
1949
1957
  return;
1950
1958
  }
1951
1959
  const status = await permissions.query({
@@ -2125,10 +2133,10 @@ var _CameraController = class _CameraController {
2125
2133
  */
2126
2134
  __publicField(this, "storedUseMicrophone", false);
2127
2135
  __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]), () => ( (() => {
2136
+ this.permissionManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _100 => _100.permissionManager]), () => ( new PermissionManager()));
2137
+ this.rtcManager = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _101 => _101.rtcManager]), () => ( new RtcManager()));
2138
+ this.mediaService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _102 => _102.mediaService]), () => ( new MediaService()));
2139
+ this.errorListener = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _103 => _103.errorListener]), () => ( (() => {
2132
2140
  })));
2133
2141
  }
2134
2142
  /**
@@ -2144,23 +2152,23 @@ var _CameraController = class _CameraController {
2144
2152
  return this.activeTrack !== void 0 && this.storedUseMicrophone;
2145
2153
  }
2146
2154
  /**
2147
- * 当前活动相机流是否启用发布前亮度检测;没有活动流时为 false。
2155
+ * 当前活动相机流是否启用发布前预热等待;没有活动流时为 false。
2148
2156
  */
2149
2157
  get isFrameValidationEnabled() {
2150
2158
  return this.activeTrack !== void 0 && this.storedFrameValidationEnabled;
2151
2159
  }
2152
2160
  /**
2153
- * 等待当前轨道首张合格帧或超时放行;禁用时跳过,取消或轨道被替换时拒绝。
2161
+ * 等待当前轨道预热完成(固定 200ms);禁用时跳过,取消或轨道被替换时拒绝。
2154
2162
  */
2155
2163
  async waitForValidCameraFrame(signal) {
2156
2164
  const track = this.activeTrack;
2157
- const mediaTrack = _optionalChain([track, 'optionalAccess', _92 => _92.mediaStreamTrack]);
2165
+ const mediaTrack = _optionalChain([track, 'optionalAccess', _104 => _104.mediaStreamTrack]);
2158
2166
  if (!mediaTrack) throw new XmaxError("MEDIA_ERROR" /* mediaError */, "Camera capture is not running");
2159
2167
  if (signal.aborted) throw new XmaxError("CANCELLED" /* cancelled */, "Camera frame validation cancelled");
2160
2168
  if (!this.isFrameValidationEnabled) return;
2161
2169
  await waitForValidCameraFrame(mediaTrack, signal);
2162
2170
  if (this.activeTrack !== track || track.mediaStreamTrack !== mediaTrack) {
2163
- throw new XmaxError("CANCELLED" /* cancelled */, "Camera track changed during exposure check");
2171
+ throw new XmaxError("CANCELLED" /* cancelled */, "Camera track changed during warmup");
2164
2172
  }
2165
2173
  }
2166
2174
  /**
@@ -2179,7 +2187,7 @@ var _CameraController = class _CameraController {
2179
2187
  * @param options.videoFormat 期望的输出尺寸、帧率和编码配置;尺寸按模型规则调整。
2180
2188
  * @param options.position 首次启动时使用的摄像头位置。
2181
2189
  * @param options.useMicrophone 是否申请麦克风权限并允许实时连接时启动音频采集。
2182
- * @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
2190
+ * @param options.enableFrameValidation 是否在发布前等待相机预热(固定 200ms),默认 true;false 跳过等待。
2183
2191
  * @returns 包含本地相机视频轨道的媒体流。
2184
2192
  * @throws 已有活动相机流、格式无效、权限不足或采集启动失败时抛出错误。
2185
2193
  */
@@ -2254,7 +2262,7 @@ var _CameraController = class _CameraController {
2254
2262
  */
2255
2263
  async switchCamera() {
2256
2264
  const track = this.activeTrack;
2257
- const position = _optionalChain([track, 'optionalAccess', _93 => _93.position]);
2265
+ const position = _optionalChain([track, 'optionalAccess', _105 => _105.position]);
2258
2266
  if (!track || !position) {
2259
2267
  throw new XmaxError(
2260
2268
  "RTC_ERROR" /* rtcError */,
@@ -2272,7 +2280,7 @@ var _CameraController = class _CameraController {
2272
2280
  }
2273
2281
  this.previewStream.addTrack(mediaTrack);
2274
2282
  }
2275
- _optionalChain([this, 'access', _94 => _94.previewMirrorApplier, 'optionalCall', _95 => _95(nextPosition === "front" /* front */)]);
2283
+ _optionalChain([this, 'access', _106 => _106.previewMirrorApplier, 'optionalCall', _107 => _107(nextPosition === "front" /* front */)]);
2276
2284
  this.observeMediaTrack(track, mediaTrack);
2277
2285
  return new RealtimeMediaStream({ id: "stream0" /* local */, videoTrack: track });
2278
2286
  }
@@ -2528,12 +2536,13 @@ var RealtimeSessionService = class _RealtimeSessionService {
2528
2536
  this.heartbeatVersion += 1;
2529
2537
  }
2530
2538
  /**
2531
- * 关闭指定实时会话。
2539
+ * 关闭指定实时会话;请求带 keepalive,页面卸载后仍能发出。
2532
2540
  */
2533
2541
  async closeSession(sessionID) {
2534
2542
  try {
2535
2543
  await this.apiService.delete(
2536
- `/session/${sessionID}`
2544
+ `/session/${sessionID}`,
2545
+ { keepalive: true }
2537
2546
  );
2538
2547
  } catch (error) {
2539
2548
  throw XmaxError.from(error);
@@ -2554,7 +2563,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2554
2563
  return;
2555
2564
  }
2556
2565
  _RealtimeSessionService.ensureSessionActive(session);
2557
- _optionalChain([handlers, 'access', _96 => _96.onRefresh, 'optionalCall', _97 => _97(session)]);
2566
+ _optionalChain([handlers, 'access', _108 => _108.onRefresh, 'optionalCall', _109 => _109(session)]);
2558
2567
  } catch (error) {
2559
2568
  if (this.heartbeatVersion !== version) {
2560
2569
  return;
@@ -2647,7 +2656,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2647
2656
  if (typeof modelExtra === "string") {
2648
2657
  try {
2649
2658
  return JSON.parse(modelExtra);
2650
- } catch (e10) {
2659
+ } catch (e12) {
2651
2660
  return void 0;
2652
2661
  }
2653
2662
  }
@@ -2660,7 +2669,7 @@ var RealtimeSessionService = class _RealtimeSessionService {
2660
2669
  * 归一化可选字符串:去除首尾空白后为空时返回 `undefined`。
2661
2670
  */
2662
2671
  static nonEmpty(value) {
2663
- const normalized = _optionalChain([value, 'optionalAccess', _98 => _98.trim, 'call', _99 => _99()]);
2672
+ const normalized = _optionalChain([value, 'optionalAccess', _110 => _110.trim, 'call', _111 => _111()]);
2664
2673
  return normalized || void 0;
2665
2674
  }
2666
2675
  };
@@ -2732,7 +2741,7 @@ var RealtimeState = class {
2732
2741
  * @param other 待比较的状态。
2733
2742
  */
2734
2743
  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]);
2744
+ return this.connectionState === other.connectionState && this.sessionID === other.sessionID && this.taskID === other.taskID && _optionalChain([this, 'access', _112 => _112.reason, 'optionalAccess', _113 => _113.kind]) === _optionalChain([other, 'access', _114 => _114.reason, 'optionalAccess', _115 => _115.kind]);
2736
2745
  }
2737
2746
  };
2738
2747
 
@@ -2901,7 +2910,7 @@ var RoomEvent = class _RoomEvent {
2901
2910
  static encode(payload) {
2902
2911
  try {
2903
2912
  return JSON.stringify({ ...payload, runtime: RuntimeInfo.toJSON() });
2904
- } catch (e11) {
2913
+ } catch (e13) {
2905
2914
  throw new XmaxError(
2906
2915
  "INTERNAL_ERROR" /* internalError */,
2907
2916
  "Failed to encode RTC room event"
@@ -3023,7 +3032,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3023
3032
  let parsed;
3024
3033
  try {
3025
3034
  parsed = JSON.parse(message);
3026
- } catch (e12) {
3035
+ } catch (e14) {
3027
3036
  XmaxLogger.room.warning(() => "\u623F\u95F4\u6D88\u606F\u4E0D\u662F\u5408\u6CD5 JSON (Room Message Is Not Valid JSON)");
3028
3037
  return void 0;
3029
3038
  }
@@ -3054,7 +3063,7 @@ var _RoomMessageCodec = class _RoomMessageCodec {
3054
3063
  const joined = parts.join("");
3055
3064
  try {
3056
3065
  return JSON.parse(joined);
3057
- } catch (e13) {
3066
+ } catch (e15) {
3058
3067
  XmaxLogger.room.warning(
3059
3068
  () => `\u623F\u95F4\u6D88\u606F\u7EC4\u5305\u540E\u4E0D\u662F\u5408\u6CD5 JSON (Reassembled Message Is Not Valid JSON)
3060
3069
  \u2514\u2500 eventId: ${chunk.eventId}`
@@ -3352,7 +3361,7 @@ var RoomController = class {
3352
3361
  if (targetUserID && ownUserID && targetUserID !== ownUserID) {
3353
3362
  return;
3354
3363
  }
3355
- _optionalChain([this, 'access', _104 => _104.listener, 'optionalAccess', _105 => _105.onRoomMessage, 'call', _106 => _106(senderUserID, message)]);
3364
+ _optionalChain([this, 'access', _116 => _116.listener, 'optionalAccess', _117 => _117.onRoomMessage, 'call', _118 => _118(senderUserID, message)]);
3356
3365
  }
3357
3366
  /**
3358
3367
  * 发送房间信令并输出日志。
@@ -3597,18 +3606,18 @@ var StreamController = class {
3597
3606
  this.rtcManager.setEventListener({
3598
3607
  onNetworkStatistics: (statistics) => {
3599
3608
  if (this.state.localVideoPublished) {
3600
- _optionalChain([this, 'access', _107 => _107.networkStatisticsListener, 'optionalCall', _108 => _108(statistics)]);
3609
+ _optionalChain([this, 'access', _119 => _119.networkStatisticsListener, 'optionalCall', _120 => _120(statistics)]);
3601
3610
  }
3602
3611
  },
3603
3612
  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(
3613
+ const userID = _optionalChain([this, 'access', _121 => _121.state, 'access', _122 => _122.activeRemoteStream, 'optionalAccess', _123 => _123.userID]);
3614
+ _optionalChain([this, 'access', _124 => _124.remoteVideoStatisticsListener, 'optionalCall', _125 => _125(
3606
3615
  userID ? statistics.find((item) => item.userID === userID) : void 0
3607
3616
  )]);
3608
3617
  },
3609
3618
  onLocalVideoStatistics: (statistics) => {
3610
3619
  if (this.state.localVideoPublished) {
3611
- _optionalChain([this, 'access', _114 => _114.localVideoStatisticsListener, 'optionalCall', _115 => _115(statistics)]);
3620
+ _optionalChain([this, 'access', _126 => _126.localVideoStatisticsListener, 'optionalCall', _127 => _127(statistics)]);
3612
3621
  }
3613
3622
  },
3614
3623
  onCustomMessageReceived: (senderUserID, message) => {
@@ -3680,7 +3689,7 @@ var StreamController = class {
3680
3689
  () => `RTC \u623F\u95F4\u5DF2\u914D\u7F6E (RTC Room Configured)
3681
3690
  \u2514\u2500 roomID: ${connection.roomID}, botID: ${_nullishCoalesce(connection.botID, () => ( "(\u672A\u8BBE\u7F6E)"))}`
3682
3691
  );
3683
- await _optionalChain([beforePublish, 'optionalCall', _116 => _116()]);
3692
+ await _optionalChain([beforePublish, 'optionalCall', _128 => _128()]);
3684
3693
  ensureActive();
3685
3694
  await this.publishLocalStream(includeLocalAudio);
3686
3695
  }
@@ -3751,7 +3760,7 @@ var StreamController = class {
3751
3760
  this.rejectGenerationStart(taskID, mapped);
3752
3761
  try {
3753
3762
  this.roomController.stopGeneration(taskID);
3754
- } catch (e14) {
3763
+ } catch (e16) {
3755
3764
  }
3756
3765
  throw mapped;
3757
3766
  }
@@ -3840,7 +3849,7 @@ var StreamController = class {
3840
3849
  */
3841
3850
  configureRoom(options) {
3842
3851
  const roomID = options.roomID.trim();
3843
- const botID = _nullishCoalesce(_optionalChain([options, 'access', _117 => _117.botID, 'optionalAccess', _118 => _118.trim, 'call', _119 => _119()]), () => ( ""));
3852
+ const botID = _nullishCoalesce(_optionalChain([options, 'access', _129 => _129.botID, 'optionalAccess', _130 => _130.trim, 'call', _131 => _131()]), () => ( ""));
3844
3853
  if (!roomID) {
3845
3854
  throw new XmaxError(
3846
3855
  "INVALID_CONFIGURATION" /* invalidConfiguration */,
@@ -3917,7 +3926,7 @@ var StreamController = class {
3917
3926
  return;
3918
3927
  }
3919
3928
  this.state.subscribedRemoteUserIDs.delete(trimmedUserID);
3920
- if (_optionalChain([this, 'access', _120 => _120.state, 'access', _121 => _121.activeRemoteStream, 'optionalAccess', _122 => _122.userID]) === trimmedUserID) {
3929
+ if (_optionalChain([this, 'access', _132 => _132.state, 'access', _133 => _133.activeRemoteStream, 'optionalAccess', _134 => _134.userID]) === trimmedUserID) {
3921
3930
  await this.unsubscribeRemoteAudio(trimmedUserID);
3922
3931
  this.state.activeRemoteStream = void 0;
3923
3932
  this.clearRemoteStream();
@@ -3993,14 +4002,23 @@ var StreamController = class {
3993
4002
  }
3994
4003
  }
3995
4004
  /**
3996
- * 订阅远端音频:先应用当前音量再订阅。
4005
+ * 订阅远端音频并按平台能力应用当前音量。
4006
+ *
4007
+ * 支持音量控制的平台先设音量再订阅,避免以默认音量短暂出声;
4008
+ * iOS 上音量降级为播放/静音二值控制,必须先完成订阅(恢复播放)
4009
+ * 再按需静音,否则订阅的恢复播放会覆盖音量 0 的静音状态。
3997
4010
  */
3998
4011
  async subscribeRemoteAudio(userID) {
3999
4012
  if (this.state.subscribedRemoteAudioUserIDs.has(userID)) {
4000
4013
  return;
4001
4014
  }
4002
- this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
4003
- await this.rtcManager.subscribeRemoteAudio(userID, true);
4015
+ if (this.rtcManager.supportsRemoteAudioVolumeControl) {
4016
+ this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
4017
+ await this.rtcManager.subscribeRemoteAudio(userID, true);
4018
+ } else {
4019
+ await this.rtcManager.subscribeRemoteAudio(userID, true);
4020
+ this.rtcManager.setRemoteAudioVolume(this.remoteAudioVolumePercentage, userID);
4021
+ }
4004
4022
  this.state.subscribedRemoteAudioUserIDs.add(userID);
4005
4023
  }
4006
4024
  /**
@@ -4077,7 +4095,7 @@ var StreamController = class {
4077
4095
  */
4078
4096
  resolveGenerationStart(taskID) {
4079
4097
  const waiter = this.state.generationWaiter;
4080
- if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _123 => _123.taskID]) !== taskID) {
4098
+ if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _135 => _135.taskID]) !== taskID) {
4081
4099
  return;
4082
4100
  }
4083
4101
  this.state.generationWaiter = void 0;
@@ -4089,7 +4107,7 @@ var StreamController = class {
4089
4107
  */
4090
4108
  rejectGenerationStart(taskID, error) {
4091
4109
  const waiter = this.state.generationWaiter;
4092
- if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _124 => _124.taskID]) !== taskID) {
4110
+ if (this.state.generationTaskID !== taskID || _optionalChain([waiter, 'optionalAccess', _136 => _136.taskID]) !== taskID) {
4093
4111
  return;
4094
4112
  }
4095
4113
  this.state.generationWaiter = void 0;
@@ -4100,7 +4118,7 @@ var StreamController = class {
4100
4118
  * 通知渲染层清理远端生成流;失败仅记录日志。
4101
4119
  */
4102
4120
  clearRemoteStream() {
4103
- _optionalChain([this, 'access', _125 => _125.remoteVideoStatisticsListener, 'optionalCall', _126 => _126(void 0)]);
4121
+ _optionalChain([this, 'access', _137 => _137.remoteVideoStatisticsListener, 'optionalCall', _138 => _138(void 0)]);
4104
4122
  try {
4105
4123
  this.remoteStreamListener(null);
4106
4124
  } catch (error) {
@@ -4371,7 +4389,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4371
4389
  }
4372
4390
  throw _RealtimeCoordinator.cancelledError();
4373
4391
  }
4374
- const terminationTask = await _asyncNullishCoalesce(await _asyncNullishCoalesce(operation.terminationTask, async () => ( _optionalChain([this, 'access', _127 => _127.termination, 'optionalAccess', _128 => _128.task]))), async () => ( await this.requestTermination(
4392
+ const terminationTask = await _asyncNullishCoalesce(await _asyncNullishCoalesce(operation.terminationTask, async () => ( _optionalChain([this, 'access', _139 => _139.termination, 'optionalAccess', _140 => _140.task]))), async () => ( await this.requestTermination(
4375
4393
  _nullishCoalesce(operation.lease.failureScope, () => ( 0)) /* connection */,
4376
4394
  void 0,
4377
4395
  RealtimeReason.normal,
@@ -4500,7 +4518,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4500
4518
  * 结束指定操作,释放操作准入场。
4501
4519
  */
4502
4520
  finish(operation) {
4503
- if (_optionalChain([this, 'access', _129 => _129.activeOperation, 'optionalAccess', _130 => _130.id]) === operation.id) {
4521
+ if (_optionalChain([this, 'access', _141 => _141.activeOperation, 'optionalAccess', _142 => _142.id]) === operation.id) {
4504
4522
  this.activeOperation = void 0;
4505
4523
  }
4506
4524
  }
@@ -4512,7 +4530,7 @@ var RealtimeCoordinator = class _RealtimeCoordinator {
4512
4530
  return;
4513
4531
  }
4514
4532
  this.state = nextState;
4515
- _optionalChain([this, 'access', _131 => _131.stateListener, 'optionalCall', _132 => _132(nextState)]);
4533
+ _optionalChain([this, 'access', _143 => _143.stateListener, 'optionalCall', _144 => _144(nextState)]);
4516
4534
  }
4517
4535
  /**
4518
4536
  * 构造统一的操作取消错误。
@@ -4588,17 +4606,25 @@ var RealtimeLaunchTimer = class {
4588
4606
  this.notify();
4589
4607
  return complete;
4590
4608
  }
4609
+ /**
4610
+ * 开始等待用户授权系统媒体权限,返回授权决定时调用的记录函数。
4611
+ * 仅在系统弹出授权请求时使用;已授权或未弹窗不记录该阶段。
4612
+ */
4613
+ startPermission() {
4614
+ return this.measure("permissionMs", performance.now());
4615
+ }
4591
4616
  /**
4592
4617
  * 开始会话与进房计时,返回连接阶段完成时调用的记录函数。
4618
+ * 相机预热等待计入连接耗时。
4593
4619
  */
4594
4620
  startConnection() {
4595
4621
  return this.measure("connectionMs", performance.now());
4596
4622
  }
4597
4623
  /**
4598
- * 开始亮度检测计时;合格帧到达或超时放行时调用返回的记录函数。
4624
+ * 开始发布本地流计时,返回发布完成时调用的记录函数。
4599
4625
  */
4600
- startFrameValidation() {
4601
- return this.measure("frameValidationMs", performance.now());
4626
+ startPublish() {
4627
+ return this.measure("publishMs", performance.now());
4602
4628
  }
4603
4629
  /**
4604
4630
  * 开始等待远端显示首帧;完成回调同时记录首帧耗时和实际总耗时。
@@ -4636,9 +4662,9 @@ var RealtimeLaunchTimer = class {
4636
4662
  */
4637
4663
  notify() {
4638
4664
  try {
4639
- void Promise.resolve(_optionalChain([this, 'access', _133 => _133.listener, 'optionalCall', _134 => _134(this.snapshot)])).catch(() => {
4665
+ void Promise.resolve(_optionalChain([this, 'access', _145 => _145.listener, 'optionalCall', _146 => _146(this.snapshot)])).catch(() => {
4640
4666
  });
4641
- } catch (e15) {
4667
+ } catch (e17) {
4642
4668
  }
4643
4669
  }
4644
4670
  };
@@ -4664,7 +4690,7 @@ var XmaxRealtimeConnectionManager = class {
4664
4690
  * 当前活动会话标识;尚未连接或清理完成后为空。
4665
4691
  */
4666
4692
  get currentSessionID() {
4667
- return _optionalChain([this, 'access', _135 => _135.activeSession, 'optionalAccess', _136 => _136.id]);
4693
+ return _optionalChain([this, 'access', _147 => _147.activeSession, 'optionalAccess', _148 => _148.id]);
4668
4694
  }
4669
4695
  /**
4670
4696
  * 未配置 API 服务时在连接操作开始前报错,不影响本地预览。
@@ -4674,7 +4700,8 @@ var XmaxRealtimeConnectionManager = class {
4674
4700
  }
4675
4701
  /**
4676
4702
  * 创建会话、配置编码、进房并发布本地媒体,启动心跳后返回远端占位流。
4677
- * 进房结束即记录连接耗时;可选 beforePublish 检查只阻塞发布,不计入连接耗时。
4703
+ * 进房结束即记录连接耗时;可选 beforePublish 检查阻塞发布,计入连接耗时;
4704
+ * 发布本地流单独计时。
4678
4705
  * @throws 会话、进房、发布失败或操作租约已失效时抛出错误,由上层统一清理。
4679
4706
  */
4680
4707
  async connect(options) {
@@ -4701,15 +4728,17 @@ var XmaxRealtimeConnectionManager = class {
4701
4728
  }
4702
4729
  await streamController.setVideoEncoderConfig(videoFormat);
4703
4730
  options.ensureCurrent();
4731
+ let completePublish;
4704
4732
  await streamController.connect(connection, options.includeLocalAudio, () => {
4705
4733
  options.ensureCurrent();
4706
4734
  }, async () => {
4707
4735
  options.ensureCurrent();
4736
+ await _optionalChain([options, 'access', _149 => _149.beforePublish, 'optionalCall', _150 => _150()]);
4708
4737
  completeConnection();
4709
- options.ensureCurrent();
4710
- await _optionalChain([options, 'access', _137 => _137.beforePublish, 'optionalCall', _138 => _138()]);
4738
+ completePublish = this.dependencies.timing.startPublish();
4711
4739
  });
4712
4740
  options.ensureCurrent();
4741
+ _optionalChain([completePublish, 'optionalCall', _151 => _151()]);
4713
4742
  options.onPublished();
4714
4743
  sessionService.startHeartbeat(session.id, {
4715
4744
  onFailure: (sessionID, error) => {
@@ -4732,26 +4761,26 @@ var XmaxRealtimeConnectionManager = class {
4732
4761
  * 更新已绑定远端视图的插帧配置;传 undefined 停用插帧。
4733
4762
  */
4734
4763
  setFrameInterpolation(options) {
4735
- _optionalChain([this, 'access', _139 => _139.remoteTarget, 'optionalAccess', _140 => _140.setFrameInterpolation, 'optionalCall', _141 => _141(options)]);
4764
+ _optionalChain([this, 'access', _152 => _152.remoteTarget, 'optionalAccess', _153 => _153.setFrameInterpolation, 'optionalCall', _154 => _154(options)]);
4736
4765
  }
4737
4766
  /**
4738
4767
  * 清空远端视图的媒体流,保留轨道与视图绑定以供统一清理。
4739
4768
  */
4740
4769
  clearRemoteMedia() {
4741
- _optionalChain([this, 'access', _142 => _142.remoteTarget, 'optionalAccess', _143 => _143.setMediaStream, 'call', _144 => _144(null)]);
4770
+ _optionalChain([this, 'access', _155 => _155.remoteTarget, 'optionalAccess', _156 => _156.setMediaStream, 'call', _157 => _157(null)]);
4742
4771
  }
4743
4772
  /**
4744
4773
  * 停止会话心跳;未配置会话服务时不执行操作。
4745
4774
  */
4746
4775
  stopHeartbeat() {
4747
- _optionalChain([this, 'access', _145 => _145.dependencies, 'access', _146 => _146.sessionService, 'optionalAccess', _147 => _147.stopHeartbeat, 'call', _148 => _148()]);
4776
+ _optionalChain([this, 'access', _158 => _158.dependencies, 'access', _159 => _159.sessionService, 'optionalAccess', _160 => _160.stopHeartbeat, 'call', _161 => _161()]);
4748
4777
  }
4749
4778
  /**
4750
4779
  * 等待接收轨解除静音;超时只警告,取消必须立即退出并移除监听。
4751
4780
  */
4752
4781
  async waitUntilRemoteTrackReady(timeoutMs, signal) {
4753
4782
  if (signal.aborted) throw RealtimeCoordinator.cancelledError();
4754
- const mediaTrack = _optionalChain([this, 'access', _149 => _149.activeRemoteTrack, 'optionalAccess', _150 => _150.mediaStreamTrack]);
4783
+ const mediaTrack = _optionalChain([this, 'access', _162 => _162.activeRemoteTrack, 'optionalAccess', _163 => _163.mediaStreamTrack]);
4755
4784
  if (!mediaTrack || !mediaTrack.muted) return;
4756
4785
  await new Promise((resolve, reject) => {
4757
4786
  const cleanup = () => {
@@ -4788,7 +4817,7 @@ var XmaxRealtimeConnectionManager = class {
4788
4817
  if (track) {
4789
4818
  track.mediaStreamTrack = void 0;
4790
4819
  }
4791
- _optionalChain([this, 'access', _151 => _151.remoteTarget, 'optionalAccess', _152 => _152.setMediaStream, 'call', _153 => _153(null)]);
4820
+ _optionalChain([this, 'access', _164 => _164.remoteTarget, 'optionalAccess', _165 => _165.setMediaStream, 'call', _166 => _166(null)]);
4792
4821
  return;
4793
4822
  }
4794
4823
  if (!track) {
@@ -4798,7 +4827,7 @@ var XmaxRealtimeConnectionManager = class {
4798
4827
  );
4799
4828
  }
4800
4829
  track.mediaStreamTrack = binding.videoTrack;
4801
- _optionalChain([this, 'access', _154 => _154.remoteTarget, 'optionalAccess', _155 => _155.setMediaStream, 'call', _156 => _156(new MediaStream([binding.videoTrack]))]);
4830
+ _optionalChain([this, 'access', _167 => _167.remoteTarget, 'optionalAccess', _168 => _168.setMediaStream, 'call', _169 => _169(new MediaStream([binding.videoTrack]))]);
4802
4831
  }
4803
4832
  /**
4804
4833
  * 为远端轨道注册渲染绑定:attach 时挂流占位,媒体轨到达后送入画面。
@@ -4811,7 +4840,7 @@ var XmaxRealtimeConnectionManager = class {
4811
4840
  }
4812
4841
  },
4813
4842
  attachHandler: (view) => {
4814
- _optionalChain([this, 'access', _157 => _157.remoteTarget, 'optionalAccess', _158 => _158.setFrameInterpolation, 'optionalCall', _159 => _159(void 0)]);
4843
+ _optionalChain([this, 'access', _170 => _170.remoteTarget, 'optionalAccess', _171 => _171.setFrameInterpolation, 'optionalCall', _172 => _172(void 0)]);
4815
4844
  view.isMirrored = this.dependencies.isMirrored();
4816
4845
  this.remoteTarget = view;
4817
4846
  this.dependencies.onRenderAttached();
@@ -4823,7 +4852,7 @@ var XmaxRealtimeConnectionManager = class {
4823
4852
  this.dependencies.onRenderDetached();
4824
4853
  this.remoteTarget = void 0;
4825
4854
  }
4826
- _optionalChain([view, 'access', _160 => _160.setFrameInterpolation, 'optionalCall', _161 => _161(void 0)]);
4855
+ _optionalChain([view, 'access', _173 => _173.setFrameInterpolation, 'optionalCall', _174 => _174(void 0)]);
4827
4856
  view.setMediaStream(null);
4828
4857
  }
4829
4858
  });
@@ -4883,7 +4912,7 @@ var XmaxRealtimeConnectionManager = class {
4883
4912
  * 尽力清理全部连接资源,即使退房失败也关闭服务端会话。
4884
4913
  */
4885
4914
  async disconnect() {
4886
- const sessionID = _optionalChain([this, 'access', _162 => _162.activeSession, 'optionalAccess', _163 => _163.id]);
4915
+ const sessionID = _optionalChain([this, 'access', _175 => _175.activeSession, 'optionalAccess', _176 => _176.id]);
4887
4916
  this.stopHeartbeat();
4888
4917
  this.clearRemoteMedia();
4889
4918
  try {
@@ -5016,7 +5045,7 @@ var XmaxRealtimeGenerationManager = class _XmaxRealtimeGenerationManager {
5016
5045
  try {
5017
5046
  await completion;
5018
5047
  } finally {
5019
- if (_optionalChain([this, 'access', _164 => _164.pendingStop, 'optionalAccess', _165 => _165.completion]) === completion) this.pendingStop = void 0;
5048
+ if (_optionalChain([this, 'access', _177 => _177.pendingStop, 'optionalAccess', _178 => _178.completion]) === completion) this.pendingStop = void 0;
5020
5049
  }
5021
5050
  }
5022
5051
  /**
@@ -5098,6 +5127,10 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5098
5127
  */
5099
5128
  __publicField(this, "launchTimer", new RealtimeLaunchTimer());
5100
5129
  __publicField(this, "remoteFrameDisplayHandler");
5130
+ /**
5131
+ * 媒体权限弹窗观察
5132
+ */
5133
+ __publicField(this, "permissionWatchDispose");
5101
5134
  /**
5102
5135
  * 本地、远端和网络统计快照及观察者
5103
5136
  */
@@ -5122,18 +5155,18 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5122
5155
  this.options = options;
5123
5156
  this.mediaService = new MediaService(options.model);
5124
5157
  this.interpolationRequested = options.isFrameInterpolationEnabled;
5125
- this.supportsInterpolation = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _166 => _166.supportsFrameInterpolation]), () => ( (async (size) => await frameInterpolationAdapter(size) !== null)));
5158
+ this.supportsInterpolation = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _179 => _179.supportsFrameInterpolation]), () => ( (async (size) => await frameInterpolationAdapter(size) !== null)));
5126
5159
  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({
5160
+ const rtcManager = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _180 => _180.rtcManager]), () => ( new RtcManager()));
5161
+ this.cameraController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _181 => _181.cameraController]), () => ( new CameraController({
5129
5162
  rtcManager,
5130
5163
  mediaService: this.mediaService,
5131
5164
  errorListener: (error) => {
5132
5165
  void this.errorHandler.report(error);
5133
5166
  }
5134
5167
  })));
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({
5168
+ const sessionService = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _182 => _182.sessionService]), () => ( (_optionalChain([dependencies, 'optionalAccess', _183 => _183.apiService]) ? new RealtimeSessionService({ apiService: dependencies.apiService }) : void 0)));
5169
+ this.streamController = _nullishCoalesce(_optionalChain([dependencies, 'optionalAccess', _184 => _184.streamController]), () => ( new StreamController({
5137
5170
  rtcManager,
5138
5171
  errorListener: (error) => {
5139
5172
  void this.errorHandler.report(error);
@@ -5147,12 +5180,12 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5147
5180
  sessionService,
5148
5181
  streamController: this.streamController,
5149
5182
  timing: this.launchTimer,
5150
- isMirrored: () => _optionalChain([this, 'access', _172 => _172.cameraController, 'access', _173 => _173.currentTrack, 'optionalAccess', _174 => _174.position]) === "front" /* front */,
5183
+ isMirrored: () => _optionalChain([this, 'access', _185 => _185.cameraController, 'access', _186 => _186.currentTrack, 'optionalAccess', _187 => _187.position]) === "front" /* front */,
5151
5184
  remoteAudioVolume: () => this.storedRemoteAudioVolume,
5152
5185
  onHeartbeatFailure: (sessionID, error) => {
5153
5186
  void this.handleHeartbeatFailure(sessionID, error);
5154
5187
  },
5155
- onFrameDisplayed: () => _optionalChain([this, 'access', _175 => _175.remoteFrameDisplayHandler, 'optionalCall', _176 => _176()]),
5188
+ onFrameDisplayed: () => _optionalChain([this, 'access', _188 => _188.remoteFrameDisplayHandler, 'optionalCall', _189 => _189()]),
5156
5189
  onRenderAttached: () => this.applyRemoteInterpolation(),
5157
5190
  onRenderDetached: () => {
5158
5191
  this.interpolationRevision++;
@@ -5199,7 +5232,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5199
5232
  */
5200
5233
  async setFrameInterpolationEnabled(enabled) {
5201
5234
  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]);
5235
+ const format = _optionalChain([this, 'access', _190 => _190.cameraController, 'access', _191 => _191.currentTrack, 'optionalAccess', _192 => _192.videoFormat]);
5203
5236
  const size = format && enabled ? this.mediaService.resolveFrameInterpolationSize(format) : void 0;
5204
5237
  if (enabled && !await this.checkInterpolationSupport(size, token)) {
5205
5238
  token.ensureCurrent();
@@ -5325,9 +5358,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5325
5358
  */
5326
5359
  notifyRemoteVideoStatistics() {
5327
5360
  try {
5328
- void Promise.resolve(_optionalChain([this, 'access', _180 => _180.remoteVideoStatisticsListener, 'optionalCall', _181 => _181(this.remoteVideoStatistics)])).catch(() => {
5361
+ void Promise.resolve(_optionalChain([this, 'access', _193 => _193.remoteVideoStatisticsListener, 'optionalCall', _194 => _194(this.remoteVideoStatistics)])).catch(() => {
5329
5362
  });
5330
- } catch (e16) {
5363
+ } catch (e18) {
5331
5364
  }
5332
5365
  }
5333
5366
  /**
@@ -5342,9 +5375,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5342
5375
  */
5343
5376
  notifyNetworkStatistics() {
5344
5377
  try {
5345
- void Promise.resolve(_optionalChain([this, 'access', _182 => _182.networkStatisticsListener, 'optionalCall', _183 => _183(this.networkStatistics)])).catch(() => {
5378
+ void Promise.resolve(_optionalChain([this, 'access', _195 => _195.networkStatisticsListener, 'optionalCall', _196 => _196(this.networkStatistics)])).catch(() => {
5346
5379
  });
5347
- } catch (e17) {
5380
+ } catch (e19) {
5348
5381
  }
5349
5382
  }
5350
5383
  /**
@@ -5362,9 +5395,9 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5362
5395
  */
5363
5396
  notifyLocalVideoStatistics() {
5364
5397
  try {
5365
- void Promise.resolve(_optionalChain([this, 'access', _184 => _184.localVideoStatisticsListener, 'optionalCall', _185 => _185(this.localVideoStatistics)])).catch(() => {
5398
+ void Promise.resolve(_optionalChain([this, 'access', _197 => _197.localVideoStatisticsListener, 'optionalCall', _198 => _198(this.localVideoStatistics)])).catch(() => {
5366
5399
  });
5367
- } catch (e18) {
5400
+ } catch (e20) {
5368
5401
  }
5369
5402
  }
5370
5403
  /**
@@ -5416,7 +5449,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5416
5449
  *
5417
5450
  * 将返回的轨道绑定到预览视图;收到有效帧且视图已绑定后进入 `ready`。
5418
5451
  *
5419
- * @param options.enableFrameValidation 是否在发布前检测帧亮度,默认 true;false 跳过检测及其等待。
5452
+ * @param options.enableFrameValidation 是否在发布前等待相机预热(固定 200ms),默认 true;false 跳过等待。
5420
5453
  * @returns 包含本地相机视频轨道的媒体流。
5421
5454
  * @throws 模型不支持相机输入、配置无效、权限或采集启动失败时抛出错误。
5422
5455
  */
@@ -5434,11 +5467,20 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5434
5467
  token.ensureCurrent();
5435
5468
  const completeCamera = this.launchTimer.startCamera();
5436
5469
  this.clearVideoStatistics();
5470
+ this.disposePermissionWatch();
5471
+ const permissionWatch = await watchMediaPermissionPrompt(_nullishCoalesce(_optionalChain([options, 'optionalAccess', _199 => _199.useMicrophone]), () => ( false)));
5472
+ if (permissionWatch) {
5473
+ const completePermission = this.launchTimer.startPermission();
5474
+ void permissionWatch.decided.then(() => completePermission()).catch(() => {
5475
+ });
5476
+ this.permissionWatchDispose = permissionWatch.dispose;
5477
+ }
5437
5478
  let stream;
5438
5479
  try {
5439
5480
  token.ensureCurrent();
5440
5481
  stream = await this.cameraController.createLocalCameraStream(options);
5441
5482
  } catch (error) {
5483
+ this.disposePermissionWatch();
5442
5484
  this.launchTimer.cancel();
5443
5485
  throw error;
5444
5486
  }
@@ -5451,6 +5493,13 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5451
5493
  }
5452
5494
  );
5453
5495
  }
5496
+ /**
5497
+ * 移除媒体权限弹窗观察;授权决定已记录或不再需要观察时调用。
5498
+ */
5499
+ disposePermissionWatch() {
5500
+ _optionalChain([this, 'access', _200 => _200.permissionWatchDispose, 'optionalCall', _201 => _201()]);
5501
+ this.permissionWatchDispose = void 0;
5502
+ }
5454
5503
  /**
5455
5504
  * 停止本地相机流并释放本地预览与 RTC 资源。
5456
5505
  */
@@ -5461,6 +5510,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5461
5510
  async (token) => {
5462
5511
  this.launchTimer.cancel();
5463
5512
  this.clearVideoStatistics();
5513
+ this.disposePermissionWatch();
5464
5514
  await this.cameraController.stopLocalCameraStream();
5465
5515
  token.ensureCurrent();
5466
5516
  await this.coordinator.commit(
@@ -5494,8 +5544,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5494
5544
  * 使用当前 Manager 创建的本地流建立实时连接。
5495
5545
  *
5496
5546
  * 创建实时会话、加入 RTC 房间并发布本地流,成功后启动会话心跳。
5497
- * 启用帧检测时,发布前并行检查相机亮度,首张合格帧即放行;2 秒内未通过检查则
5498
- * 记录警告并按当前画面继续发布,不因环境较暗而阻断连接。
5547
+ * 启用帧检测时,发布前并行等待相机预热(固定 200ms),避免把黑帧推给 RTC。
5499
5548
  * 返回的远端媒体流在生成开始后承载远端生成画面。
5500
5549
  *
5501
5550
  * @param localStream 由 `createLocalCameraStream` 创建的本地媒体流。
@@ -5604,17 +5653,15 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5604
5653
  new RealtimeState({ connectionState: "Connecting" /* connecting */ }),
5605
5654
  token
5606
5655
  );
5607
- const exposureController = new AbortController();
5608
- const abortExposure = () => exposureController.abort();
5609
- token.signal.addEventListener("abort", abortExposure, { once: true });
5610
- if (token.signal.aborted) abortExposure();
5656
+ const warmupController = new AbortController();
5657
+ const abortWarmup = () => warmupController.abort();
5658
+ token.signal.addEventListener("abort", abortWarmup, { once: true });
5659
+ if (token.signal.aborted) abortWarmup();
5611
5660
  try {
5612
5661
  let beforePublish;
5613
5662
  if (this.cameraController.isFrameValidationEnabled) {
5614
- const completeFrameValidation = this.launchTimer.startFrameValidation();
5615
- const cameraReady = this.cameraController.waitForValidCameraFrame(exposureController.signal).then(() => {
5663
+ const cameraReady = this.cameraController.waitForValidCameraFrame(warmupController.signal).then(() => {
5616
5664
  token.ensureCurrent();
5617
- completeFrameValidation();
5618
5665
  });
5619
5666
  void cameraReady.catch(() => {
5620
5667
  });
@@ -5639,8 +5686,8 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5639
5686
  );
5640
5687
  return remote;
5641
5688
  } finally {
5642
- token.signal.removeEventListener("abort", abortExposure);
5643
- abortExposure();
5689
+ token.signal.removeEventListener("abort", abortWarmup);
5690
+ abortWarmup();
5644
5691
  }
5645
5692
  }
5646
5693
  /**
@@ -5697,6 +5744,7 @@ var XmaxRealtimeManager = class _XmaxRealtimeManager {
5697
5744
  await this.generationManager.reset(taskID);
5698
5745
  const sessionID = await this.connectionManager.disconnect();
5699
5746
  if (scope === 1 /* all */) {
5747
+ this.disposePermissionWatch();
5700
5748
  try {
5701
5749
  await this.cameraController.stopLocalCameraStream();
5702
5750
  } catch (error) {
@@ -5856,11 +5904,11 @@ var RealtimeConfiguration = class {
5856
5904
  */
5857
5905
  __publicField(this, "frameInterpolation");
5858
5906
  this.model = init.model;
5859
- const targetFrameRate = _nullishCoalesce(_optionalChain([init, 'access', _186 => _186.frameInterpolation, 'optionalAccess', _187 => _187.targetFrameRate]), () => ( 60));
5907
+ const targetFrameRate = _nullishCoalesce(_optionalChain([init, 'access', _202 => _202.frameInterpolation, 'optionalAccess', _203 => _203.targetFrameRate]), () => ( 60));
5860
5908
  if (!Number.isFinite(targetFrameRate) || targetFrameRate < 1 || targetFrameRate > 60) {
5861
5909
  throw new XmaxError("INVALID_CONFIGURATION" /* invalidConfiguration */, "Interpolation targetFrameRate must be between 1 and 60");
5862
5910
  }
5863
- this.isFrameInterpolationEnabled = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'access', _188 => _188.frameInterpolation, 'optionalAccess', _189 => _189.enabled]), () => ( init.isFrameInterpolationEnabled)), () => ( false));
5911
+ this.isFrameInterpolationEnabled = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'access', _204 => _204.frameInterpolation, 'optionalAccess', _205 => _205.enabled]), () => ( init.isFrameInterpolationEnabled)), () => ( false));
5864
5912
  this.frameInterpolation = Object.freeze({ enabled: this.isFrameInterpolationEnabled, targetFrameRate });
5865
5913
  }
5866
5914
  };
@@ -5890,7 +5938,7 @@ var RealtimeContext = class {
5890
5938
  */
5891
5939
  __publicField(this, "referencePath");
5892
5940
  this.prompt = init.prompt.trim();
5893
- const normalizedReferencePath = _optionalChain([init, 'access', _190 => _190.referencePath, 'optionalAccess', _191 => _191.trim, 'call', _192 => _192()]);
5941
+ const normalizedReferencePath = _optionalChain([init, 'access', _206 => _206.referencePath, 'optionalAccess', _207 => _207.trim, 'call', _208 => _208()]);
5894
5942
  this.referencePath = normalizedReferencePath || void 0;
5895
5943
  }
5896
5944
  };
@@ -8017,11 +8065,11 @@ var _FrameInterpolationManager = class _FrameInterpolationManager {
8017
8065
  if (this.destroyed) return;
8018
8066
  this.destroyed = true;
8019
8067
  this.device.removeEventListener("uncapturederror", this.onGPUError);
8020
- _optionalChain([this, 'access', _193 => _193.runtime, 'optionalAccess', _194 => _194.destroy, 'call', _195 => _195()]);
8068
+ _optionalChain([this, 'access', _209 => _209.runtime, 'optionalAccess', _210 => _210.destroy, 'call', _211 => _211()]);
8021
8069
  this.runtime = void 0;
8022
8070
  this.textures.forEach((texture) => texture.destroy());
8023
8071
  this.textures = [];
8024
- _optionalChain([this, 'access', _196 => _196.context, 'optionalAccess', _197 => _197.unconfigure, 'call', _198 => _198()]);
8072
+ _optionalChain([this, 'access', _212 => _212.context, 'optionalAccess', _213 => _213.unconfigure, 'call', _214 => _214()]);
8025
8073
  this.device.destroy();
8026
8074
  }
8027
8075
  };
@@ -8200,7 +8248,7 @@ var RemoteVideoFramePipeline = class {
8200
8248
  setActive(active) {
8201
8249
  if (active === this.active) return;
8202
8250
  this.active = active;
8203
- _optionalChain([this, 'access', _199 => _199.options, 'access', _200 => _200.onActiveChange, 'optionalCall', _201 => _201(active)]);
8251
+ _optionalChain([this, 'access', _215 => _215.options, 'access', _216 => _216.onActiveChange, 'optionalCall', _217 => _217(active)]);
8204
8252
  }
8205
8253
  resetTimeline() {
8206
8254
  this.generation++;
@@ -8231,7 +8279,7 @@ var RemoteVideoFramePipeline = class {
8231
8279
  this.callback = void 0;
8232
8280
  clearTimeout(this.initializationTimer);
8233
8281
  this.resetTimeline();
8234
- _optionalChain([this, 'access', _202 => _202.processor, 'optionalAccess', _203 => _203.destroy, 'call', _204 => _204()]);
8282
+ _optionalChain([this, 'access', _218 => _218.processor, 'optionalAccess', _219 => _219.destroy, 'call', _220 => _220()]);
8235
8283
  this.processor = void 0;
8236
8284
  }
8237
8285
  };
@@ -8289,7 +8337,7 @@ var XmaxVideoView = class {
8289
8337
  * DOM 挂载状态
8290
8338
  */
8291
8339
  __publicField(this, "detached", false);
8292
- this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _205 => _205.videoContentMode]), () => ( "fill")) /* fill */;
8340
+ this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _221 => _221.videoContentMode]), () => ( "fill")) /* fill */;
8293
8341
  this.element = document.createElement("div");
8294
8342
  this.element.style.position = "relative";
8295
8343
  this.element.style.overflow = "hidden";
@@ -8436,8 +8484,8 @@ var XmaxVideoView = class {
8436
8484
  stopInterpolation() {
8437
8485
  const pipeline = this.interpolationPipeline;
8438
8486
  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()]);
8487
+ _optionalChain([pipeline, 'optionalAccess', _222 => _222.stop, 'call', _223 => _223()]);
8488
+ _optionalChain([this, 'access', _224 => _224.interpolationCanvas, 'optionalAccess', _225 => _225.remove, 'call', _226 => _226()]);
8441
8489
  this.interpolationCanvas = void 0;
8442
8490
  }
8443
8491
  /**
@@ -8463,7 +8511,7 @@ var XmaxVideoView = class {
8463
8511
  detachCurrentTrack() {
8464
8512
  const track = this.currentTrack;
8465
8513
  if (track) {
8466
- _optionalChain([VideoRenderRegistry, 'access', _211 => _211.binding, 'call', _212 => _212(track), 'optionalAccess', _213 => _213.detachHandler, 'call', _214 => _214(this)]);
8514
+ _optionalChain([VideoRenderRegistry, 'access', _227 => _227.binding, 'call', _228 => _228(track), 'optionalAccess', _229 => _229.detachHandler, 'call', _230 => _230(this)]);
8467
8515
  }
8468
8516
  this.setFrameInterpolation(void 0);
8469
8517
  this.cancelFrameDisplayNotification();
@@ -8483,8 +8531,8 @@ var XmaxVideoView = class {
8483
8531
  }
8484
8532
  this.hasNotifiedFrameDisplay = true;
8485
8533
  this.cancelFrameDisplayNotification();
8486
- _optionalChain([binding, 'optionalAccess', _215 => _215.frameDisplayHandler, 'optionalCall', _216 => _216()]);
8487
- _optionalChain([this, 'access', _217 => _217.frameDisplayHandler, 'optionalCall', _218 => _218()]);
8534
+ _optionalChain([binding, 'optionalAccess', _231 => _231.frameDisplayHandler, 'optionalCall', _232 => _232()]);
8535
+ _optionalChain([this, 'access', _233 => _233.frameDisplayHandler, 'optionalCall', _234 => _234()]);
8488
8536
  };
8489
8537
  if (typeof this.videoElement.requestVideoFrameCallback === "function") {
8490
8538
  this.videoFrameCallbackID = this.videoElement.requestVideoFrameCallback(notify);
@@ -8554,9 +8602,9 @@ var XmaxRealtimeVideoView = class {
8554
8602
  __publicField(this, "currentLocalTrack");
8555
8603
  __publicField(this, "currentRemoteTrack");
8556
8604
  __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]);
8605
+ this.contentMode = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _235 => _235.videoContentMode]), () => ( "fill")) /* fill */;
8606
+ this.currentLocalTrack = _optionalChain([options, 'optionalAccess', _236 => _236.localTrack]);
8607
+ this.currentRemoteTrack = _optionalChain([options, 'optionalAccess', _237 => _237.remoteTrack]);
8560
8608
  this.element = document.createElement("div");
8561
8609
  this.element.style.position = "relative";
8562
8610
  this.element.style.overflow = "hidden";
@@ -8715,4 +8763,4 @@ var XmaxRealtimeVideoView = class {
8715
8763
 
8716
8764
 
8717
8765
  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
8766
+ //# sourceMappingURL=chunk-JWAAS6P4.cjs.map