@xmaxai/sdk 0.1.0

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 +40 -0
  2. package/LICENSE +21 -0
  3. package/README.md +201 -0
  4. package/dist/chunk-67XSD3QJ.js +2673 -0
  5. package/dist/chunk-67XSD3QJ.js.map +1 -0
  6. package/dist/chunk-74ZZWUPI.js +992 -0
  7. package/dist/chunk-74ZZWUPI.js.map +1 -0
  8. package/dist/chunk-EGNRAZ5U.js +432 -0
  9. package/dist/chunk-EGNRAZ5U.js.map +1 -0
  10. package/dist/chunk-PLU2YVAS.js +414 -0
  11. package/dist/chunk-PLU2YVAS.js.map +1 -0
  12. package/dist/experimental/index.cjs +3897 -0
  13. package/dist/experimental/index.cjs.map +1 -0
  14. package/dist/experimental/index.d.cts +2 -0
  15. package/dist/experimental/index.d.ts +2 -0
  16. package/dist/experimental/index.js +5 -0
  17. package/dist/experimental/index.js.map +1 -0
  18. package/dist/index-BIUk_kvb.d.ts +350 -0
  19. package/dist/index-Dai62Sos.d.cts +350 -0
  20. package/dist/index.cjs +5109 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +235 -0
  23. package/dist/index.d.ts +235 -0
  24. package/dist/index.js +359 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/local-publish-diagnostics-ABLL56IT.js +79 -0
  27. package/dist/local-publish-diagnostics-ABLL56IT.js.map +1 -0
  28. package/dist/react/index.cjs +4304 -0
  29. package/dist/react/index.cjs.map +1 -0
  30. package/dist/react/index.d.cts +112 -0
  31. package/dist/react/index.d.ts +112 -0
  32. package/dist/react/index.js +817 -0
  33. package/dist/react/index.js.map +1 -0
  34. package/dist/rtc-manager-gYDkADGw.d.cts +412 -0
  35. package/dist/rtc-manager-gYDkADGw.d.ts +412 -0
  36. package/dist/video-file-stream-UXJ6TNIB.js +4 -0
  37. package/dist/video-file-stream-UXJ6TNIB.js.map +1 -0
  38. package/package.json +82 -0
