@xmaxai/sdk 1.0.1 → 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.
Files changed (38) hide show
  1. package/CHANGELOG.md +11 -2
  2. package/README.md +19 -5
  3. package/dist/{chunk-LCPC56BK.js → chunk-B5EAQTBY.js} +497 -136
  4. package/dist/chunk-B5EAQTBY.js.map +1 -0
  5. package/dist/{chunk-V4NR6CPP.js → chunk-ZMLV7YQP.js} +24 -184
  6. package/dist/chunk-ZMLV7YQP.js.map +1 -0
  7. package/dist/index.cjs +53 -273
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +380 -8
  10. package/dist/index.d.ts +380 -8
  11. package/dist/index.js +1004 -9
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/index.cjs +30 -271
  14. package/dist/react/index.cjs.map +1 -1
  15. package/dist/react/index.d.cts +1 -3
  16. package/dist/react/index.d.ts +1 -3
  17. package/dist/react/index.js +6 -26
  18. package/dist/react/index.js.map +1 -1
  19. package/dist/{rtc-manager-gYDkADGw.d.ts → rtc-manager-Bk2lIJ0d.d.cts} +7 -17
  20. package/dist/{rtc-manager-gYDkADGw.d.cts → rtc-manager-Bk2lIJ0d.d.ts} +7 -17
  21. package/dist/video-file-stream-OVPBV7IX.js +3 -0
  22. package/dist/{video-file-stream-K7GQMIJ5.js.map → video-file-stream-OVPBV7IX.js.map} +1 -1
  23. package/package.json +1 -6
  24. package/dist/chunk-4YHBCQ2C.js +0 -432
  25. package/dist/chunk-4YHBCQ2C.js.map +0 -1
  26. package/dist/chunk-LCPC56BK.js.map +0 -1
  27. package/dist/chunk-V4NR6CPP.js.map +0 -1
  28. package/dist/chunk-VLJZ56PT.js +0 -997
  29. package/dist/chunk-VLJZ56PT.js.map +0 -1
  30. package/dist/experimental/index.cjs +0 -3897
  31. package/dist/experimental/index.cjs.map +0 -1
  32. package/dist/experimental/index.d.cts +0 -2
  33. package/dist/experimental/index.d.ts +0 -2
  34. package/dist/experimental/index.js +0 -5
  35. package/dist/experimental/index.js.map +0 -1
  36. package/dist/index-Cw8C8LbO.d.ts +0 -367
  37. package/dist/index-Dg1ElHAj.d.cts +0 -367
  38. package/dist/video-file-stream-K7GQMIJ5.js +0 -4
