@xmaxai/sdk 1.0.0 → 1.0.2

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 +10 -2
  2. package/README.md +9 -5
  3. package/dist/{chunk-M5QA6LWS.js → chunk-F567ONBH.js} +32 -190
  4. package/dist/chunk-F567ONBH.js.map +1 -0
  5. package/dist/{chunk-PLU2YVAS.js → chunk-TFGYBN3Q.js} +493 -136
  6. package/dist/chunk-TFGYBN3Q.js.map +1 -0
  7. package/dist/index.cjs +76 -294
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +376 -10
  10. package/dist/index.d.ts +376 -10
  11. package/dist/index.js +1003 -9
  12. package/dist/index.js.map +1 -1
  13. package/dist/react/index.cjs +34 -277
  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.cts → rtc-manager-DnQoznIg.d.cts} +2 -17
  20. package/dist/{rtc-manager-gYDkADGw.d.ts → rtc-manager-DnQoznIg.d.ts} +2 -17
  21. package/dist/video-file-stream-4TAGKL2Q.js +3 -0
  22. package/dist/{video-file-stream-UXJ6TNIB.js.map → video-file-stream-4TAGKL2Q.js.map} +1 -1
  23. package/package.json +1 -6
  24. package/dist/chunk-7X7QCO3O.js +0 -992
  25. package/dist/chunk-7X7QCO3O.js.map +0 -1
  26. package/dist/chunk-EGNRAZ5U.js +0 -432
  27. package/dist/chunk-EGNRAZ5U.js.map +0 -1
  28. package/dist/chunk-M5QA6LWS.js.map +0 -1
  29. package/dist/chunk-PLU2YVAS.js.map +0 -1
  30. package/dist/experimental/index.cjs +0 -3873
  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-BEgtCLkJ.d.cts +0 -346
  37. package/dist/index-D9wwNTfP.d.ts +0 -346
  38. package/dist/video-file-stream-UXJ6TNIB.js +0 -4