@@ -0,0 +1,817 @@
1
+ import { browserToast, notifyError, RtcManager, XmaxOpenClient, ACTIVE_STATUS, createStopRtcRoomEvent, models, requestCameraMediaStream, createNativeCameraStream, createTaskUid, createStartRtcRoomEvent, createTracksRtcRoomEvent, createDragTrackController } from '../chunk-67XSD3QJ.js';
2
+ import { createVideoFileStream } from '../chunk-EGNRAZ5U.js';
3
+ import { createSDKI18n, resolveUploadVideoSize, isMobilePublishEnvironment, resolveSessionTargetSize, DEFAULT_SESSION_TARGET_SIZE } from '../chunk-PLU2YVAS.js';
4
+ import { createContext, useMemo, useRef, useCallback, useState, useEffect, useContext } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ // src/shared/session-debug.ts
8
+ function createSessionDebugLogger(enabled, onLog) {
9
+ return (category, level, message, data) => {
10
+ if (!enabled || !onLog) {
11
+ return;
12
+ }
13
+ const scope = category === "rtc" || category === "sei" ? "rtc" : "client";
14
+ onLog({
15
+ scope,
16
+ level,
17
+ message: `[${category}] ${message}`,
18
+ data
19
+ });
20
+ };
21
+ }
22
+ function seiGateChanged(prev, next) {
23
+ if (!prev) {
24
+ return true;
25
+ }
26
+ return prev.matched !== next.matched || prev.expectedSei !== next.expectedSei || prev.sei !== next.sei || prev.userId !== next.userId;
27
+ }
28
+ function outputVisibilityChanged(prev, next) {
29
+ if (!prev) {
30
+ return true;
31
+ }
32
+ return prev.showOutputVideo !== next.showOutputVideo || prev.outputEnabled !== next.outputEnabled || prev.hasResult !== next.hasResult || prev.remoteVideoUserId !== next.remoteVideoUserId || prev.localVideoPublished !== next.localVideoPublished || prev.inputSource !== next.inputSource || prev.seiMatched !== next.seiMatched;
33
+ }
34
+ function isVideoInputUnhealthy(health) {
35
+ return health.stalled || health.paused || health.ended || health.trackReadyState === "ended";
36
+ }
37
+ function videoInputHealthChanged(prev, next) {
38
+ if (!prev) {
39
+ return isVideoInputUnhealthy(next);
40
+ }
41
+ const prevBad = isVideoInputUnhealthy(prev);
42
+ const nextBad = isVideoInputUnhealthy(next);
43
+ if (prevBad !== nextBad) {
44
+ return true;
45
+ }
46
+ if (nextBad) {
47
+ return prev.stalled !== next.stalled || prev.paused !== next.paused || prev.ended !== next.ended || prev.trackReadyState !== next.trackReadyState;
48
+ }
49
+ return false;
50
+ }
51
+ var XmaxContext = createContext(null);
52
+ var createDefaultRtcSnapshot = () => ({
53
+ roomId: null,
54
+ userId: null,
55
+ appId: null,
56
+ botUserId: null,
57
+ joined: false,
58
+ localVideoPublished: false,
59
+ remoteVideoUserId: null,
60
+ users: []
61
+ });
62
+ var createDefaultSeiGateState = () => ({
63
+ expectedSei: null,
64
+ sei: null,
65
+ userId: null,
66
+ matched: true
67
+ });
68
+ function XmaxProvider({ config, children }) {
69
+ const sdkI18n = useMemo(() => config.i18n ?? createSDKI18n({ locale: config.locale }), [config.i18n, config.locale]);
70
+ const debugEnabled = config.debug ?? false;
71
+ const debugLog = useMemo(
72
+ () => createSessionDebugLogger(debugEnabled, config.onLog),
73
+ [config.onLog, debugEnabled]
74
+ );
75
+ const debugLogRef = useRef(debugLog);
76
+ debugLogRef.current = debugLog;
77
+ const debugEnabledRef = useRef(debugEnabled);
78
+ debugEnabledRef.current = debugEnabled;
79
+ const prevSeiGateRef = useRef(null);
80
+ const prevOutputVisibilityRef = useRef(null);
81
+ const prevVideoHealthRef = useRef(null);
82
+ const resetDebugSnapshots = useCallback(() => {
83
+ prevSeiGateRef.current = null;
84
+ prevOutputVisibilityRef.current = null;
85
+ prevVideoHealthRef.current = null;
86
+ }, []);
87
+ const externalOnError = config.onError;
88
+ const clientRef = useRef(null);
89
+ const sessionUidRef = useRef(null);
90
+ const taskUidRef = useRef(null);
91
+ const lastStartPromptRef = useRef(void 0);
92
+ const lastStartModelRef = useRef("");
93
+ const lastStartRefImageRef = useRef(void 0);
94
+ const videoFileStreamRef = useRef(null);
95
+ const cameraStreamRef = useRef(null);
96
+ const localVideoSizeRef = useRef(null);
97
+ const inputSourceRef = useRef("none");
98
+ const disconnectRef = useRef(null);
99
+ const disconnectPromiseRef = useRef(null);
100
+ const localContainerRef = useRef(null);
101
+ const remoteContainerRef = useRef(null);
102
+ const outputEnabledRef = useRef(false);
103
+ const activeModeCodeRef = useRef(null);
104
+ const onRemoteOutputStalledRef = useRef(null);
105
+ const [rtcState, setRtcState] = useState(createDefaultRtcSnapshot);
106
+ const [inputSource, setInputSource] = useState("none");
107
+ const [isCameraOn, setIsCameraOn] = useState(false);
108
+ const [isRecording, setIsRecording] = useState(false);
109
+ const [isGenerating, setIsGenerating] = useState(false);
110
+ const [hasResult, setHasResult] = useState(false);
111
+ const [outputEnabled, setOutputEnabled] = useState(false);
112
+ const [activeModeCode, setActiveModeCode] = useState(null);
113
+ const [localVideoSize, setLocalVideoSize] = useState(null);
114
+ const [sessionTargetSize, setSessionTargetSize] = useState(null);
115
+ const [seiGate, setSeiGate] = useState(createDefaultSeiGateState);
116
+ inputSourceRef.current = inputSource;
117
+ localVideoSizeRef.current = localVideoSize;
118
+ outputEnabledRef.current = outputEnabled;
119
+ activeModeCodeRef.current = activeModeCode;
120
+ const ensureLocalVideoContainer = useCallback(() => {
121
+ const rtc = rtcManagerRef.current;
122
+ const container = localContainerRef.current;
123
+ if (!rtc || !container) {
124
+ return;
125
+ }
126
+ rtc.setLocalVideoContainer(container);
127
+ }, []);
128
+ const bindRemoteContainer = useCallback(() => {
129
+ const rtc = rtcManagerRef.current;
130
+ const container = remoteContainerRef.current;
131
+ if (!rtc || !container) {
132
+ return;
133
+ }
134
+ rtc.setRemoteVideoContainer(container);
135
+ void rtc.refreshRemoteVideoBinding();
136
+ }, []);
137
+ const detachRemoteContainer = useCallback(() => {
138
+ rtcManagerRef.current?.setRemoteVideoContainer(null);
139
+ }, []);
140
+ const notifyUserError = useCallback((message, error) => {
141
+ if (externalOnError) {
142
+ externalOnError(message, error);
143
+ return;
144
+ }
145
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
146
+ browserToast.error(message);
147
+ }
148
+ }, [externalOnError]);
149
+ const reportError = useCallback((error, fallbackMessage) => {
150
+ return notifyError(error, notifyUserError, fallbackMessage ?? sdkI18n.t("errors.requestFailed"));
151
+ }, [notifyUserError, sdkI18n]);
152
+ const rtcManagerRef = useRef(null);
153
+ if (!rtcManagerRef.current) {
154
+ rtcManagerRef.current = new RtcManager({
155
+ onStateChange: (state) => setRtcState(state),
156
+ onSeiGateChange: (state) => {
157
+ setSeiGate(state);
158
+ if (debugEnabledRef.current && seiGateChanged(prevSeiGateRef.current, state)) {
159
+ debugLogRef.current("sei", state.matched ? "success" : "warn", "gate change", state);
160
+ prevSeiGateRef.current = state;
161
+ }
162
+ },
163
+ onLog: (entry) => config.onLog?.({ scope: "rtc", ...entry }),
164
+ onRemoteOutputStalled: (info) => onRemoteOutputStalledRef.current?.(info),
165
+ debug: debugEnabled,
166
+ i18n: sdkI18n
167
+ });
168
+ }
169
+ const getClient = useCallback(() => {
170
+ const existing = clientRef.current;
171
+ if (existing) {
172
+ return existing;
173
+ }
174
+ const created = new XmaxOpenClient({
175
+ apiKey: config.apiKey,
176
+ authToken: config.authToken,
177
+ baseUrl: config.baseUrl,
178
+ locale: config.locale,
179
+ i18n: sdkI18n,
180
+ onError: notifyUserError
181
+ });
182
+ clientRef.current = created;
183
+ return created;
184
+ }, [config.apiKey, config.authToken, config.baseUrl, config.locale, notifyUserError, sdkI18n]);
185
+ const ensureConnected = useCallback(async () => {
186
+ const client = getClient();
187
+ const rtc = rtcManagerRef.current;
188
+ if (!rtc) {
189
+ return;
190
+ }
191
+ if (rtc.snapshot.joined && sessionUidRef.current) {
192
+ return;
193
+ }
194
+ const model = config.model ?? "x2.0";
195
+ const session = await client.createSession(model);
196
+ if (!session.sessionUid?.trim()) {
197
+ throw new Error(sdkI18n.t("errors.sessionUidMissing"));
198
+ }
199
+ sessionUidRef.current = session.sessionUid;
200
+ client.startHeartbeat(
201
+ session.sessionUid,
202
+ config.heartbeatIntervalMs ?? 1e4,
203
+ (heartbeatSession) => {
204
+ if (heartbeatSession.status !== ACTIVE_STATUS) {
205
+ void disconnectRef.current?.();
206
+ }
207
+ },
208
+ () => {
209
+ void disconnectRef.current?.();
210
+ }
211
+ );
212
+ const joinInfo = client.getRtcJoinInfo(session);
213
+ await rtc.join(joinInfo);
214
+ ensureLocalVideoContainer();
215
+ }, [config.heartbeatIntervalMs, config.model, ensureLocalVideoContainer, getClient, sdkI18n]);
216
+ const connect = useCallback(async () => {
217
+ try {
218
+ await ensureConnected();
219
+ } catch (error) {
220
+ throw reportError(error);
221
+ }
222
+ }, [ensureConnected, reportError]);
223
+ const stopPublishing = useCallback(async () => {
224
+ const rtc = rtcManagerRef.current;
225
+ if (!rtc) {
226
+ return;
227
+ }
228
+ try {
229
+ await rtc.stopVideoPublishing();
230
+ } catch {
231
+ }
232
+ rtc.clearRemoteVideo();
233
+ detachRemoteContainer();
234
+ setOutputEnabled(false);
235
+ setHasResult(false);
236
+ if (videoFileStreamRef.current) {
237
+ videoFileStreamRef.current.destroy();
238
+ videoFileStreamRef.current = null;
239
+ }
240
+ if (cameraStreamRef.current) {
241
+ cameraStreamRef.current.destroy();
242
+ cameraStreamRef.current = null;
243
+ }
244
+ setInputSource("none");
245
+ setIsCameraOn(false);
246
+ setIsRecording(false);
247
+ setLocalVideoSize(null);
248
+ localVideoSizeRef.current = null;
249
+ setSessionTargetSize(null);
250
+ setSeiGate(createDefaultSeiGateState());
251
+ resetDebugSnapshots();
252
+ debugLogRef.current("lifecycle", "info", "stopPublishing", { inputSource: inputSourceRef.current });
253
+ }, [detachRemoteContainer, resetDebugSnapshots]);
254
+ const stop = useCallback(async () => {
255
+ try {
256
+ const rtc = rtcManagerRef.current;
257
+ taskUidRef.current = null;
258
+ rtc?.configureSessionSei(null);
259
+ setSeiGate(createDefaultSeiGateState());
260
+ resetDebugSnapshots();
261
+ debugLogRef.current("lifecycle", "info", "stop", {});
262
+ if (!rtc?.snapshot.joined) {
263
+ rtc?.clearRemoteVideo();
264
+ detachRemoteContainer();
265
+ setHasResult(false);
266
+ setOutputEnabled(false);
267
+ return;
268
+ }
269
+ try {
270
+ const stopEvent = createStopRtcRoomEvent({
271
+ userId: rtc.snapshot.userId ?? void 0,
272
+ sessionUid: sessionUidRef.current ?? void 0
273
+ });
274
+ await rtc.sendRoomEvent(stopEvent);
275
+ } finally {
276
+ rtc.clearRemoteVideo();
277
+ detachRemoteContainer();
278
+ setHasResult(false);
279
+ setOutputEnabled(false);
280
+ }
281
+ } catch (error) {
282
+ throw reportError(error);
283
+ }
284
+ }, [detachRemoteContainer, reportError, resetDebugSnapshots]);
285
+ const disconnect = useCallback(async () => {
286
+ if (disconnectPromiseRef.current) {
287
+ return disconnectPromiseRef.current;
288
+ }
289
+ const run = async () => {
290
+ const client = clientRef.current;
291
+ const rtc = rtcManagerRef.current;
292
+ const activeSessionUid = sessionUidRef.current;
293
+ client?.beginTeardown();
294
+ rtc?.configureSessionSei(null);
295
+ setSeiGate(createDefaultSeiGateState());
296
+ taskUidRef.current = null;
297
+ resetDebugSnapshots();
298
+ rtc?.clearRemoteVideo();
299
+ try {
300
+ await stop();
301
+ } catch {
302
+ }
303
+ await stopPublishing();
304
+ if (rtc) {
305
+ try {
306
+ rtc.configureSessionSei(null);
307
+ await rtc.leave();
308
+ } catch {
309
+ }
310
+ }
311
+ if (client && activeSessionUid) {
312
+ try {
313
+ await client.closeSession(activeSessionUid);
314
+ } catch {
315
+ }
316
+ }
317
+ client?.endTeardown();
318
+ sessionUidRef.current = null;
319
+ taskUidRef.current = null;
320
+ setHasResult(false);
321
+ setOutputEnabled(false);
322
+ setActiveModeCode(null);
323
+ setSessionTargetSize(null);
324
+ setIsRecording(false);
325
+ detachRemoteContainer();
326
+ setRtcState(createDefaultRtcSnapshot());
327
+ setSeiGate(createDefaultSeiGateState());
328
+ };
329
+ const promise = run().finally(() => {
330
+ if (disconnectPromiseRef.current === promise) {
331
+ disconnectPromiseRef.current = null;
332
+ }
333
+ });
334
+ disconnectPromiseRef.current = promise;
335
+ return promise;
336
+ }, [detachRemoteContainer, stop, stopPublishing, resetDebugSnapshots]);
337
+ disconnectRef.current = disconnect;
338
+ useEffect(() => {
339
+ return () => {
340
+ void disconnectRef.current?.();
341
+ };
342
+ }, []);
343
+ useEffect(() => {
344
+ if (!debugEnabled) {
345
+ return;
346
+ }
347
+ const snapshot = {
348
+ outputEnabled,
349
+ hasResult,
350
+ showOutputVideo: outputEnabled && (hasResult || !!rtcState.remoteVideoUserId),
351
+ remoteVideoUserId: rtcState.remoteVideoUserId,
352
+ localVideoPublished: rtcState.localVideoPublished,
353
+ inputSource,
354
+ seiMatched: seiGate.matched,
355
+ expectedSei: seiGate.expectedSei,
356
+ lastSei: seiGate.sei
357
+ };
358
+ if (!outputVisibilityChanged(prevOutputVisibilityRef.current, snapshot)) {
359
+ return;
360
+ }
361
+ const level = snapshot.showOutputVideo ? "info" : "warn";
362
+ debugLog("output", level, "visibility change", snapshot);
363
+ prevOutputVisibilityRef.current = snapshot;
364
+ }, [
365
+ debugEnabled,
366
+ debugLog,
367
+ outputEnabled,
368
+ hasResult,
369
+ rtcState.remoteVideoUserId,
370
+ rtcState.localVideoPublished,
371
+ inputSource,
372
+ seiGate.matched,
373
+ seiGate.expectedSei,
374
+ seiGate.sei
375
+ ]);
376
+ useEffect(() => {
377
+ const rtc = rtcManagerRef.current;
378
+ if (!rtc) {
379
+ return;
380
+ }
381
+ if (!outputEnabled) {
382
+ rtc.clearRemoteVideo();
383
+ detachRemoteContainer();
384
+ return;
385
+ }
386
+ bindRemoteContainer();
387
+ }, [bindRemoteContainer, detachRemoteContainer, outputEnabled]);
388
+ const openCamera = useCallback(async (options) => {
389
+ try {
390
+ await ensureConnected();
391
+ const rtc = rtcManagerRef.current;
392
+ if (!rtc) {
393
+ return;
394
+ }
395
+ await stopPublishing();
396
+ ensureLocalVideoContainer();
397
+ const defaultCamera = models.realtime("x2.0");
398
+ const cameraHint = options?.size ?? [defaultCamera.width, defaultCamera.height];
399
+ const precomputedSize = resolveUploadVideoSize(cameraHint[0], cameraHint[1]);
400
+ setSessionTargetSize(precomputedSize);
401
+ const rawStream = await requestCameraMediaStream({
402
+ width: cameraHint[0],
403
+ height: cameraHint[1],
404
+ frameRate: 30,
405
+ facingMode: "user"
406
+ });
407
+ const publishStream = await createNativeCameraStream(rawStream);
408
+ cameraStreamRef.current = publishStream;
409
+ await rtc.startExternalVideoPublishing(publishStream.videoTrack, precomputedSize, {
410
+ mobile: isMobilePublishEnvironment(),
411
+ highQuality: true
412
+ });
413
+ const sessionDims = { width: publishStream.width, height: publishStream.height };
414
+ setLocalVideoSize(sessionDims);
415
+ localVideoSizeRef.current = sessionDims;
416
+ setSessionTargetSize([sessionDims.width, sessionDims.height]);
417
+ setIsCameraOn(true);
418
+ setIsRecording(true);
419
+ setInputSource("camera");
420
+ } catch (error) {
421
+ throw reportError(error);
422
+ }
423
+ }, [ensureConnected, ensureLocalVideoContainer, reportError, sdkI18n, stopPublishing]);
424
+ const closeCamera = useCallback(async () => {
425
+ await disconnect();
426
+ }, [disconnect]);
427
+ const loadVideoFile = useCallback(async (file, options) => {
428
+ try {
429
+ await ensureConnected();
430
+ const rtc = rtcManagerRef.current;
431
+ if (!rtc) {
432
+ return null;
433
+ }
434
+ await stopPublishing();
435
+ ensureLocalVideoContainer();
436
+ prevVideoHealthRef.current = null;
437
+ const stream = await createVideoFileStream(file, {
438
+ muted: true,
439
+ loop: true,
440
+ mobile: isMobilePublishEnvironment(),
441
+ targetSize: options?.size,
442
+ i18n: sdkI18n,
443
+ onHealthReport: debugEnabled ? (health) => {
444
+ if (!videoInputHealthChanged(prevVideoHealthRef.current, health)) {
445
+ return;
446
+ }
447
+ prevVideoHealthRef.current = health;
448
+ const level = health.stalled || health.paused || health.ended ? "warn" : "info";
449
+ debugLogRef.current("video-input", level, "health change", health);
450
+ } : void 0
451
+ });
452
+ if (videoFileStreamRef.current) {
453
+ videoFileStreamRef.current.destroy();
454
+ }
455
+ videoFileStreamRef.current = stream;
456
+ await rtc.startExternalVideoPublishing(stream.videoTrack, [stream.width, stream.height], {
457
+ mobile: isMobilePublishEnvironment(),
458
+ fps: stream.fps,
459
+ highQuality: true
460
+ });
461
+ setInputSource("video");
462
+ setIsCameraOn(true);
463
+ setIsRecording(true);
464
+ debugLogRef.current("lifecycle", "info", "loadVideoFile", {
465
+ width: stream.width,
466
+ height: stream.height,
467
+ fileName: file.name
468
+ });
469
+ if (stream.width && stream.height) {
470
+ const size = { width: stream.width, height: stream.height };
471
+ setLocalVideoSize(size);
472
+ localVideoSizeRef.current = size;
473
+ setSessionTargetSize([stream.width, stream.height]);
474
+ } else {
475
+ setLocalVideoSize(null);
476
+ localVideoSizeRef.current = null;
477
+ }
478
+ const targetSize = [stream.width, stream.height];
479
+ return {
480
+ url: stream.url,
481
+ width: stream.width,
482
+ height: stream.height,
483
+ previewStream: stream.previewStream,
484
+ videoEl: stream.videoEl,
485
+ targetSize
486
+ };
487
+ } catch (error) {
488
+ throw reportError(error);
489
+ }
490
+ }, [ensureConnected, ensureLocalVideoContainer, reportError, sdkI18n, stopPublishing, debugEnabled]);
491
+ const clearInput = useCallback(async () => {
492
+ await disconnect();
493
+ }, [disconnect]);
494
+ const uploadImage = useCallback(async (file) => {
495
+ try {
496
+ const client = getClient();
497
+ return await client.uploadImage(file);
498
+ } catch (error) {
499
+ throw reportError(error);
500
+ }
501
+ }, [getClient, reportError]);
502
+ const setRefImage = useCallback(async (input) => {
503
+ try {
504
+ if (typeof input !== "string") {
505
+ const result2 = await uploadImage(input);
506
+ return result2.url;
507
+ }
508
+ const raw = input.trim();
509
+ if (!raw) {
510
+ return "";
511
+ }
512
+ if (/^https?:\/\//i.test(raw)) {
513
+ return raw;
514
+ }
515
+ const res = await fetch(raw);
516
+ if (!res.ok) {
517
+ throw new Error(`Failed to load ref image: ${res.status}`);
518
+ }
519
+ const blob = await res.blob();
520
+ const mime = blob.type || "image/png";
521
+ const ext = mime === "image/jpeg" ? "jpg" : mime === "image/webp" ? "webp" : mime === "image/gif" ? "gif" : "png";
522
+ const file = new File([blob], `ref.${ext}`, { type: mime });
523
+ const result = await uploadImage(file);
524
+ return result.url;
525
+ } catch (error) {
526
+ throw reportError(error);
527
+ }
528
+ }, [reportError, uploadImage]);
529
+ const start = useCallback(async (options) => {
530
+ try {
531
+ await ensureConnected();
532
+ const rtc = rtcManagerRef.current;
533
+ if (!rtc?.snapshot.joined) {
534
+ return;
535
+ }
536
+ const refForEvent = options.refImage;
537
+ const resolvedPrompt = options.prompt?.trim() ?? "";
538
+ setIsGenerating(true);
539
+ rtc.clearRemoteVideo();
540
+ rtc.setRemoteVideoContainer(null);
541
+ setHasResult(false);
542
+ setOutputEnabled(true);
543
+ outputEnabledRef.current = true;
544
+ const size = resolveSessionTargetSize({
545
+ localVideoSize: localVideoSizeRef.current,
546
+ overrideSize: options.size
547
+ });
548
+ setSessionTargetSize(size);
549
+ const taskUid = createTaskUid();
550
+ taskUidRef.current = taskUid;
551
+ lastStartPromptRef.current = resolvedPrompt || void 0;
552
+ lastStartModelRef.current = options.model;
553
+ lastStartRefImageRef.current = refForEvent?.trim() || void 0;
554
+ prevSeiGateRef.current = null;
555
+ rtc.configureSessionSei(taskUid);
556
+ debugLogRef.current("lifecycle", "info", "start", {
557
+ taskUid,
558
+ size,
559
+ inputSource: inputSourceRef.current
560
+ });
561
+ const startEvent = createStartRtcRoomEvent({
562
+ userId: rtc.snapshot.userId ?? void 0,
563
+ uid: taskUid,
564
+ sessionUid: taskUid,
565
+ model: options.model,
566
+ size,
567
+ prompt: resolvedPrompt || void 0,
568
+ extraParams: options.extraParams,
569
+ refImagePath: refForEvent?.trim() || void 0
570
+ });
571
+ if (remoteContainerRef.current) {
572
+ rtc.setRemoteVideoContainer(remoteContainerRef.current);
573
+ }
574
+ await rtc.ensurePublishedInputBeforeStart();
575
+ await rtc.sendRoomEvent(startEvent);
576
+ await rtc.refreshRemoteVideoBinding({ force: true });
577
+ setHasResult(true);
578
+ } catch (error) {
579
+ setOutputEnabled(false);
580
+ throw reportError(error);
581
+ } finally {
582
+ setIsGenerating(false);
583
+ }
584
+ }, [ensureConnected, reportError]);
585
+ const sendTracks = useCallback(async (options) => {
586
+ const rtc = rtcManagerRef.current;
587
+ if (!rtc?.snapshot.joined) {
588
+ return;
589
+ }
590
+ const activeTaskUid = taskUidRef.current;
591
+ if (!activeTaskUid) {
592
+ return;
593
+ }
594
+ console.log("[Xmax] sendTracks", {
595
+ taskUid: activeTaskUid,
596
+ targetSize: sessionTargetSize,
597
+ tracks: options.tracks
598
+ });
599
+ try {
600
+ const event = createTracksRtcRoomEvent({
601
+ userId: rtc.snapshot.userId ?? void 0,
602
+ uid: activeTaskUid,
603
+ sessionUid: activeTaskUid,
604
+ tracks: options.tracks
605
+ });
606
+ await rtc.sendRoomEvent(event);
607
+ } catch {
608
+ }
609
+ }, [sessionTargetSize]);
610
+ useCallback(async () => {
611
+ const rtc = rtcManagerRef.current;
612
+ if (!rtc?.snapshot.joined || !outputEnabledRef.current) {
613
+ return;
614
+ }
615
+ try {
616
+ debugLogRef.current("lifecycle", "warn", "recoverGeneration", {
617
+ inputSource: inputSourceRef.current
618
+ });
619
+ const taskUid = createTaskUid();
620
+ taskUidRef.current = taskUid;
621
+ prevSeiGateRef.current = null;
622
+ const size = resolveSessionTargetSize({
623
+ localVideoSize: localVideoSizeRef.current
624
+ });
625
+ const startEvent = createStartRtcRoomEvent({
626
+ userId: rtc.snapshot.userId ?? void 0,
627
+ uid: taskUid,
628
+ sessionUid: taskUid,
629
+ model: lastStartModelRef.current,
630
+ size,
631
+ prompt: lastStartPromptRef.current,
632
+ refImagePath: lastStartRefImageRef.current
633
+ });
634
+ if (remoteContainerRef.current) {
635
+ rtc.setRemoteVideoContainer(remoteContainerRef.current);
636
+ }
637
+ await rtc.sendRoomEvent(startEvent);
638
+ if (remoteContainerRef.current) {
639
+ rtc.setRemoteVideoContainer(remoteContainerRef.current);
640
+ }
641
+ await rtc.refreshRemoteVideoBinding({ force: true });
642
+ } catch (error) {
643
+ reportError(error);
644
+ }
645
+ }, [reportError]);
646
+ useEffect(() => {
647
+ onRemoteOutputStalledRef.current = (info) => {
648
+ debugLogRef.current("lifecycle", "warn", "remote output stalled", info);
649
+ };
650
+ }, []);
651
+ const changeModel = useCallback(async () => {
652
+ return;
653
+ }, []);
654
+ const bindLocalView = useCallback((el) => {
655
+ localContainerRef.current = el;
656
+ rtcManagerRef.current?.setLocalVideoContainer(el);
657
+ }, []);
658
+ const bindRemoteView = useCallback((el) => {
659
+ remoteContainerRef.current = el;
660
+ if (!el) {
661
+ detachRemoteContainer();
662
+ return;
663
+ }
664
+ if (outputEnabledRef.current) {
665
+ bindRemoteContainer();
666
+ }
667
+ }, [bindRemoteContainer, detachRemoteContainer]);
668
+ const value = useMemo(() => ({
669
+ snapshot: {
670
+ sessionUid: sessionUidRef.current,
671
+ rtc: rtcState,
672
+ inputSource,
673
+ isCameraOn,
674
+ isRecording,
675
+ isGenerating,
676
+ isOutputVerifying: !!seiGate.expectedSei && !seiGate.matched,
677
+ hasResult,
678
+ outputEnabled,
679
+ dragEnabled: outputEnabled && isRecording,
680
+ showOutputVideo: outputEnabled && (hasResult || !!rtcState.remoteVideoUserId),
681
+ localVideoSize,
682
+ sessionTargetSize,
683
+ seiGate
684
+ },
685
+ connect,
686
+ disconnect,
687
+ openCamera,
688
+ closeCamera,
689
+ loadVideoFile,
690
+ clearInput,
691
+ uploadImage,
692
+ setRefImage,
693
+ start,
694
+ sendTracks,
695
+ stop,
696
+ changeModel,
697
+ bindLocalView,
698
+ bindRemoteView
699
+ }), [
700
+ rtcState,
701
+ inputSource,
702
+ isCameraOn,
703
+ isRecording,
704
+ isGenerating,
705
+ seiGate,
706
+ hasResult,
707
+ outputEnabled,
708
+ localVideoSize,
709
+ sessionTargetSize,
710
+ connect,
711
+ disconnect,
712
+ openCamera,
713
+ closeCamera,
714
+ loadVideoFile,
715
+ clearInput,
716
+ uploadImage,
717
+ setRefImage,
718
+ start,
719
+ sendTracks,
720
+ stop,
721
+ changeModel,
722
+ bindLocalView,
723
+ bindRemoteView
724
+ ]);
725
+ return /* @__PURE__ */ jsx(XmaxContext.Provider, { value, children });
726
+ }
727
+ function useXmax() {
728
+ const ctx = useContext(XmaxContext);
729
+ if (!ctx) {
730
+ throw new Error("useXmax must be used within XmaxProvider");
731
+ }
732
+ return ctx;
733
+ }
734
+ function DragTrackSurface({
735
+ enabled,
736
+ targetSize,
737
+ onTracks,
738
+ className,
739
+ style,
740
+ fitMode = "cover"
741
+ }) {
742
+ const containerRef = useRef(null);
743
+ const controllerRef = useRef(null);
744
+ const onTracksRef = useRef(onTracks);
745
+ onTracksRef.current = onTracks;
746
+ useEffect(() => {
747
+ const container = containerRef.current;
748
+ if (!container) {
749
+ return;
750
+ }
751
+ const controller = createDragTrackController(container, {
752
+ targetSize,
753
+ fitMode,
754
+ enabled,
755
+ onTracks: (tracks) => onTracksRef.current(tracks)
756
+ });
757
+ controllerRef.current = controller;
758
+ return () => {
759
+ controller.destroy();
760
+ controllerRef.current = null;
761
+ };
762
+ }, []);
763
+ useEffect(() => {
764
+ controllerRef.current?.setEnabled(enabled);
765
+ }, [enabled]);
766
+ useEffect(() => {
767
+ controllerRef.current?.setTargetSize(targetSize);
768
+ }, [targetSize]);
769
+ useEffect(() => {
770
+ controllerRef.current?.setFitMode(fitMode);
771
+ }, [fitMode]);
772
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style });
773
+ }
774
+ function XmaxView({
775
+ kind,
776
+ className,
777
+ style
778
+ }) {
779
+ const ref = useRef(null);
780
+ const { bindLocalView, bindRemoteView, snapshot, sendTracks } = useXmax();
781
+ useEffect(() => {
782
+ const el = ref.current;
783
+ if (!el) {
784
+ return;
785
+ }
786
+ if (kind === "local") {
787
+ bindLocalView(el);
788
+ return () => bindLocalView(null);
789
+ }
790
+ bindRemoteView(el);
791
+ return () => bindRemoteView(null);
792
+ }, [bindLocalView, bindRemoteView, kind]);
793
+ if (kind === "remote") {
794
+ const dragTargetSize = snapshot.sessionTargetSize ?? DEFAULT_SESSION_TARGET_SIZE;
795
+ return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", ...style }, children: [
796
+ /* @__PURE__ */ jsx("div", { ref, style: { width: "100%", height: "100%" } }),
797
+ /* @__PURE__ */ jsx(
798
+ DragTrackSurface,
799
+ {
800
+ enabled: snapshot.dragEnabled,
801
+ targetSize: dragTargetSize,
802
+ fitMode: "contain",
803
+ onTracks: (tracks) => {
804
+ void sendTracks({ tracks });
805
+ },
806
+ className: "absolute inset-0 touch-none",
807
+ style: { zIndex: 10 }
808
+ }
809
+ )
810
+ ] });
811
+ }
812
+ return /* @__PURE__ */ jsx("div", { ref, className, style });
813
+ }
814
+
815
+ export { DragTrackSurface, XmaxProvider, XmaxView, useXmax };
816
+ //# sourceMappingURL=index.js.map
817
+ //# sourceMappingURL=index.js.map