package/dist/index.cjs CHANGED
@@ -36,6 +36,8 @@ var init_en_US = __esm({
36
36
  "errors.sessionJoinInfoIncomplete": "session does not contain complete RTC join info",
37
37
  "errors.sessionUidMissing": "session does not contain sessionUid",
38
38
  "errors.refImageMissing": "missing reference image",
39
+ "errors.imageCheckMissingUrl": "image check response missing url",
40
+ "errors.imageCheckUnsafe": "image did not pass safety check",
39
41
  "errors.captureStreamUnsupported": "captureStream is not supported in this browser",
40
42
  "errors.videoTrackMissing": "failed to create video track from file",
41
43
  "errors.imageTrackMissing": "failed to create video track from image file",
@@ -97,6 +99,8 @@ var init_zh_CN = __esm({
97
99
  "errors.sessionJoinInfoIncomplete": "session \u7F3A\u5C11\u5B8C\u6574\u7684 RTC \u8FDB\u623F\u4FE1\u606F",
98
100
  "errors.sessionUidMissing": "session \u7F3A\u5C11 sessionUid",
99
101
  "errors.refImageMissing": "\u7F3A\u5C11\u53C2\u8003\u56FE",
102
+ "errors.imageCheckMissingUrl": "\u56FE\u7247\u68C0\u67E5\u54CD\u5E94\u7F3A\u5C11 url",
103
+ "errors.imageCheckUnsafe": "\u56FE\u7247\u672A\u901A\u8FC7\u5B89\u5168\u68C0\u67E5",
100
104
  "errors.captureStreamUnsupported": "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301 captureStream",
101
105
  "errors.videoTrackMissing": "\u672A\u80FD\u4ECE\u89C6\u9891\u6587\u4EF6\u521B\u5EFA video track",
102
106
  "errors.imageTrackMissing": "\u672A\u80FD\u4ECE\u56FE\u7247\u6587\u4EF6\u521B\u5EFA video track",
@@ -441,13 +445,12 @@ function resolveRtcPublishFps(options = {}) {
441
445
  function resolveRtcPublishFrameIntervalS(options) {
442
446
  return 1 / resolveRtcPublishFps(options);
443
447
  }
444
- exports.MOBILE_PUBLISH_MAX_WIDTH_PX = void 0; exports.RTC_PUBLISH_FPS_WEB = void 0; exports.RTC_PUBLISH_FPS_MOBILE = void 0; exports.RTC_PUBLISH_FPS = void 0; exports.RTC_PUBLISH_FRAME_INTERVAL_S = void 0; exports.RTC_PUBLISH_FRAME_INTERVAL_MS = void 0; exports.MEDIA_TIME_EPSILON_S = void 0; exports.MEDIA_TIME_LOOP_JUMP_S = void 0;
448
+ exports.MOBILE_PUBLISH_MAX_WIDTH_PX = void 0; exports.RTC_PUBLISH_FPS_WEB = void 0; exports.RTC_PUBLISH_FPS_MOBILE = void 0; exports.RTC_PUBLISH_FRAME_INTERVAL_S = void 0; exports.RTC_PUBLISH_FRAME_INTERVAL_MS = void 0; exports.MEDIA_TIME_EPSILON_S = void 0; exports.MEDIA_TIME_LOOP_JUMP_S = void 0;
445
449
  var init_publish_fps = __esm({
446
450
  "src/rtc/publish-fps.ts"() {
447
451
  exports.MOBILE_PUBLISH_MAX_WIDTH_PX = 768;
448
452
  exports.RTC_PUBLISH_FPS_WEB = 30;
449
453
  exports.RTC_PUBLISH_FPS_MOBILE = 24;
450
- exports.RTC_PUBLISH_FPS = exports.RTC_PUBLISH_FPS_WEB;
451
454
  exports.RTC_PUBLISH_FRAME_INTERVAL_S = 1 / exports.RTC_PUBLISH_FPS_WEB;
452
455
  exports.RTC_PUBLISH_FRAME_INTERVAL_MS = 1e3 / exports.RTC_PUBLISH_FPS_WEB;
453
456
  exports.MEDIA_TIME_EPSILON_S = 1e-3;
@@ -627,17 +630,6 @@ function shouldSampleAtMediaTime(lastMediaTimeS, mediaTimeS) {
627
630
  }
628
631
  return mediaTimeS > lastMediaTimeS + exports.MEDIA_TIME_EPSILON_S;
629
632
  }
630
- function sampleCanvasFingerprint(ctx, width, height) {
631
- const points = [
632
- [0, 0],
633
- [Math.max(0, (width >> 1) - 1), Math.max(0, (height >> 1) - 1)],
634
- [Math.max(0, width - 1), Math.max(0, height - 1)]
635
- ];
636
- return points.map(([x, y]) => {
637
- const data = ctx.getImageData(x, y, 1, 1).data;
638
- return `${data[0]},${data[1]},${data[2]}`;
639
- }).join("|");
640
- }
641
633
  async function applyPublishTrackConstraints(track, width, height) {
642
634
  if (typeof track.applyConstraints !== "function") {
643
635
  return;
@@ -710,12 +702,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
710
702
  let lastDrawMediaTimeS = -1;
711
703
  let videoFrameCallbackId = null;
712
704
  let rafId = null;
713
- const logPublishFrames = options.logPublishFrames ?? false;
714
- let publishFrameIndex = 0;
715
- let lastPublishWallMs = 0;
716
- let lastPublishMediaTimeS = -1;
717
- let publishFramesInWindow = 0;
718
- let publishWindowStartMs = 0;
719
705
  const drawFrame = () => {
720
706
  if (destroyed) {
721
707
  return;
@@ -729,52 +715,13 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
729
715
  sourceHeight
730
716
  );
731
717
  };
732
- const logPublishedFrame = (mediaTimeS, presentedFrames) => {
733
- if (!logPublishFrames) {
734
- return;
735
- }
736
- publishFrameIndex += 1;
737
- const wallNowMs = performance.now();
738
- const wallDeltaMs = lastPublishWallMs > 0 ? wallNowMs - lastPublishWallMs : 0;
739
- const mediaDeltaS = lastPublishMediaTimeS >= 0 ? mediaTimeS - lastPublishMediaTimeS : 0;
740
- lastPublishWallMs = wallNowMs;
741
- lastPublishMediaTimeS = mediaTimeS;
742
- publishFramesInWindow += 1;
743
- if (publishWindowStartMs === 0) {
744
- publishWindowStartMs = wallNowMs;
745
- }
746
- const fingerprint = sampleCanvasFingerprint(ctx, outputWidth, outputHeight);
747
- console.warn("[upload-video \u2192 publishFrame]", {
748
- frameIndex: publishFrameIndex,
749
- mediaTimeS: Number(mediaTimeS.toFixed(4)),
750
- mediaDeltaS: Number(mediaDeltaS.toFixed(4)),
751
- wallDeltaMs: Number(wallDeltaMs.toFixed(1)),
752
- effectiveFps: wallDeltaMs > 0 ? Number((1e3 / wallDeltaMs).toFixed(1)) : null,
753
- presentedFrames,
754
- size: `${outputWidth}\xD7${outputHeight}`,
755
- fingerprint
756
- });
757
- const windowElapsedMs = wallNowMs - publishWindowStartMs;
758
- if (windowElapsedMs >= 1e3) {
759
- console.warn("[upload-video \u2192 publishFps]", {
760
- framesInLast1s: publishFramesInWindow,
761
- avgFps: Number((publishFramesInWindow / (windowElapsedMs / 1e3)).toFixed(2)),
762
- videoCurrentTime: Number(videoEl.currentTime.toFixed(4)),
763
- videoPaused: videoEl.paused,
764
- videoReadyState: videoEl.readyState
765
- });
766
- publishFramesInWindow = 0;
767
- publishWindowStartMs = wallNowMs;
768
- }
769
- };
770
- const onPlaybackSample = (mediaTimeS, presentedFrames) => {
718
+ const onPlaybackSample = (mediaTimeS) => {
771
719
  if (destroyed || !shouldSampleAtMediaTime(lastDrawMediaTimeS, mediaTimeS)) {
772
720
  return;
773
721
  }
774
722
  lastDrawMediaTimeS = mediaTimeS;
775
723
  lastAdvanceAt = Date.now();
776
724
  drawFrame();
777
- logPublishedFrame(mediaTimeS, presentedFrames);
778
725
  };
779
726
  const schedulePlaybackWatch = () => {
780
727
  if (destroyed) {
@@ -783,7 +730,7 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
783
730
  const video = videoEl;
784
731
  if (video.requestVideoFrameCallback) {
785
732
  videoFrameCallbackId = video.requestVideoFrameCallback((_now, metadata) => {
786
- onPlaybackSample(metadata.mediaTime, metadata.presentedFrames);
733
+ onPlaybackSample(metadata.mediaTime);
787
734
  schedulePlaybackWatch();
788
735
  });
789
736
  return;
@@ -794,7 +741,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
794
741
  });
795
742
  };
796
743
  drawFrame();
797
- logPublishedFrame(videoEl.currentTime);
798
744
  schedulePlaybackWatch();
799
745
  const ensurePlaying = () => {
800
746
  if (destroyed) {
@@ -816,18 +762,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
816
762
  videoEl.addEventListener("ended", ensurePlaying);
817
763
  videoEl.addEventListener("stalled", ensurePlaying);
818
764
  videoEl.addEventListener("waiting", ensurePlaying);
819
- if (logPublishFrames) {
820
- const logPlaybackEvent = (eventName) => {
821
- console.warn("[upload-video \u2192 playbackEvent]", {
822
- event: eventName,
823
- currentTime: Number(videoEl.currentTime.toFixed(4)),
824
- readyState: videoEl.readyState,
825
- paused: videoEl.paused
826
- });
827
- };
828
- videoEl.addEventListener("waiting", () => logPlaybackEvent("waiting"));
829
- videoEl.addEventListener("stalled", () => logPlaybackEvent("stalled"));
830
- }
831
765
  const healthIntervalMs = options.healthIntervalMs ?? 3e3;
832
766
  let healthTimer = null;
833
767
  if (options.onHealthReport) {
@@ -1009,148 +943,13 @@ var init_video_file_stream = __esm({
1009
943
  // src/sdk/client.ts
1010
944
  init_i18n();
1011
945
 
1012
- // src/shared/browser-toast.ts
1013
- var TOAST_ROOT_ID = "Xmax-sdk-toast-root";
1014
- var TOAST_DURATION_MS = 3e3;
1015
- var toastSeed = 0;
1016
- var timeoutMap = /* @__PURE__ */ new Map();
1017
- var elementMap = /* @__PURE__ */ new Map();
1018
- function ensureToastRoot() {
1019
- if (typeof document === "undefined") {
1020
- return null;
1021
- }
1022
- let root = document.getElementById(TOAST_ROOT_ID);
1023
- if (root) {
1024
- return root;
1025
- }
1026
- root = document.createElement("div");
1027
- root.id = TOAST_ROOT_ID;
1028
- root.setAttribute("aria-live", "polite");
1029
- Object.assign(root.style, {
1030
- position: "fixed",
1031
- left: "50%",
1032
- top: "24px",
1033
- transform: "translateX(-50%)",
1034
- zIndex: "3000",
1035
- display: "flex",
1036
- flexDirection: "column",
1037
- alignItems: "center",
1038
- gap: "12px",
1039
- pointerEvents: "none"
1040
- });
1041
- document.body.appendChild(root);
1042
- return root;
1043
- }
1044
- function createIcon(type) {
1045
- const icon = document.createElement("span");
1046
- icon.setAttribute("aria-hidden", "true");
1047
- icon.innerHTML = type === "success" ? '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M10 3L4.5 8.5 2 6" stroke="#4ade80" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none"><circle cx="6" cy="6" r="5" stroke="#f87171" stroke-width="1.5"/><path d="M4 4l4 4M8 4L4 8" stroke="#f87171" stroke-width="1.5" stroke-linecap="round"/></svg>';
1048
- Object.assign(icon.style, {
1049
- display: "inline-flex",
1050
- width: "12px",
1051
- height: "12px",
1052
- flexShrink: "0"
1053
- });
1054
- return icon;
1055
- }
1056
- function clearToastTimer(id) {
1057
- const timer = timeoutMap.get(id);
1058
- if (timer) {
1059
- window.clearTimeout(timer);
1060
- timeoutMap.delete(id);
1061
- }
1062
- }
1063
- function destroyToast(id) {
1064
- if (!id) {
1065
- timeoutMap.forEach((timer) => window.clearTimeout(timer));
1066
- timeoutMap.clear();
1067
- elementMap.forEach((element2) => element2.remove());
1068
- elementMap.clear();
1069
- const root2 = typeof document !== "undefined" ? document.getElementById(TOAST_ROOT_ID) : null;
1070
- root2?.remove();
1071
- return;
1072
- }
1073
- clearToastTimer(id);
1074
- const element = elementMap.get(id);
1075
- element?.remove();
1076
- elementMap.delete(id);
1077
- const root = typeof document !== "undefined" ? document.getElementById(TOAST_ROOT_ID) : null;
1078
- if (root && !root.childElementCount) {
1079
- root.remove();
1080
- }
1081
- }
1082
- function showToast(type, message, options) {
1083
- const root = ensureToastRoot();
1084
- if (!root) {
1085
- return "";
1086
- }
1087
- const id = options?.key || `toast-${Date.now()}-${++toastSeed}`;
1088
- clearToastTimer(id);
1089
- elementMap.get(id)?.remove();
1090
- const isMobile = typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches;
1091
- const textSize = isMobile ? "12px" : "14px";
1092
- const toast = document.createElement("div");
1093
- toast.setAttribute("role", "status");
1094
- Object.assign(toast.style, {
1095
- pointerEvents: "auto",
1096
- display: "flex",
1097
- alignItems: "center",
1098
- gap: "12px",
1099
- minWidth: "240px",
1100
- padding: "16px",
1101
- borderRadius: "12px",
1102
- border: "1px solid rgba(255, 255, 255, 0.20)",
1103
- background: "rgba(0, 0, 0, 0.80)",
1104
- boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
1105
- fontFamily: "Rubik, ui-sans-serif, system-ui, sans-serif",
1106
- fontSize: textSize,
1107
- fontWeight: "400",
1108
- lineHeight: "14px"
1109
- });
1110
- const content = document.createElement("span");
1111
- content.textContent = message;
1112
- Object.assign(content.style, {
1113
- paddingRight: "0px",
1114
- textAlign: "center",
1115
- color: "#ffffff",
1116
- fontFamily: "Rubik, ui-sans-serif, system-ui, sans-serif",
1117
- fontSize: textSize,
1118
- fontWeight: "400",
1119
- lineHeight: "14px"
1120
- });
1121
- toast.appendChild(createIcon(type));
1122
- toast.appendChild(content);
1123
- root.appendChild(toast);
1124
- elementMap.set(id, toast);
1125
- const timer = window.setTimeout(() => {
1126
- destroyToast(id);
1127
- }, options?.duration ?? TOAST_DURATION_MS);
1128
- timeoutMap.set(id, timer);
1129
- return id;
1130
- }
1131
- var browserToast = {
1132
- success(message, options) {
1133
- return showToast("success", message, options);
1134
- },
1135
- error(message, options) {
1136
- return showToast("error", message, options);
1137
- },
1138
- destroy(id) {
1139
- destroyToast(id);
1140
- }
1141
- };
1142
-
1143
946
  // src/shared/error-utils.ts
1144
947
  function notifyError(error, notifier, fallbackMessage) {
1145
948
  const resolved = error instanceof Error ? error : Object.assign(new Error(fallbackMessage), { cause: error });
1146
- if (!resolved.__XmaxNotified) {
949
+ if (!resolved.__XmaxNotified && notifier) {
1147
950
  resolved.__XmaxNotified = true;
1148
951
  const message = resolved.message || fallbackMessage;
1149
- if (notifier) {
1150
- notifier(message, error);
1151
- } else if (typeof window !== "undefined" && typeof document !== "undefined") {
1152
- browserToast.error(message);
1153
- }
952
+ notifier(message, error);
1154
953
  }
1155
954
  return resolved;
1156
955
  }
@@ -1222,7 +1021,7 @@ var XmaxOpenClient = class {
1222
1021
  }
1223
1022
  throw notifyError(error, this.onError, message);
1224
1023
  }
1225
- /** Stop heartbeat, abort in-flight API requests, and suppress error toasts during teardown. */
1024
+ /** Stop heartbeat, abort in-flight API requests, and suppress error notifications during teardown. */
1226
1025
  beginTeardown() {
1227
1026
  this.suppressErrorNotification = true;
1228
1027
  this.stopHeartbeat();
@@ -1382,6 +1181,23 @@ var XmaxOpenClient = class {
1382
1181
  async uploadVideo(file) {
1383
1182
  return this.uploadObject(file, "video/");
1384
1183
  }
1184
+ /**
1185
+ * Upload an image then run Playground-style `/cos/image/check`.
1186
+ * Returns the checked URL. Existing {@link uploadImage} is unchanged.
1187
+ */
1188
+ async uploadAndCheckImage(file) {
1189
+ const uploaded = await this.uploadImage(file);
1190
+ const data = await this.request("POST", "/cos/image/check", {
1191
+ url: uploaded.url
1192
+ });
1193
+ if (!data?.url) {
1194
+ this.throwError(this.i18n.t("errors.imageCheckMissingUrl"));
1195
+ }
1196
+ if (data.safe === false) {
1197
+ this.throwError(this.i18n.t("errors.imageCheckUnsafe"));
1198
+ }
1199
+ return data.url;
1200
+ }
1385
1201
  startHeartbeat(sessionUid, intervalMs = 1e4, onHeartbeat, onError) {
1386
1202
  this.stopHeartbeat();
1387
1203
  const heartbeat = async () => {
@@ -2079,45 +1895,6 @@ var _RtcManager = class _RtcManager {
2079
1895
  sentFrameRate: this.lastObservedSentFrameRate
2080
1896
  });
2081
1897
  }
2082
- /** @deprecated Use {@link ensurePublishedInputBeforeStart}. */
2083
- async waitForRtcOutboundBeforeStart(options) {
2084
- const timeoutMs = options?.timeoutMs ?? PUBLISH_READY_TIMEOUT_MS;
2085
- const publishAgeMs = this.publishStartedAtMs > 0 ? performance.now() - this.publishStartedAtMs : 0;
2086
- const localFramesPromise = publishAgeMs >= PUBLISH_WARM_SKIP_MS ? Promise.resolve({ ready: true, frameCount: 0, elapsedMs: 0 }) : this.waitForPublishedInputFrames({
2087
- minFrames: PUBLISH_START_MIN_FRAMES,
2088
- timeoutMs: PUBLISH_START_FRAME_TIMEOUT_MS
2089
- });
2090
- const [, rtcReady] = await Promise.all([
2091
- localFramesPromise,
2092
- this.waitForLocalPublishReady({ timeoutMs })
2093
- ]);
2094
- return rtcReady;
2095
- }
2096
- /** @deprecated Use {@link waitForRtcOutboundBeforeStart}. */
2097
- async waitForPublishedInputFramesBeforeStart() {
2098
- const waitOptions = this.resolveInputFrameWaitOptions();
2099
- const waitTasks = [
2100
- this.waitForLocalPublishReady({ timeoutMs: PUBLISH_READY_TIMEOUT_MS })
2101
- ];
2102
- if (waitOptions) {
2103
- waitTasks.push(
2104
- this.waitForMinPublishDuration(waitOptions.minPublishMs),
2105
- this.waitForPublishedInputFrames({
2106
- minFrames: waitOptions.minFrames,
2107
- timeoutMs: waitOptions.timeoutMs
2108
- })
2109
- );
2110
- }
2111
- const [rtcResult, ...rest] = await Promise.all(waitTasks);
2112
- const rtcSend = rtcResult;
2113
- const frameResult = waitOptions ? rest[1] : { ready: true, frameCount: 0, elapsedMs: 0 };
2114
- return {
2115
- ...frameResult,
2116
- rtcSendReady: rtcSend.ready,
2117
- rtcSendReason: rtcSend.reason,
2118
- rtcSendElapsedMs: rtcSend.elapsedSincePublishMs
2119
- };
2120
- }
2121
1898
  async waitForMinPublishDuration(minMs) {
2122
1899
  if (minMs <= 0 || this.publishStartedAtMs <= 0) {
2123
1900
  return;
@@ -3612,9 +3389,6 @@ function getFitContentRect(rect, targetSize, fitMode = "contain") {
3612
3389
  height
3613
3390
  };
3614
3391
  }
3615
- function getContentRect(rect, targetSize) {
3616
- return getFitContentRect(rect, targetSize, "contain");
3617
- }
3618
3392
  function findDragMediaElement(dragSurface) {
3619
3393
  const root = dragSurface.parentElement ?? dragSurface;
3620
3394
  if (typeof root.querySelector !== "function") {
@@ -4219,6 +3993,10 @@ var RealtimeSession = class _RealtimeSession {
4219
3993
  this.currentPrompt = "";
4220
3994
  this.currentGenerationModel = "";
4221
3995
  this.disconnected = false;
3996
+ /** True only after `connect()` bootstrap succeeds — gates `onDisconnect`. */
3997
+ this.sessionEstablished = false;
3998
+ this.disconnectNotified = false;
3999
+ this.pendingDisconnectReason = "client";
4222
4000
  this.remoteViewHost = null;
4223
4001
  this.dragController = null;
4224
4002
  this.dragTrackFitMode = "cover";
@@ -4232,6 +4010,7 @@ var RealtimeSession = class _RealtimeSession {
4232
4010
  this.rtc = rtc;
4233
4011
  this.reportError = reportError;
4234
4012
  this.onRemoteStream = options.onRemoteStream;
4013
+ this.onDisconnect = options.onDisconnect;
4235
4014
  this.onPublishPipelineWaitChange = clientOptions?.onPublishPipelineWaitChange;
4236
4015
  if (options.remoteElement) {
4237
4016
  this.remoteElement = options.remoteElement;
@@ -4260,7 +4039,7 @@ var RealtimeSession = class _RealtimeSession {
4260
4039
  }
4261
4040
  static async connect(stream, options, clientOptions) {
4262
4041
  const sdkI18n = createSDKI18n({});
4263
- const notifyUserError = clientOptions.onError;
4042
+ const notifyUserError = options.onError ?? clientOptions.onError;
4264
4043
  const reportError = (error, fallbackMessage) => {
4265
4044
  throw notifyError(error, notifyUserError, fallbackMessage ?? sdkI18n.t("errors.requestFailed"));
4266
4045
  };
@@ -4281,6 +4060,7 @@ var RealtimeSession = class _RealtimeSession {
4281
4060
  const session = new _RealtimeSession(client, rtc, reportError, options, clientOptions);
4282
4061
  try {
4283
4062
  await session.bootstrap(stream, options, clientOptions.heartbeatIntervalMs ?? 1e4, prewarmed);
4063
+ session.sessionEstablished = true;
4284
4064
  return session;
4285
4065
  } catch (error) {
4286
4066
  await session.safeDisconnect();
@@ -4306,11 +4086,11 @@ var RealtimeSession = class _RealtimeSession {
4306
4086
  heartbeatIntervalMs,
4307
4087
  (heartbeatSession) => {
4308
4088
  if (heartbeatSession.status !== ACTIVE_STATUS) {
4309
- void this.safeDisconnect();
4089
+ void this.safeDisconnect({ reason: "session_inactive" });
4310
4090
  }
4311
4091
  },
4312
4092
  () => {
4313
- void this.safeDisconnect();
4093
+ void this.safeDisconnect({ reason: "heartbeat_error" });
4314
4094
  }
4315
4095
  );
4316
4096
  const joinInfo = this.client.getRtcJoinInfo(createdSession);
@@ -4472,9 +4252,6 @@ var RealtimeSession = class _RealtimeSession {
4472
4252
  this.reportError(error);
4473
4253
  }
4474
4254
  }
4475
- async setMode(options) {
4476
- return this.restart(options);
4477
- }
4478
4255
  async refreshRemoteOutput(options) {
4479
4256
  if (!this.remoteVideoContainer) {
4480
4257
  return;
@@ -4548,7 +4325,7 @@ var RealtimeSession = class _RealtimeSession {
4548
4325
  return;
4549
4326
  }
4550
4327
  this.disconnected = true;
4551
- await this.safeDisconnect(options);
4328
+ await this.safeDisconnect({ ...options, reason: "client" });
4552
4329
  }
4553
4330
  getSessionUid() {
4554
4331
  return this.sessionUid;
@@ -4631,17 +4408,6 @@ var RealtimeSession = class _RealtimeSession {
4631
4408
  mirrored: false,
4632
4409
  enabled: true,
4633
4410
  onTracks: (tracks) => {
4634
- const media = this.remoteViewHost?.videoHost?.querySelector("video, canvas");
4635
- const mediaRect = media?.getBoundingClientRect();
4636
- const dragRect = dragHost.getBoundingClientRect();
4637
- console.log("[Xmax] sendTracks", {
4638
- taskUid: this.taskUid,
4639
- targetSize: this.sessionTargetSize,
4640
- fitMode: this.dragTrackFitMode,
4641
- tracks,
4642
- dragRect: dragRect ? { w: dragRect.width, h: dragRect.height } : null,
4643
- mediaRect: mediaRect ? { w: mediaRect.width, h: mediaRect.height, l: mediaRect.left, t: mediaRect.top } : null
4644
- });
4645
4411
  void this.sendTracks(tracks);
4646
4412
  }
4647
4413
  });
@@ -4690,7 +4456,20 @@ var RealtimeSession = class _RealtimeSession {
4690
4456
  this.remoteStreamNotifyTimer = null;
4691
4457
  }
4692
4458
  }
4459
+ emitDisconnect(reason) {
4460
+ if (!this.sessionEstablished || this.disconnectNotified) {
4461
+ return;
4462
+ }
4463
+ this.disconnectNotified = true;
4464
+ try {
4465
+ this.onDisconnect?.(reason);
4466
+ } catch {
4467
+ }
4468
+ }
4693
4469
  async safeDisconnect(options) {
4470
+ const reason = options?.reason ?? this.pendingDisconnectReason;
4471
+ this.pendingDisconnectReason = reason;
4472
+ this.disconnected = true;
4694
4473
  this.clearRemoteStreamNotifyTimer();
4695
4474
  this.lastNotifiedRemoteStream = null;
4696
4475
  const activeSessionUid = this.sessionUid;
@@ -4741,6 +4520,7 @@ var RealtimeSession = class _RealtimeSession {
4741
4520
  this.hiddenRemoteElement.remove();
4742
4521
  this.hiddenRemoteElement = null;
4743
4522
  }
4523
+ this.emitDisconnect(reason);
4744
4524
  }
4745
4525
  };
4746
4526
 
@@ -4780,7 +4560,8 @@ function createXmaxClient(options) {
4780
4560
  files: {
4781
4561
  uploadImage: (file) => httpClient.uploadImage(file),
4782
4562
  uploadVideo: (file) => httpClient.uploadVideo(file),
4783
- upload: (file) => httpClient.uploadImage(file)
4563
+ upload: (file) => httpClient.uploadImage(file),
4564
+ uploadAndCheckImage: (file) => httpClient.uploadAndCheckImage(file)
4784
4565
  },
4785
4566
  realtime: {
4786
4567
  prewarm: (options2) => RealtimePrewarmHandle.create(options2.model, resolved),
@@ -5080,7 +4861,6 @@ exports.createTracksRtcRoomEvent = createTracksRtcRoomEvent;
5080
4861
  exports.createVideoFileStream = createVideoFileStream;
5081
4862
  exports.createXmaxClient = createXmaxClient;
5082
4863
  exports.getCameraEnvironmentIssue = getCameraEnvironmentIssue;
5083
- exports.getContentRect = getContentRect;
5084
4864
  exports.getDefaultMessages = getDefaultMessages;
5085
4865
  exports.isCameraAccessError = isCameraAccessError;
5086
4866
  exports.isImageMediaFile = isImageMediaFile;