package/dist/index.cjs CHANGED
@@ -441,13 +441,12 @@ function resolveRtcPublishFps(options = {}) {
441
441
  function resolveRtcPublishFrameIntervalS(options) {
442
442
  return 1 / resolveRtcPublishFps(options);
443
443
  }
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;
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_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
445
  var init_publish_fps = __esm({
446
446
  "src/rtc/publish-fps.ts"() {
447
447
  exports.MOBILE_PUBLISH_MAX_WIDTH_PX = 768;
448
448
  exports.RTC_PUBLISH_FPS_WEB = 30;
449
449
  exports.RTC_PUBLISH_FPS_MOBILE = 24;
450
- exports.RTC_PUBLISH_FPS = exports.RTC_PUBLISH_FPS_WEB;
451
450
  exports.RTC_PUBLISH_FRAME_INTERVAL_S = 1 / exports.RTC_PUBLISH_FPS_WEB;
452
451
  exports.RTC_PUBLISH_FRAME_INTERVAL_MS = 1e3 / exports.RTC_PUBLISH_FPS_WEB;
453
452
  exports.MEDIA_TIME_EPSILON_S = 1e-3;
@@ -627,17 +626,6 @@ function shouldSampleAtMediaTime(lastMediaTimeS, mediaTimeS) {
627
626
  }
628
627
  return mediaTimeS > lastMediaTimeS + exports.MEDIA_TIME_EPSILON_S;
629
628
  }
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
629
  async function applyPublishTrackConstraints(track, width, height) {
642
630
  if (typeof track.applyConstraints !== "function") {
643
631
  return;
@@ -710,12 +698,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
710
698
  let lastDrawMediaTimeS = -1;
711
699
  let videoFrameCallbackId = null;
712
700
  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
701
  const drawFrame = () => {
720
702
  if (destroyed) {
721
703
  return;
@@ -729,52 +711,13 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
729
711
  sourceHeight
730
712
  );
731
713
  };
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) => {
714
+ const onPlaybackSample = (mediaTimeS) => {
771
715
  if (destroyed || !shouldSampleAtMediaTime(lastDrawMediaTimeS, mediaTimeS)) {
772
716
  return;
773
717
  }
774
718
  lastDrawMediaTimeS = mediaTimeS;
775
719
  lastAdvanceAt = Date.now();
776
720
  drawFrame();
777
- logPublishedFrame(mediaTimeS, presentedFrames);
778
721
  };
779
722
  const schedulePlaybackWatch = () => {
780
723
  if (destroyed) {
@@ -783,7 +726,7 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
783
726
  const video = videoEl;
784
727
  if (video.requestVideoFrameCallback) {
785
728
  videoFrameCallbackId = video.requestVideoFrameCallback((_now, metadata) => {
786
- onPlaybackSample(metadata.mediaTime, metadata.presentedFrames);
729
+ onPlaybackSample(metadata.mediaTime);
787
730
  schedulePlaybackWatch();
788
731
  });
789
732
  return;
@@ -794,7 +737,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
794
737
  });
795
738
  };
796
739
  drawFrame();
797
- logPublishedFrame(videoEl.currentTime);
798
740
  schedulePlaybackWatch();
799
741
  const ensurePlaying = () => {
800
742
  if (destroyed) {
@@ -816,18 +758,6 @@ async function createVideoFileStream(fileOrUrl, options = {}) {
816
758
  videoEl.addEventListener("ended", ensurePlaying);
817
759
  videoEl.addEventListener("stalled", ensurePlaying);
818
760
  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
761
  const healthIntervalMs = options.healthIntervalMs ?? 3e3;
832
762
  let healthTimer = null;
833
763
  if (options.onHealthReport) {
@@ -1009,148 +939,13 @@ var init_video_file_stream = __esm({
1009
939
  // src/sdk/client.ts
1010
940
  init_i18n();
1011
941
 
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
942
  // src/shared/error-utils.ts
1144
943
  function notifyError(error, notifier, fallbackMessage) {
1145
944
  const resolved = error instanceof Error ? error : Object.assign(new Error(fallbackMessage), { cause: error });
1146
- if (!resolved.__XmaxNotified) {
945
+ if (!resolved.__XmaxNotified && notifier) {
1147
946
  resolved.__XmaxNotified = true;
1148
947
  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
- }
948
+ notifier(message, error);
1154
949
  }
1155
950
  return resolved;
1156
951
  }
@@ -1222,7 +1017,7 @@ var XmaxOpenClient = class {
1222
1017
  }
1223
1018
  throw notifyError(error, this.onError, message);
1224
1019
  }
1225
- /** Stop heartbeat, abort in-flight API requests, and suppress error toasts during teardown. */
1020
+ /** Stop heartbeat, abort in-flight API requests, and suppress error notifications during teardown. */
1226
1021
  beginTeardown() {
1227
1022
  this.suppressErrorNotification = true;
1228
1023
  this.stopHeartbeat();
@@ -1278,7 +1073,8 @@ var XmaxOpenClient = class {
1278
1073
  return p.data;
1279
1074
  }
1280
1075
  const mappedMessage = resolveApiErrorMessage(p.code, this.i18n.locale);
1281
- const errorMessage = mappedMessage ?? this.i18n.t("errors.requestRetry");
1076
+ const backendMessage = typeof p.message === "string" && p.message.trim() ? p.message.trim() : void 0;
1077
+ const errorMessage = mappedMessage ?? backendMessage ?? this.i18n.t("errors.requestRetry");
1282
1078
  throw new Error(errorMessage);
1283
1079
  } catch (error) {
1284
1080
  if (this.isAbortError(error)) {
@@ -1430,6 +1226,7 @@ function createStartRtcRoomEvent(input) {
1430
1226
  const resolvedPrompt = input.prompt?.trim() ?? "";
1431
1227
  const refImagePath = input.refImagePath?.trim() || input.refImage?.trim() || void 0;
1432
1228
  const useRefImage = !!refImagePath;
1229
+ const staticImagePath = input.staticImagePath?.trim() || void 0;
1433
1230
  return {
1434
1231
  event: "start",
1435
1232
  user_id: input.userId?.trim() || void 0,
@@ -1441,7 +1238,9 @@ function createStartRtcRoomEvent(input) {
1441
1238
  prompt: resolvedPrompt,
1442
1239
  ref_image_path: useRefImage ? refImagePath : void 0,
1443
1240
  ref_image: useRefImage ? refImagePath : void 0,
1444
- static_generate: input.staticGenerate === void 0 ? void 0 : input.staticGenerate,
1241
+ // Instant-only omit when false/undefined so non-instant starts never carry leftover flags.
1242
+ static_generate: input.staticGenerate === true ? true : void 0,
1243
+ static_image_path: staticImagePath,
1445
1244
  ...input.extraParams ?? {}
1446
1245
  }
1447
1246
  };
@@ -1727,7 +1526,7 @@ var _RtcManager = class _RtcManager {
1727
1526
  this.lastRemoteSei = null;
1728
1527
  this.lastRemoteSeiUserId = null;
1729
1528
  this.hasLoggedSeiSendFailure = false;
1730
- if (previousExpectedRemoteSei !== this.expectedRemoteSei && this.remoteVideoUserId) {
1529
+ if (previousExpectedRemoteSei !== this.expectedRemoteSei && this.remoteVideoUserId && !this.preserveRemoteDom) {
1731
1530
  this.pendingRemoteVideoUserId = this.remoteVideoUserId;
1732
1531
  this.remoteVideoUserId = null;
1733
1532
  }
@@ -2075,45 +1874,6 @@ var _RtcManager = class _RtcManager {
2075
1874
  sentFrameRate: this.lastObservedSentFrameRate
2076
1875
  });
2077
1876
  }
2078
- /** @deprecated Use {@link ensurePublishedInputBeforeStart}. */
2079
- async waitForRtcOutboundBeforeStart(options) {
2080
- const timeoutMs = options?.timeoutMs ?? PUBLISH_READY_TIMEOUT_MS;
2081
- const publishAgeMs = this.publishStartedAtMs > 0 ? performance.now() - this.publishStartedAtMs : 0;
2082
- const localFramesPromise = publishAgeMs >= PUBLISH_WARM_SKIP_MS ? Promise.resolve({ ready: true, frameCount: 0, elapsedMs: 0 }) : this.waitForPublishedInputFrames({
2083
- minFrames: PUBLISH_START_MIN_FRAMES,
2084
- timeoutMs: PUBLISH_START_FRAME_TIMEOUT_MS
2085
- });
2086
- const [, rtcReady] = await Promise.all([
2087
- localFramesPromise,
2088
- this.waitForLocalPublishReady({ timeoutMs })
2089
- ]);
2090
- return rtcReady;
2091
- }
2092
- /** @deprecated Use {@link waitForRtcOutboundBeforeStart}. */
2093
- async waitForPublishedInputFramesBeforeStart() {
2094
- const waitOptions = this.resolveInputFrameWaitOptions();
2095
- const waitTasks = [
2096
- this.waitForLocalPublishReady({ timeoutMs: PUBLISH_READY_TIMEOUT_MS })
2097
- ];
2098
- if (waitOptions) {
2099
- waitTasks.push(
2100
- this.waitForMinPublishDuration(waitOptions.minPublishMs),
2101
- this.waitForPublishedInputFrames({
2102
- minFrames: waitOptions.minFrames,
2103
- timeoutMs: waitOptions.timeoutMs
2104
- })
2105
- );
2106
- }
2107
- const [rtcResult, ...rest] = await Promise.all(waitTasks);
2108
- const rtcSend = rtcResult;
2109
- const frameResult = waitOptions ? rest[1] : { ready: true, frameCount: 0, elapsedMs: 0 };
2110
- return {
2111
- ...frameResult,
2112
- rtcSendReady: rtcSend.ready,
2113
- rtcSendReason: rtcSend.reason,
2114
- rtcSendElapsedMs: rtcSend.elapsedSincePublishMs
2115
- };
2116
- }
2117
1877
  async waitForMinPublishDuration(minMs) {
2118
1878
  if (minMs <= 0 || this.publishStartedAtMs <= 0) {
2119
1879
  return;
@@ -3608,9 +3368,6 @@ function getFitContentRect(rect, targetSize, fitMode = "contain") {
3608
3368
  height
3609
3369
  };
3610
3370
  }
3611
- function getContentRect(rect, targetSize) {
3612
- return getFitContentRect(rect, targetSize, "contain");
3613
- }
3614
3371
  function findDragMediaElement(dragSurface) {
3615
3372
  const root = dragSurface.parentElement ?? dragSurface;
3616
3373
  if (typeof root.querySelector !== "function") {
@@ -3776,6 +3533,7 @@ function drawNeon(ctx, segs, container, targetSize, dpr, fitMode, mirrored) {
3776
3533
  }
3777
3534
  var DragTrackController = class {
3778
3535
  constructor(container, options) {
3536
+ this.strokeActive = false;
3779
3537
  this.activePointers = /* @__PURE__ */ new Map();
3780
3538
  this.lastTargetPoints = /* @__PURE__ */ new Map();
3781
3539
  this.segQueue = [];
@@ -3785,6 +3543,8 @@ var DragTrackController = class {
3785
3543
  this.resizeObserver = null;
3786
3544
  this.container = container;
3787
3545
  this.onTracks = options.onTracks;
3546
+ this.onStrokeStart = options.onStrokeStart;
3547
+ this.onStrokeEnd = options.onStrokeEnd;
3788
3548
  this.targetSize = options.targetSize;
3789
3549
  this.fitMode = options.fitMode ?? "cover";
3790
3550
  this.mirrored = options.mirrored ?? false;
@@ -3955,11 +3715,17 @@ var DragTrackController = class {
3955
3715
  this.flushTracks();
3956
3716
  }, 30);
3957
3717
  }
3958
- finishStroke() {
3718
+ finishStroke(emitEnd = false) {
3719
+ const wasActive = this.strokeActive;
3720
+ this.strokeActive = false;
3959
3721
  this.stopSampling();
3960
3722
  this.stopDrawLoop();
3961
3723
  this.segQueue.length = 0;
3962
3724
  this.clearCanvas();
3725
+ if (emitEnd && wasActive && this.onStrokeEnd) {
3726
+ Promise.resolve(this.onStrokeEnd()).catch(() => {
3727
+ });
3728
+ }
3963
3729
  }
3964
3730
  handlePointerDown(e) {
3965
3731
  if (!this.enabled) {
@@ -3983,6 +3749,12 @@ var DragTrackController = class {
3983
3749
  if (targetPoint) {
3984
3750
  this.lastTargetPoints.set(e.pointerId, targetPoint);
3985
3751
  }
3752
+ const strokeWasActive = this.strokeActive;
3753
+ this.strokeActive = true;
3754
+ if (!strokeWasActive && this.onStrokeStart) {
3755
+ Promise.resolve(this.onStrokeStart()).catch(() => {
3756
+ });
3757
+ }
3986
3758
  this.startDrawLoop();
3987
3759
  this.startSampling();
3988
3760
  }
@@ -4015,7 +3787,7 @@ var DragTrackController = class {
4015
3787
  } catch {
4016
3788
  }
4017
3789
  if (this.activePointers.size === 0) {
4018
- this.finishStroke();
3790
+ this.finishStroke(true);
4019
3791
  }
4020
3792
  }
4021
3793
  handlePointerCancel(e) {
@@ -4025,7 +3797,7 @@ var DragTrackController = class {
4025
3797
  this.activePointers.delete(e.pointerId);
4026
3798
  this.lastTargetPoints.delete(e.pointerId);
4027
3799
  if (this.activePointers.size === 0) {
4028
- this.finishStroke();
3800
+ this.finishStroke(true);
4029
3801
  }
4030
3802
  }
4031
3803
  };
@@ -4199,8 +3971,11 @@ var RealtimeSession = class _RealtimeSession {
4199
3971
  this.localVideoSize = null;
4200
3972
  this.currentPrompt = "";
4201
3973
  this.currentGenerationModel = "";
4202
- this.currentStaticGenerate = void 0;
4203
3974
  this.disconnected = false;
3975
+ /** True only after `connect()` bootstrap succeeds — gates `onDisconnect`. */
3976
+ this.sessionEstablished = false;
3977
+ this.disconnectNotified = false;
3978
+ this.pendingDisconnectReason = "client";
4204
3979
  this.remoteViewHost = null;
4205
3980
  this.dragController = null;
4206
3981
  this.dragTrackFitMode = "cover";
@@ -4214,6 +3989,7 @@ var RealtimeSession = class _RealtimeSession {
4214
3989
  this.rtc = rtc;
4215
3990
  this.reportError = reportError;
4216
3991
  this.onRemoteStream = options.onRemoteStream;
3992
+ this.onDisconnect = options.onDisconnect;
4217
3993
  this.onPublishPipelineWaitChange = clientOptions?.onPublishPipelineWaitChange;
4218
3994
  if (options.remoteElement) {
4219
3995
  this.remoteElement = options.remoteElement;
@@ -4242,7 +4018,7 @@ var RealtimeSession = class _RealtimeSession {
4242
4018
  }
4243
4019
  static async connect(stream, options, clientOptions) {
4244
4020
  const sdkI18n = createSDKI18n({});
4245
- const notifyUserError = clientOptions.onError;
4021
+ const notifyUserError = options.onError ?? clientOptions.onError;
4246
4022
  const reportError = (error, fallbackMessage) => {
4247
4023
  throw notifyError(error, notifyUserError, fallbackMessage ?? sdkI18n.t("errors.requestFailed"));
4248
4024
  };
@@ -4263,6 +4039,7 @@ var RealtimeSession = class _RealtimeSession {
4263
4039
  const session = new _RealtimeSession(client, rtc, reportError, options, clientOptions);
4264
4040
  try {
4265
4041
  await session.bootstrap(stream, options, clientOptions.heartbeatIntervalMs ?? 1e4, prewarmed);
4042
+ session.sessionEstablished = true;
4266
4043
  return session;
4267
4044
  } catch (error) {
4268
4045
  await session.safeDisconnect();
@@ -4288,11 +4065,11 @@ var RealtimeSession = class _RealtimeSession {
4288
4065
  heartbeatIntervalMs,
4289
4066
  (heartbeatSession) => {
4290
4067
  if (heartbeatSession.status !== ACTIVE_STATUS) {
4291
- void this.safeDisconnect();
4068
+ void this.safeDisconnect({ reason: "session_inactive" });
4292
4069
  }
4293
4070
  },
4294
4071
  () => {
4295
- void this.safeDisconnect();
4072
+ void this.safeDisconnect({ reason: "heartbeat_error" });
4296
4073
  }
4297
4074
  );
4298
4075
  const joinInfo = this.client.getRtcJoinInfo(createdSession);
@@ -4335,7 +4112,6 @@ var RealtimeSession = class _RealtimeSession {
4335
4112
  const initialState = options.initialState;
4336
4113
  this.currentPrompt = resolvePromptText(initialState?.prompt);
4337
4114
  this.currentGenerationModel = resolveGenerationModel(initialState?.model);
4338
- this.currentStaticGenerate = initialState?.staticGenerate;
4339
4115
  const publishFpsOptions = {
4340
4116
  mobile: options.mobile,
4341
4117
  highQuality: true,
@@ -4362,7 +4138,8 @@ var RealtimeSession = class _RealtimeSession {
4362
4138
  model: this.currentGenerationModel || void 0,
4363
4139
  size: options.size,
4364
4140
  refImage: initialRefImageUrl,
4365
- staticGenerate: this.currentStaticGenerate
4141
+ staticGenerate: initialState?.staticGenerate,
4142
+ staticImagePath: initialState?.staticImagePath?.trim() || void 0
4366
4143
  });
4367
4144
  }
4368
4145
  }
@@ -4441,23 +4218,19 @@ var RealtimeSession = class _RealtimeSession {
4441
4218
  if (options?.model !== void 0) {
4442
4219
  this.currentGenerationModel = resolveGenerationModel(options.model);
4443
4220
  }
4444
- if (options?.staticGenerate !== void 0) {
4445
- this.currentStaticGenerate = options.staticGenerate;
4446
- }
4447
4221
  await this.runStart({
4448
4222
  prompt: this.currentPrompt || void 0,
4449
4223
  model: this.currentGenerationModel || void 0,
4450
4224
  size: options?.size,
4451
4225
  refImage: resolvedRefImage,
4452
- staticGenerate: this.currentStaticGenerate
4226
+ staticGenerate: options?.staticGenerate === true ? true : void 0,
4227
+ staticImagePath: options?.staticImagePath?.trim() || void 0,
4228
+ preserveRemoteOutput: options?.preserveRemoteOutput
4453
4229
  });
4454
4230
  } catch (error) {
4455
4231
  this.reportError(error);
4456
4232
  }
4457
4233
  }
4458
- async setMode(options) {
4459
- return this.restart(options);
4460
- }
4461
4234
  async refreshRemoteOutput(options) {
4462
4235
  if (!this.remoteVideoContainer) {
4463
4236
  return;
@@ -4531,7 +4304,7 @@ var RealtimeSession = class _RealtimeSession {
4531
4304
  return;
4532
4305
  }
4533
4306
  this.disconnected = true;
4534
- await this.safeDisconnect(options);
4307
+ await this.safeDisconnect({ ...options, reason: "client" });
4535
4308
  }
4536
4309
  getSessionUid() {
4537
4310
  return this.sessionUid;
@@ -4540,17 +4313,20 @@ var RealtimeSession = class _RealtimeSession {
4540
4313
  if (!this.rtc.snapshot.joined) {
4541
4314
  return;
4542
4315
  }
4316
+ const preserveRemoteOutput = options.preserveRemoteOutput ?? false;
4317
+ if (preserveRemoteOutput) {
4318
+ this.rtc.setPreserveRemoteDom(true);
4319
+ }
4543
4320
  const startPrompt = options.prompt?.trim() ?? this.currentPrompt;
4544
4321
  if (options.model !== void 0) {
4545
4322
  this.currentGenerationModel = resolveGenerationModel(options.model);
4546
4323
  }
4547
- if (options.staticGenerate !== void 0) {
4548
- this.currentStaticGenerate = options.staticGenerate;
4549
- }
4550
4324
  const modelLabel = resolveGenerationModel(this.currentGenerationModel);
4551
4325
  await this.rtc.ensurePublishedInputBeforeStart();
4552
- this.rtc.setRemoteVideoContainer(null);
4553
- this.rtc.clearRemoteVideo();
4326
+ if (!preserveRemoteOutput) {
4327
+ this.rtc.setRemoteVideoContainer(null);
4328
+ this.rtc.clearRemoteVideo();
4329
+ }
4554
4330
  const size = resolveSessionTargetSize({
4555
4331
  localVideoSize: this.localVideoSize,
4556
4332
  overrideSize: options.size ?? this.sessionSizeOverride
@@ -4571,16 +4347,21 @@ var RealtimeSession = class _RealtimeSession {
4571
4347
  size,
4572
4348
  prompt: startPrompt,
4573
4349
  refImagePath: resolveRefImagePath(options.refImage),
4574
- staticGenerate: this.currentStaticGenerate
4350
+ // Never reuse prior start — caller must pass Instant fields each time.
4351
+ staticGenerate: options.staticGenerate === true ? true : void 0,
4352
+ staticImagePath: options.staticImagePath?.trim() || void 0
4575
4353
  });
4576
- console.log("[Xmax SDK] \u771F\u5B9E\u53D1\u9001 start ->", startRoomEvent);
4577
4354
  await this.rtc.sendRoomEvent(startRoomEvent);
4578
- await this.rtc.refreshRemoteVideoBinding({ force: true });
4355
+ await this.rtc.refreshRemoteVideoBinding({ force: !preserveRemoteOutput });
4579
4356
  this.updateDragController(true);
4580
4357
  this.scheduleNotifyRemoteStream();
4581
4358
  } catch (error) {
4582
4359
  this.rtc.configureSessionSei(null);
4583
4360
  throw error;
4361
+ } finally {
4362
+ if (preserveRemoteOutput) {
4363
+ this.rtc.setPreserveRemoteDom(false);
4364
+ }
4584
4365
  }
4585
4366
  }
4586
4367
  isDragEnabled() {
@@ -4606,17 +4387,6 @@ var RealtimeSession = class _RealtimeSession {
4606
4387
  mirrored: false,
4607
4388
  enabled: true,
4608
4389
  onTracks: (tracks) => {
4609
- const media = this.remoteViewHost?.videoHost?.querySelector("video, canvas");
4610
- const mediaRect = media?.getBoundingClientRect();
4611
- const dragRect = dragHost.getBoundingClientRect();
4612
- console.log("[Xmax] sendTracks", {
4613
- taskUid: this.taskUid,
4614
- targetSize: this.sessionTargetSize,
4615
- fitMode: this.dragTrackFitMode,
4616
- tracks,
4617
- dragRect: dragRect ? { w: dragRect.width, h: dragRect.height } : null,
4618
- mediaRect: mediaRect ? { w: mediaRect.width, h: mediaRect.height, l: mediaRect.left, t: mediaRect.top } : null
4619
- });
4620
4390
  void this.sendTracks(tracks);
4621
4391
  }
4622
4392
  });
@@ -4665,7 +4435,20 @@ var RealtimeSession = class _RealtimeSession {
4665
4435
  this.remoteStreamNotifyTimer = null;
4666
4436
  }
4667
4437
  }
4438
+ emitDisconnect(reason) {
4439
+ if (!this.sessionEstablished || this.disconnectNotified) {
4440
+ return;
4441
+ }
4442
+ this.disconnectNotified = true;
4443
+ try {
4444
+ this.onDisconnect?.(reason);
4445
+ } catch {
4446
+ }
4447
+ }
4668
4448
  async safeDisconnect(options) {
4449
+ const reason = options?.reason ?? this.pendingDisconnectReason;
4450
+ this.pendingDisconnectReason = reason;
4451
+ this.disconnected = true;
4669
4452
  this.clearRemoteStreamNotifyTimer();
4670
4453
  this.lastNotifiedRemoteStream = null;
4671
4454
  const activeSessionUid = this.sessionUid;
@@ -4707,7 +4490,6 @@ var RealtimeSession = class _RealtimeSession {
4707
4490
  this.taskUid = null;
4708
4491
  this.currentPrompt = "";
4709
4492
  this.currentGenerationModel = "";
4710
- this.currentStaticGenerate = void 0;
4711
4493
  this.sessionTargetSize = null;
4712
4494
  this.dragController?.destroy();
4713
4495
  this.dragController = null;
@@ -4717,6 +4499,7 @@ var RealtimeSession = class _RealtimeSession {
4717
4499
  this.hiddenRemoteElement.remove();
4718
4500
  this.hiddenRemoteElement = null;
4719
4501
  }
4502
+ this.emitDisconnect(reason);
4720
4503
  }
4721
4504
  };
4722
4505
 
@@ -5056,7 +4839,6 @@ exports.createTracksRtcRoomEvent = createTracksRtcRoomEvent;
5056
4839
  exports.createVideoFileStream = createVideoFileStream;
5057
4840
  exports.createXmaxClient = createXmaxClient;
5058
4841
  exports.getCameraEnvironmentIssue = getCameraEnvironmentIssue;
5059
- exports.getContentRect = getContentRect;
5060
4842
  exports.getDefaultMessages = getDefaultMessages;
5061
4843
  exports.isCameraAccessError = isCameraAccessError;
5062
4844
  exports.isImageMediaFile = isImageMediaFile;