@vivix-ai/ivi-frontend-sdk 0.3.7 → 0.3.9

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.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ReceiveConversationItemAddedEvent, ReceiveConversationItemDoneEvent, ReceiveResponseCreatedEvent, IviClient, ReceiveSessionCreatedEvent, ReceiveSessionEndedEvent, ReceiveSessionStageGetResponseEvent, ReceiveSessionStageUpdatedEvent, ReceiveSessionTrackCreatedEvent, ReceiveSessionTrackDeletedEvent, ReceiveSessionTrackTookEvent, ReceiveSessionTrackCuedEvent, ReceiveSessionTrackNextSetEvent, ReceiveSessionTracksListResponseEvent, ReceiveSessionSourceCreatedEvent, ReceiveSessionSourceReadyEvent, ReceiveSessionSourceFailedEvent, ReceiveSessionSourceDeletedEvent, ReceiveSessionSourcePreloadEvent, ReceiveSessionSourceClearPreloadEvent, ReceiveSessionSourcesListResponseEvent, ReceiveSessionSourcePlaybackCompletedEvent, ReceiveSessionStreamCreatedEvent, ReceiveSessionStreamStartedEvent, ReceiveSessionStreamEndedEvent, ReceiveSessionStreamFailedEvent, ReceiveSessionStreamDeletedEvent, ReceiveSessionStreamsListResponseEvent, ReceiveConversationListResponseEvent, ReceiveResponseOutputTextDeltaEvent, ReceiveResponseOutputTextDoneEvent, ReceiveResponseOutputAudioTranscriptDeltaEvent, ReceiveResponseOutputAudioTranscriptDoneEvent, ReceiveResponseDoneEvent } from '@vivix-ai/ivi-sdk-ts';
2
+ import { WebSocketTransport } from '@vivix-ai/ivi-sdk-ts/transports/websocket';
2
3
  import { createContext, useState, useEffect, useMemo, useRef, useContext, useCallback, useId } from 'react';
3
4
  import { jsx, jsxs } from 'react/jsx-runtime';
4
5
 
@@ -620,7 +621,9 @@ var SourceManager = class {
620
621
  durationMs: previous?.durationMs,
621
622
  hasAudio: previous?.hasAudio,
622
623
  error: previous?.status === "failed" ? previous.error : void 0,
623
- preload: previous?.preload
624
+ preload: previous?.preload,
625
+ origin: previous?.origin,
626
+ provisional: previous?.provisional ? false : previous?.provisional
624
627
  });
625
628
  this.sources = next;
626
629
  }
@@ -635,7 +638,38 @@ var SourceManager = class {
635
638
  height: payload.height,
636
639
  durationMs: payload.durationMs,
637
640
  hasAudio: payload.hasAudio,
638
- preload: previous?.preload
641
+ preload: previous?.preload,
642
+ origin: previous?.origin,
643
+ provisional: previous?.provisional ? false : previous?.provisional
644
+ });
645
+ this.sources = next;
646
+ }
647
+ upsertBootstrapReady(payload) {
648
+ const previous = this.sources.get(payload.sourceId);
649
+ const next = new Map(this.sources);
650
+ const source = {
651
+ source_id: payload.sourceId,
652
+ kind: payload.source?.kind ?? (payload.streamId ? "generation_stream" : "stream"),
653
+ stream_id: payload.source?.stream_id ?? payload.streamId,
654
+ asset_type: payload.source?.asset_type ?? "video",
655
+ url: payload.source?.url,
656
+ generation: payload.source?.generation,
657
+ metadata: {
658
+ ...payload.source?.metadata ?? {},
659
+ label: payload.source?.metadata?.label ?? "prebuilt"
660
+ }
661
+ };
662
+ next.set(payload.sourceId, {
663
+ source,
664
+ status: "ready",
665
+ playback: previous?.playback ?? payload.playback,
666
+ width: previous?.width ?? payload.width,
667
+ height: previous?.height ?? payload.height,
668
+ durationMs: previous?.durationMs ?? payload.durationMs,
669
+ hasAudio: previous?.hasAudio ?? payload.hasAudio,
670
+ preload: previous?.preload ?? { autoclearAfterPlay: true },
671
+ origin: previous?.origin ?? "prebuilt-session-response",
672
+ provisional: previous?.provisional ?? true
639
673
  });
640
674
  this.sources = next;
641
675
  }
@@ -669,11 +703,14 @@ var SourceManager = class {
669
703
  const next = /* @__PURE__ */ new Map();
670
704
  sources.forEach((source) => {
671
705
  const previous = this.sources.get(source.source_id);
706
+ const playback = source.playback ?? previous?.playback;
672
707
  next.set(source.source_id, {
673
708
  source,
674
- status: source.playback ? "ready" : "created",
675
- playback: source.playback,
676
- preload: previous?.preload
709
+ status: playback ? "ready" : "created",
710
+ playback,
711
+ preload: previous?.preload,
712
+ origin: previous?.origin,
713
+ provisional: source.playback && previous?.provisional ? false : previous?.provisional
677
714
  });
678
715
  });
679
716
  this.sources = next;
@@ -1105,6 +1142,8 @@ var ConversationManager = class {
1105
1142
 
1106
1143
  // src/runtime/managers/trtc-source-manager.ts
1107
1144
  var TAG = "[IVI-TRTC]";
1145
+ var TRTC_VIEW_ATTR = "data-ivi-trtc-view";
1146
+ var TRTC_MEDIA_STYLE_ID = "ivi-trtc-media-style";
1108
1147
  var DEFAULT_DENOISER_OPTIONS = {
1109
1148
  enabled: true,
1110
1149
  mode: "normal"
@@ -1266,6 +1305,8 @@ var TrtcSourceManager = class {
1266
1305
  if (!session) {
1267
1306
  throw new Error(`TRTC source session not found: ${sourceId}`);
1268
1307
  }
1308
+ ensureTrtcMediaStyleSheet();
1309
+ container.setAttribute(TRTC_VIEW_ATTR, "");
1269
1310
  session.views.set(viewId, {
1270
1311
  sourceId,
1271
1312
  container,
@@ -1273,6 +1314,7 @@ var TrtcSourceManager = class {
1273
1314
  startedVideoKeys: /* @__PURE__ */ new Set(),
1274
1315
  startingVideoKeys: /* @__PURE__ */ new Set()
1275
1316
  });
1317
+ this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
1276
1318
  await this.ensureConnected(session);
1277
1319
  const binding = session.views.get(viewId);
1278
1320
  if (!binding) {
@@ -1288,6 +1330,8 @@ var TrtcSourceManager = class {
1288
1330
  }
1289
1331
  const binding = session.views.get(viewId);
1290
1332
  if (binding?.sourceId === sourceId) {
1333
+ this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
1334
+ binding.container.removeAttribute(TRTC_VIEW_ATTR);
1291
1335
  session.views.delete(viewId);
1292
1336
  }
1293
1337
  void this.applyAudioPolicy(session);
@@ -1304,11 +1348,30 @@ var TrtcSourceManager = class {
1304
1348
  binding.muted = muted;
1305
1349
  void this.applyAudioPolicy(session);
1306
1350
  }
1351
+ reassignView(fromSourceId, toSourceId, viewId) {
1352
+ if (fromSourceId === toSourceId) {
1353
+ return true;
1354
+ }
1355
+ const fromSession = this.getSession(fromSourceId);
1356
+ const toSession = this.getSession(toSourceId);
1357
+ if (!fromSession || !toSession || fromSession !== toSession) {
1358
+ return false;
1359
+ }
1360
+ const binding = fromSession.views.get(viewId);
1361
+ if (!binding || binding.sourceId !== fromSourceId) {
1362
+ return false;
1363
+ }
1364
+ this.log("info", `\u8FC1\u79FB\u89C6\u56FE source=${fromSourceId}->${toSourceId} view=${viewId}`);
1365
+ binding.sourceId = toSourceId;
1366
+ void this.applyAudioPolicy(fromSession);
1367
+ return true;
1368
+ }
1307
1369
  unlinkSourceFromSession(sourceId, session) {
1308
1370
  session.sourceIds.delete(sourceId);
1309
1371
  this.sourceSessionKeys.delete(sourceId);
1310
1372
  session.views.forEach((binding, viewId) => {
1311
1373
  if (binding.sourceId === sourceId) {
1374
+ binding.container.removeAttribute(TRTC_VIEW_ATTR);
1312
1375
  session.views.delete(viewId);
1313
1376
  }
1314
1377
  });
@@ -1477,6 +1540,7 @@ var TrtcSourceManager = class {
1477
1540
  if (!parsed) {
1478
1541
  continue;
1479
1542
  }
1543
+ this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
1480
1544
  await this.startRemoteVideoForBinding(
1481
1545
  client,
1482
1546
  parsed.userId,
@@ -1492,12 +1556,14 @@ var TrtcSourceManager = class {
1492
1556
  }
1493
1557
  binding.startingVideoKeys.add(remoteVideoKey);
1494
1558
  try {
1559
+ this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1495
1560
  await client.startRemoteVideo({
1496
1561
  userId,
1497
1562
  streamType,
1498
1563
  view: binding.container,
1499
1564
  option: { fillMode: "contain" }
1500
1565
  });
1566
+ this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1501
1567
  binding.startedVideoKeys.add(remoteVideoKey);
1502
1568
  enforceContainMedia(binding.container);
1503
1569
  } catch (error) {
@@ -1628,11 +1694,43 @@ var TrtcSourceManager = class {
1628
1694
  this.onTrtcEvent?.(event);
1629
1695
  }
1630
1696
  };
1697
+ function ensureTrtcMediaStyleSheet() {
1698
+ if (typeof document === "undefined") {
1699
+ return;
1700
+ }
1701
+ if (document.getElementById(TRTC_MEDIA_STYLE_ID)) {
1702
+ return;
1703
+ }
1704
+ const style = document.createElement("style");
1705
+ style.id = TRTC_MEDIA_STYLE_ID;
1706
+ style.textContent = `
1707
+ [${TRTC_VIEW_ATTR}] [id^="player_"],
1708
+ [${TRTC_VIEW_ATTR}] [id^="video_"],
1709
+ [${TRTC_VIEW_ATTR}] [id^="audio_"],
1710
+ [${TRTC_VIEW_ATTR}] video,
1711
+ [${TRTC_VIEW_ATTR}] canvas {
1712
+ width: 100% !important;
1713
+ height: 100% !important;
1714
+ max-width: 100% !important;
1715
+ max-height: 100% !important;
1716
+ background: transparent !important;
1717
+ background-color: transparent !important;
1718
+ background-image: none !important;
1719
+ }
1720
+
1721
+ [${TRTC_VIEW_ATTR}] video,
1722
+ [${TRTC_VIEW_ATTR}] canvas {
1723
+ object-fit: contain !important;
1724
+ display: block !important;
1725
+ }
1726
+ `;
1727
+ document.head.appendChild(style);
1728
+ }
1631
1729
  function isRuntimeTrtcSource(source) {
1632
1730
  return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
1633
1731
  }
1634
1732
  function isSameTrtcConfig(a, b) {
1635
- return a.app_id === b.app_id && a.user_id === b.user_id && a.user_sig === b.user_sig && a.room_id === b.room_id;
1733
+ return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
1636
1734
  }
1637
1735
  function buildTrtcSessionKey(trtc, aiDenoiser) {
1638
1736
  const denoiser = resolveAIDenoiserOptions(aiDenoiser);
@@ -1640,7 +1738,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
1640
1738
  appId: trtc.app_id,
1641
1739
  roomId: trtc.room_id,
1642
1740
  userId: trtc.user_id,
1643
- userSig: trtc.user_sig,
1644
1741
  aiDenoiser: {
1645
1742
  enabled: denoiser.enabled,
1646
1743
  mode: denoiser.mode,
@@ -1719,6 +1816,9 @@ function enforceContainMedia(container) {
1719
1816
  element.style.setProperty("max-height", "100%", "important");
1720
1817
  element.style.setProperty("object-fit", "contain", "important");
1721
1818
  element.style.setProperty("display", "block", "important");
1819
+ element.style.setProperty("background", "transparent", "important");
1820
+ element.style.setProperty("background-color", "transparent", "important");
1821
+ element.style.setProperty("background-image", "none", "important");
1722
1822
  });
1723
1823
  }
1724
1824
 
@@ -1746,8 +1846,9 @@ function describeLivekitRoom(livekit) {
1746
1846
  // src/runtime/managers/livekit-source-manager.ts
1747
1847
  var TAG2 = "[IVI-LIVEKIT]";
1748
1848
  var LivekitSourceManager = class {
1749
- constructor(onLog) {
1849
+ constructor(onLog, onRemoteVideoAvailable) {
1750
1850
  this.onLog = onLog;
1851
+ this.onRemoteVideoAvailable = onRemoteVideoAvailable;
1751
1852
  this.sessions = /* @__PURE__ */ new Map();
1752
1853
  this.listeners = /* @__PURE__ */ new Map();
1753
1854
  }
@@ -1974,6 +2075,7 @@ var LivekitSourceManager = class {
1974
2075
  waiter(true);
1975
2076
  }
1976
2077
  session.remoteVideoWaiters.length = 0;
2078
+ this.onRemoteVideoAvailable?.(session.sourceId);
1977
2079
  }
1978
2080
  session.views.forEach((binding) => {
1979
2081
  this.attachTrackToView(track, kind, trackKey, binding);
@@ -2169,6 +2271,7 @@ var IviRuntimeCoordinator = class {
2169
2271
  this.userTextFlowCounter = 0;
2170
2272
  this.waitingTracksListValidation = false;
2171
2273
  this.waitingSourcesListValidation = false;
2274
+ this.bootstrapSource = null;
2172
2275
  this.state = {
2173
2276
  status: "idle",
2174
2277
  session: null,
@@ -2177,7 +2280,8 @@ var IviRuntimeCoordinator = class {
2177
2280
  sources: /* @__PURE__ */ new Map(),
2178
2281
  streams: /* @__PURE__ */ new Map(),
2179
2282
  conversationItems: /* @__PURE__ */ new Map(),
2180
- conversations: []
2283
+ conversations: [],
2284
+ bootstrap: null
2181
2285
  };
2182
2286
  this.client = client;
2183
2287
  this.config = {
@@ -2186,10 +2290,12 @@ var IviRuntimeCoordinator = class {
2186
2290
  };
2187
2291
  this.trtcSourceManager = new TrtcSourceManager(
2188
2292
  this.config.onLog,
2189
- (event) => this.emitTrtcEvent(event),
2293
+ (event) => this.onTrtcManagerEvent(event),
2190
2294
  this.config.trtcAIDenoiser
2191
2295
  );
2192
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
2296
+ this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
2297
+ this.recomposeBootstrapState();
2298
+ });
2193
2299
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2194
2300
  onSessionCreated: (event) => this.onSessionCreated(event),
2195
2301
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2270,6 +2376,22 @@ var IviRuntimeCoordinator = class {
2270
2376
  emitLog(entry) {
2271
2377
  this.config.onLog?.(entry);
2272
2378
  }
2379
+ setBootstrapSource(source) {
2380
+ const previous = this.bootstrapSource;
2381
+ if (previous && (!source || previous.sourceId !== source.sourceId)) {
2382
+ const existing = this.sourceManager.get(previous.sourceId);
2383
+ if (existing?.origin === "prebuilt-session-response" && existing.provisional) {
2384
+ this.sourceManager.remove(previous.sourceId);
2385
+ }
2386
+ }
2387
+ this.bootstrapSource = source;
2388
+ this.ensureBootstrapSourceRegistered();
2389
+ this.syncPlaybackManagers();
2390
+ this.setState({
2391
+ ...this.state,
2392
+ sources: this.sourceManager.getAll()
2393
+ });
2394
+ }
2273
2395
  /**
2274
2396
  * 编排一次"用户文本输入 -> 触发模型回复"链路。
2275
2397
  *
@@ -2390,10 +2512,10 @@ var IviRuntimeCoordinator = class {
2390
2512
  this.trackManager.reset();
2391
2513
  this.sourceManager.reset();
2392
2514
  this.streamManager.reset();
2393
- this.trtcSourceManager.reset();
2394
- this.livekitSourceManager.reset();
2395
2515
  this.conversationManager.reset();
2396
2516
  this.reset();
2517
+ this.ensureBootstrapSourceRegistered();
2518
+ this.syncPlaybackManagers();
2397
2519
  const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
2398
2520
  this.setState({
2399
2521
  status: nextStatus,
@@ -2431,8 +2553,8 @@ var IviRuntimeCoordinator = class {
2431
2553
  onTracksChanged(listRefreshed) {
2432
2554
  const nextStage = this.stageManager.getStage();
2433
2555
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2434
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2435
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2556
+ this.ensureBootstrapSourceRegistered();
2557
+ this.syncPlaybackManagers();
2436
2558
  this.setState({
2437
2559
  ...this.state,
2438
2560
  tracks: this.trackManager.getAll(),
@@ -2446,8 +2568,8 @@ var IviRuntimeCoordinator = class {
2446
2568
  applyLocalTrackTake(trackId) {
2447
2569
  this.trackManager.applyTrackTook(trackId);
2448
2570
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2449
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2450
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2571
+ this.ensureBootstrapSourceRegistered();
2572
+ this.syncPlaybackManagers();
2451
2573
  this.setState({
2452
2574
  ...this.state,
2453
2575
  tracks: this.trackManager.getAll(),
@@ -2487,8 +2609,8 @@ var IviRuntimeCoordinator = class {
2487
2609
  }
2488
2610
  onSourcesChanged(listRefreshed) {
2489
2611
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2490
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2491
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2612
+ this.ensureBootstrapSourceRegistered();
2613
+ this.syncPlaybackManagers();
2492
2614
  this.setState({
2493
2615
  ...this.state,
2494
2616
  sources: this.sourceManager.getAll()
@@ -2511,12 +2633,101 @@ var IviRuntimeCoordinator = class {
2511
2633
  });
2512
2634
  }
2513
2635
  setState(nextState) {
2514
- if (this.state.status === nextState.status && this.state.session === nextState.session && this.state.stage === nextState.stage && this.state.tracks === nextState.tracks && this.state.sources === nextState.sources && this.state.streams === nextState.streams && this.state.conversationItems === nextState.conversationItems && this.state.conversations === nextState.conversations) {
2636
+ let resolvedNextState = nextState;
2637
+ if (this.bootstrapSource && nextState.status !== "stopped" && !nextState.sources.has(this.bootstrapSource.sourceId)) {
2638
+ this.ensureBootstrapSourceRegistered();
2639
+ resolvedNextState = {
2640
+ ...nextState,
2641
+ sources: this.sourceManager.getAll()
2642
+ };
2643
+ }
2644
+ const composedNextState = this.composeBootstrapState(resolvedNextState);
2645
+ if (this.state.status === composedNextState.status && this.state.session === composedNextState.session && this.state.stage === composedNextState.stage && this.state.tracks === composedNextState.tracks && this.state.sources === composedNextState.sources && this.state.streams === composedNextState.streams && this.state.conversationItems === composedNextState.conversationItems && this.state.conversations === composedNextState.conversations && isSameBootstrapState(this.state.bootstrap, composedNextState.bootstrap)) {
2515
2646
  return;
2516
2647
  }
2517
- this.state = nextState;
2648
+ this.state = composedNextState;
2518
2649
  this.stateListeners.forEach((listener) => listener(this.state));
2519
2650
  }
2651
+ composeBootstrapState(nextState) {
2652
+ const bootstrap = this.bootstrapSource;
2653
+ if (!bootstrap) {
2654
+ return nextState.bootstrap === null || nextState.bootstrap === void 0 ? nextState : { ...nextState, bootstrap: null };
2655
+ }
2656
+ const shouldExpose = this.shouldExposeBootstrap(nextState, bootstrap);
2657
+ const bootstrapState = {
2658
+ active: shouldExpose,
2659
+ slot: bootstrap.slot,
2660
+ trackId: bootstrap.trackId,
2661
+ sourceId: bootstrap.sourceId,
2662
+ streamId: bootstrap.streamId
2663
+ };
2664
+ if (!shouldExpose) {
2665
+ return {
2666
+ ...nextState,
2667
+ bootstrap: bootstrapState
2668
+ };
2669
+ }
2670
+ const tracks = new Map(nextState.tracks);
2671
+ tracks.set(bootstrap.trackId, {
2672
+ track_id: bootstrap.trackId,
2673
+ active_source_id: bootstrap.sourceId
2674
+ });
2675
+ return {
2676
+ ...nextState,
2677
+ tracks,
2678
+ bootstrap: bootstrapState
2679
+ };
2680
+ }
2681
+ shouldExposeBootstrap(state, bootstrap) {
2682
+ if (state.status === "stopped") {
2683
+ return false;
2684
+ }
2685
+ const bootstrapRuntimeSource = state.sources.get(bootstrap.sourceId);
2686
+ if (!isReadyRenderableSource(bootstrapRuntimeSource)) {
2687
+ return false;
2688
+ }
2689
+ const layer = (state.stage?.composition ?? []).find((item) => item.slot === bootstrap.slot);
2690
+ if (!layer) {
2691
+ return true;
2692
+ }
2693
+ const track = state.tracks.get(layer.track_id);
2694
+ const activeSourceId = track?.active_source_id;
2695
+ if (!activeSourceId || activeSourceId === bootstrap.sourceId) {
2696
+ return true;
2697
+ }
2698
+ return !this.isReadyForBootstrapHandoff(activeSourceId, state.sources.get(activeSourceId));
2699
+ }
2700
+ isReadyForBootstrapHandoff(sourceId, source) {
2701
+ if (!isReadyRenderableSource(source)) {
2702
+ return false;
2703
+ }
2704
+ if (isTrtcPlaybackSource(source)) {
2705
+ return this.trtcSourceManager.hasRemoteVideoAvailable(sourceId);
2706
+ }
2707
+ if (isLivekitPlaybackSource(source)) {
2708
+ return this.livekitSourceManager.hasRemoteVideoAvailable(sourceId);
2709
+ }
2710
+ return true;
2711
+ }
2712
+ recomposeBootstrapState() {
2713
+ if (!this.bootstrapSource) {
2714
+ return;
2715
+ }
2716
+ this.setState({ ...this.state });
2717
+ }
2718
+ ensureBootstrapSourceRegistered() {
2719
+ if (!this.bootstrapSource) {
2720
+ return;
2721
+ }
2722
+ if (this.sourceManager.has(this.bootstrapSource.sourceId)) {
2723
+ return;
2724
+ }
2725
+ this.sourceManager.upsertBootstrapReady(this.bootstrapSource);
2726
+ }
2727
+ syncPlaybackManagers() {
2728
+ this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2729
+ this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2730
+ }
2520
2731
  emitEvent(event) {
2521
2732
  this.progressUserTextToResponseFlows(event);
2522
2733
  this.eventListeners.forEach((listener) => listener(event, this.state));
@@ -2525,6 +2736,12 @@ var IviRuntimeCoordinator = class {
2525
2736
  this.config.onTrtcEvent?.(event);
2526
2737
  this.trtcEventListeners.forEach((listener) => listener(event));
2527
2738
  }
2739
+ onTrtcManagerEvent(event) {
2740
+ this.emitTrtcEvent(event);
2741
+ if (event.type === "remote_video_available") {
2742
+ this.recomposeBootstrapState();
2743
+ }
2744
+ }
2528
2745
  resetStoppedState() {
2529
2746
  this.rejectPendingUserTextToResponseFlows(
2530
2747
  new Error("Runtime stopped before user text to response flow completed.")
@@ -2689,7 +2906,331 @@ function isLivekitPlaybackSource(source) {
2689
2906
  if (!source || !source.playback) return false;
2690
2907
  return isLivekitSourcePlayback(source.playback);
2691
2908
  }
2909
+ function isReadyRenderableSource(source) {
2910
+ return Boolean(source && source.status === "ready" && source.playback);
2911
+ }
2912
+ function isSameBootstrapState(a, b) {
2913
+ if (!a && !b) return true;
2914
+ if (!a || !b) return false;
2915
+ return a.active === b.active && a.slot === b.slot && a.trackId === b.trackId && a.sourceId === b.sourceId && a.streamId === b.streamId;
2916
+ }
2917
+ var IviCreateIVISessionError = class extends Error {
2918
+ constructor(response, body) {
2919
+ super(`CreateIVISession failed with ${response.status} ${response.statusText}`);
2920
+ this.name = "IviCreateIVISessionError";
2921
+ this.status = response.status;
2922
+ this.statusText = response.statusText;
2923
+ this.body = body;
2924
+ }
2925
+ };
2926
+ async function createIVISession(request, options) {
2927
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2928
+ if (!fetchImpl) {
2929
+ throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
2930
+ }
2931
+ const requestBody = toCpCreateIVISessionRequestBody(request);
2932
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
2933
+ method: "POST",
2934
+ headers: await buildHeaders(options.headers),
2935
+ body: JSON.stringify(requestBody),
2936
+ credentials: options.credentials,
2937
+ signal: options.signal
2938
+ });
2939
+ const responseBody = await readJsonResponse(response);
2940
+ if (!response.ok) {
2941
+ throw new IviCreateIVISessionError(response, responseBody);
2942
+ }
2943
+ return normalizeCreateIVISessionResponse(responseBody, request, requestBody);
2944
+ }
2945
+ function createRuntimeFromSession(session, options = {}) {
2946
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
2947
+ const client = new IviClient({
2948
+ ...options.clientConfig ?? {},
2949
+ transport: new WebSocketTransport({
2950
+ url: websocketUrl,
2951
+ sessionId: session.iviSessionId,
2952
+ protocols: options.websocket?.protocols,
2953
+ socketFactory: options.websocket?.socketFactory
2954
+ })
2955
+ });
2956
+ const runtime = new IviRuntimeCoordinator(client, options.runtimeConfig);
2957
+ if (options.fastStart !== false && session.prebuiltPlayback) {
2958
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
2959
+ }
2960
+ return {
2961
+ session,
2962
+ client,
2963
+ runtime
2964
+ };
2965
+ }
2966
+ async function createIVISessionRuntime(request, options) {
2967
+ const session = await createIVISession(request, options.http);
2968
+ return createRuntimeFromSession(session, options);
2969
+ }
2970
+ function toCpCreateIVISessionRequestBody(request) {
2971
+ const body = { ...request };
2972
+ delete body.idempotencyKey;
2973
+ delete body.streamType;
2974
+ delete body.iviVersion;
2975
+ delete body.sessionParameters;
2976
+ delete body.enableDecoderPublish;
2977
+ delete body.sessionRecording;
2978
+ delete body.prebuiltCharacters;
2979
+ delete body.prebuiltStream;
2980
+ if (request.idempotencyKey !== void 0) body.idempotency_key = request.idempotencyKey;
2981
+ if (request.streamType !== void 0) body.stream_type = request.streamType;
2982
+ if (request.iviVersion !== void 0) body.ivi_version = request.iviVersion;
2983
+ if (request.sessionParameters !== void 0) body.session_parameters = request.sessionParameters;
2984
+ if (request.enableDecoderPublish !== void 0) body.enable_decoder_publish = request.enableDecoderPublish;
2985
+ if (request.sessionRecording !== void 0) {
2986
+ body.session_recording = {
2987
+ enabled: request.sessionRecording.enabled,
2988
+ aspect_ratio: request.sessionRecording.aspectRatio
2989
+ };
2990
+ }
2991
+ if (request.prebuiltCharacters !== void 0) {
2992
+ body.prebuilt_characters = request.prebuiltCharacters.map((character) => ({
2993
+ character_id: character.characterId,
2994
+ character_payload: serializePrebuiltPayload(character.characterPayload)
2995
+ }));
2996
+ }
2997
+ if (request.prebuiltStream !== void 0) {
2998
+ body.prebuilt_stream = {
2999
+ stream_id: request.prebuiltStream.streamId,
3000
+ mode: request.prebuiltStream.mode,
3001
+ stream_config: serializePrebuiltPayload(request.prebuiltStream.streamConfig)
3002
+ };
3003
+ }
3004
+ return body;
3005
+ }
3006
+ function normalizeCreateIVISessionResponse(responseBody, request = {}, requestBody = toCpCreateIVISessionRequestBody(request)) {
3007
+ const payload = unwrapResponsePayload(responseBody);
3008
+ const iviSessionId = getString(payload, "ivi_session_id") ?? getString(payload, "iviSessionId");
3009
+ const endpoint = getString(payload, "endpoint");
3010
+ if (!iviSessionId) {
3011
+ throw new Error("CreateIVISession response missing ivi_session_id.");
3012
+ }
3013
+ if (!endpoint) {
3014
+ throw new Error("CreateIVISession response missing endpoint.");
3015
+ }
3016
+ const trtcInfo = normalizeTrtcInfo(getObjectValue(payload, "trtc_info") ?? getObjectValue(payload, "trtcInfo"));
3017
+ const livekitInfo = normalizeLivekitInfo(getObjectValue(payload, "livekit_info") ?? getObjectValue(payload, "livekitInfo"));
3018
+ const prebuildInfo = getObjectValue(payload, "prebuild_trtc_info") ?? getObjectValue(payload, "prebuildTrtcInfo");
3019
+ const prebuildTrtcInfo = normalizeTrtcInfo(
3020
+ getObjectValue(prebuildInfo, "trtc_info") ?? getObjectValue(prebuildInfo, "trtcInfo")
3021
+ );
3022
+ const prebuildLivekitInfo = normalizeLivekitInfo(
3023
+ getObjectValue(prebuildInfo, "livekit_info") ?? getObjectValue(prebuildInfo, "livekitInfo")
3024
+ );
3025
+ const prebuiltPlayback = buildPrebuiltPlayback(
3026
+ request,
3027
+ requestBody,
3028
+ prebuildTrtcInfo,
3029
+ prebuildLivekitInfo
3030
+ );
3031
+ return {
3032
+ iviSessionId,
3033
+ endpoint,
3034
+ tokenInfo: getObjectValue(payload, "token_info") ?? getObjectValue(payload, "tokenInfo"),
3035
+ trtcInfo,
3036
+ livekitInfo,
3037
+ prebuildTrtcInfo: prebuildTrtcInfo || prebuildLivekitInfo ? {
3038
+ trtcInfo: prebuildTrtcInfo,
3039
+ livekitInfo: prebuildLivekitInfo
3040
+ } : void 0,
3041
+ prebuiltPlayback,
3042
+ raw: responseBody,
3043
+ requestBody
3044
+ };
3045
+ }
3046
+ function resolveCreateIVISessionUrl(options) {
3047
+ if (options.url) return options.url;
3048
+ if (!options.baseUrl) {
3049
+ throw new Error("CreateIVISession requires options.baseUrl or options.url.");
3050
+ }
3051
+ const path = options.path ?? "/v1/ivi/sessions";
3052
+ return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3053
+ }
3054
+ async function buildHeaders(headers) {
3055
+ const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3056
+ const next = new Headers(resolvedHeaders);
3057
+ if (!next.has("content-type")) {
3058
+ next.set("content-type", "application/json");
3059
+ }
3060
+ return next;
3061
+ }
3062
+ async function readJsonResponse(response) {
3063
+ const text = await response.text();
3064
+ if (!text) {
3065
+ return null;
3066
+ }
3067
+ try {
3068
+ return JSON.parse(text);
3069
+ } catch {
3070
+ return text;
3071
+ }
3072
+ }
3073
+ function buildWebSocketUrl(endpoint, options) {
3074
+ const rawUrl = options?.url ?? endpoint;
3075
+ if (!options?.query) {
3076
+ return rawUrl;
3077
+ }
3078
+ const url = new URL(rawUrl);
3079
+ Object.entries(options.query).forEach(([key, value]) => {
3080
+ if (value === void 0 || value === null) return;
3081
+ url.searchParams.set(key, value);
3082
+ });
3083
+ return url.toString();
3084
+ }
3085
+ function toBootstrapSource(prebuiltPlayback, options) {
3086
+ return {
3087
+ sourceId: prebuiltPlayback.sourceId,
3088
+ streamId: prebuiltPlayback.streamId,
3089
+ slot: options.bootstrapSlot ?? "main",
3090
+ trackId: options.bootstrapTrackId ?? "__ivi_bootstrap_track",
3091
+ playback: prebuiltPlayback.playback,
3092
+ source: {
3093
+ source_id: prebuiltPlayback.sourceId,
3094
+ kind: prebuiltPlayback.streamId ? "generation_stream" : "stream",
3095
+ stream_id: prebuiltPlayback.streamId,
3096
+ asset_type: "video",
3097
+ metadata: {
3098
+ label: "prebuilt"
3099
+ }
3100
+ }
3101
+ };
3102
+ }
3103
+ function buildPrebuiltPlayback(request, requestBody, trtcInfo, livekitInfo) {
3104
+ if (!trtcInfo && !livekitInfo) {
3105
+ return void 0;
3106
+ }
3107
+ const streamId = resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo);
3108
+ const sourceId = request.prebuiltStream?.sourceId ?? streamId;
3109
+ if (!sourceId) {
3110
+ return void 0;
3111
+ }
3112
+ if (trtcInfo) {
3113
+ return {
3114
+ sourceId,
3115
+ streamId,
3116
+ kind: "trtc",
3117
+ playback: {
3118
+ type: "trtc",
3119
+ trtc: trtcInfo
3120
+ },
3121
+ provisional: true
3122
+ };
3123
+ }
3124
+ if (livekitInfo) {
3125
+ return {
3126
+ sourceId,
3127
+ streamId,
3128
+ kind: "livekit",
3129
+ playback: {
3130
+ type: "livekit",
3131
+ livekit: livekitInfo
3132
+ },
3133
+ provisional: true
3134
+ };
3135
+ }
3136
+ return void 0;
3137
+ }
3138
+ function resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo) {
3139
+ const prebuiltStream = getObjectValue(requestBody, "prebuilt_stream");
3140
+ return request.prebuiltStream?.streamId ?? getString(prebuiltStream, "stream_id") ?? getString(prebuiltStream, "streamId") ?? trtcInfo?.room_id ?? livekitInfo?.room;
3141
+ }
3142
+ function normalizeTrtcInfo(value) {
3143
+ if (!isRecord(value)) {
3144
+ return void 0;
3145
+ }
3146
+ const appId = getString(value, "app_id") ?? getString(value, "appId");
3147
+ const roomId = getString(value, "room_id") ?? getString(value, "roomId");
3148
+ const userId = getString(value, "user_id") ?? getString(value, "userId");
3149
+ const userSig = getString(value, "user_sig") ?? getString(value, "userSig");
3150
+ if (!appId || !roomId || !userId || !userSig) {
3151
+ return void 0;
3152
+ }
3153
+ const normalized = {
3154
+ ...value,
3155
+ app_id: appId,
3156
+ room_id: roomId,
3157
+ user_id: userId,
3158
+ user_sig: userSig
3159
+ };
3160
+ delete normalized.secret_key;
3161
+ delete normalized.secretKey;
3162
+ return normalized;
3163
+ }
3164
+ function normalizeLivekitInfo(value) {
3165
+ if (!isRecord(value)) {
3166
+ return void 0;
3167
+ }
3168
+ const wsUrl = getString(value, "ws_url") ?? getString(value, "url") ?? getString(value, "wsUrl");
3169
+ const token = getString(value, "token") ?? getString(value, "access_token") ?? getString(value, "accessToken");
3170
+ if (!wsUrl || !token) {
3171
+ return void 0;
3172
+ }
3173
+ return {
3174
+ ws_url: wsUrl,
3175
+ token,
3176
+ room: getString(value, "room") ?? getString(value, "room_name") ?? getString(value, "roomName"),
3177
+ identity: getString(value, "identity")
3178
+ };
3179
+ }
3180
+ function serializePrebuiltPayload(value) {
3181
+ if (typeof value === "string") {
3182
+ return value;
3183
+ }
3184
+ return encodeJsonBase64(value);
3185
+ }
3186
+ function encodeJsonBase64(value) {
3187
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
3188
+ let binary = "";
3189
+ bytes.forEach((byte) => {
3190
+ binary += String.fromCharCode(byte);
3191
+ });
3192
+ if (typeof btoa === "function") {
3193
+ return btoa(binary);
3194
+ }
3195
+ const maybeBuffer = globalThis.Buffer;
3196
+ if (maybeBuffer) {
3197
+ return maybeBuffer.from(bytes).toString("base64");
3198
+ }
3199
+ throw new Error("Base64 encoder is not available in this runtime.");
3200
+ }
3201
+ function unwrapResponsePayload(value) {
3202
+ if (!isRecord(value)) {
3203
+ return {};
3204
+ }
3205
+ const data = getObjectValue(value, "data");
3206
+ return data ?? value;
3207
+ }
3208
+ function getObjectValue(value, key) {
3209
+ if (!isRecord(value)) {
3210
+ return void 0;
3211
+ }
3212
+ const nested = value[key];
3213
+ return isRecord(nested) ? nested : void 0;
3214
+ }
3215
+ function getString(value, key) {
3216
+ if (!isRecord(value)) {
3217
+ return void 0;
3218
+ }
3219
+ const nested = value[key];
3220
+ return typeof nested === "string" && nested.trim().length > 0 ? nested.trim() : void 0;
3221
+ }
3222
+ function isRecord(value) {
3223
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3224
+ }
3225
+ function ensureTrailingSlash(value) {
3226
+ return value.endsWith("/") ? value : `${value}/`;
3227
+ }
3228
+
3229
+ // src/sdk.ts
2692
3230
  var IviFrontendSdk = class {
3231
+ constructor(config = {}) {
3232
+ this.config = config;
3233
+ }
2693
3234
  createClient(config) {
2694
3235
  return new IviClient(config);
2695
3236
  }
@@ -2697,6 +3238,25 @@ var IviFrontendSdk = class {
2697
3238
  const client = this.createClient(clientConfig);
2698
3239
  return new IviRuntimeCoordinator(client, runtimeConfig);
2699
3240
  }
3241
+ createIVISession(request, options) {
3242
+ return createIVISession(request, {
3243
+ ...this.config.http ?? {},
3244
+ ...options ?? {}
3245
+ });
3246
+ }
3247
+ createRuntimeFromSession(session, options) {
3248
+ return createRuntimeFromSession(session, options);
3249
+ }
3250
+ createIVISessionRuntime(request, options) {
3251
+ const http = {
3252
+ ...this.config.http ?? {},
3253
+ ...options?.http ?? {}
3254
+ };
3255
+ return createIVISessionRuntime(request, {
3256
+ ...options ?? {},
3257
+ http
3258
+ });
3259
+ }
2700
3260
  };
2701
3261
  var IviStageViewContext = createContext(null);
2702
3262
  var EMPTY_RUNTIME_STATE = {
@@ -2707,7 +3267,8 @@ var EMPTY_RUNTIME_STATE = {
2707
3267
  sources: /* @__PURE__ */ new Map(),
2708
3268
  streams: /* @__PURE__ */ new Map(),
2709
3269
  conversationItems: /* @__PURE__ */ new Map(),
2710
- conversations: []
3270
+ conversations: [],
3271
+ bootstrap: null
2711
3272
  };
2712
3273
  function useRuntimeState(runtime) {
2713
3274
  const [state, setState] = useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
@@ -2735,10 +3296,10 @@ function IVIStageView(props) {
2735
3296
  const state = useRuntimeState(runtime);
2736
3297
  const slotTrackMap = useMemo(() => {
2737
3298
  return buildSlotTrackMapFromState(state);
2738
- }, [state.stage]);
3299
+ }, [state.stage, state.bootstrap]);
2739
3300
  const slotBindings = useMemo(() => {
2740
3301
  return buildSlotBindingsFromState(state);
2741
- }, [state.stage, state.tracks, state.sources]);
3302
+ }, [state.stage, state.tracks, state.sources, state.bootstrap]);
2742
3303
  useEffect(() => {
2743
3304
  onBindingsChange?.(slotBindings);
2744
3305
  }, [slotBindings, onBindingsChange]);
@@ -2778,15 +3339,18 @@ function buildSlotTrackMapFromState(state) {
2778
3339
  (state.stage?.composition ?? []).forEach((item) => {
2779
3340
  map.set(item.slot, item.track_id);
2780
3341
  });
3342
+ if (state.bootstrap?.active) {
3343
+ map.set(state.bootstrap.slot, state.bootstrap.trackId);
3344
+ }
2781
3345
  return map;
2782
3346
  }
2783
3347
  function buildSlotBindingsFromState(state) {
2784
- const bindings = [];
3348
+ const bindingsBySlot = /* @__PURE__ */ new Map();
2785
3349
  (state.stage?.composition ?? []).forEach((item) => {
2786
3350
  const track = state.tracks.get(item.track_id);
2787
3351
  const sourceId = track?.active_source_id ?? null;
2788
3352
  const source = sourceId ? state.sources.get(sourceId) : void 0;
2789
- bindings.push({
3353
+ bindingsBySlot.set(item.slot, {
2790
3354
  slot: item.slot,
2791
3355
  trackId: item.track_id,
2792
3356
  track,
@@ -2794,7 +3358,18 @@ function buildSlotBindingsFromState(state) {
2794
3358
  source
2795
3359
  });
2796
3360
  });
2797
- return bindings;
3361
+ if (state.bootstrap?.active) {
3362
+ const track = state.tracks.get(state.bootstrap.trackId);
3363
+ const source = state.sources.get(state.bootstrap.sourceId);
3364
+ bindingsBySlot.set(state.bootstrap.slot, {
3365
+ slot: state.bootstrap.slot,
3366
+ trackId: state.bootstrap.trackId,
3367
+ track,
3368
+ sourceId: state.bootstrap.sourceId,
3369
+ source
3370
+ });
3371
+ }
3372
+ return Array.from(bindingsBySlot.values());
2798
3373
  }
2799
3374
  var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
2800
3375
  var DEFAULT_VOLUME = 80;
@@ -3562,23 +4137,25 @@ function IVITrtcPlayer(props) {
3562
4137
  const [loading, setLoading] = useState(true);
3563
4138
  const [error, setError] = useState(null);
3564
4139
  const mutedRef = useRef(muted);
4140
+ const attachedSourceIdRef = useRef(null);
3565
4141
  mutedRef.current = muted;
4142
+ useEffect(() => {
4143
+ const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
4144
+ setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
4145
+ setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
4146
+ });
4147
+ return unsubscribe;
4148
+ }, [manager, resolvedSourceId]);
3566
4149
  useEffect(() => {
3567
4150
  const container = containerRef.current;
3568
4151
  if (!container) {
3569
4152
  return;
3570
4153
  }
3571
4154
  let disposed = false;
4155
+ attachedSourceIdRef.current = resolvedSourceId;
3572
4156
  if (shouldManageSourceLifecycle) {
3573
4157
  manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
3574
4158
  }
3575
- const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
3576
- if (disposed) {
3577
- return;
3578
- }
3579
- setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
3580
- setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
3581
- });
3582
4159
  void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
3583
4160
  if (disposed) {
3584
4161
  return;
@@ -3588,15 +4165,15 @@ function IVITrtcPlayer(props) {
3588
4165
  });
3589
4166
  return () => {
3590
4167
  disposed = true;
3591
- unsubscribe();
3592
- manager.detachView(resolvedSourceId, viewIdRef.current);
4168
+ const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
4169
+ manager.detachView(attachedSourceId, viewIdRef.current);
3593
4170
  if (shouldManageSourceLifecycle) {
3594
- manager.removeSource(resolvedSourceId);
4171
+ manager.removeSource(attachedSourceId);
3595
4172
  }
4173
+ attachedSourceIdRef.current = null;
3596
4174
  };
3597
4175
  }, [
3598
4176
  manager,
3599
- resolvedSourceId,
3600
4177
  shouldManageSourceLifecycle,
3601
4178
  trtc.app_id,
3602
4179
  trtc.room_id,
@@ -3604,6 +4181,15 @@ function IVITrtcPlayer(props) {
3604
4181
  trtc.user_sig,
3605
4182
  trtcAIDenoiser
3606
4183
  ]);
4184
+ useEffect(() => {
4185
+ const previousSourceId = attachedSourceIdRef.current;
4186
+ if (!previousSourceId || previousSourceId === resolvedSourceId) {
4187
+ return;
4188
+ }
4189
+ if (manager.reassignView(previousSourceId, resolvedSourceId, viewIdRef.current)) {
4190
+ attachedSourceIdRef.current = resolvedSourceId;
4191
+ }
4192
+ }, [manager, resolvedSourceId]);
3607
4193
  useEffect(() => {
3608
4194
  manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
3609
4195
  }, [manager, muted, resolvedSourceId]);
@@ -3616,7 +4202,6 @@ function IVITrtcPlayer(props) {
3616
4202
  height: "100%",
3617
4203
  minWidth: 0,
3618
4204
  minHeight: 0,
3619
- backgroundColor: "#000",
3620
4205
  position: "relative",
3621
4206
  ...style
3622
4207
  },
@@ -4112,13 +4697,7 @@ function getSourceRenderKey(sourceId, source) {
4112
4697
  if (!trtc) {
4113
4698
  return sourceId;
4114
4699
  }
4115
- return [
4116
- "trtc",
4117
- trtc.app_id ?? "",
4118
- trtc.room_id ?? "",
4119
- trtc.user_id ?? "",
4120
- trtc.user_sig ?? ""
4121
- ].join(":");
4700
+ return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
4122
4701
  }
4123
4702
  function TrackSlotMediaContent(props) {
4124
4703
  const {
@@ -4574,7 +5153,83 @@ function getClientLogTag(category) {
4574
5153
  if (category === "reconnect") return "[IVI-RECONNECT]";
4575
5154
  return "[IVI-CLIENT]";
4576
5155
  }
5156
+ function useIviSessionRuntime(config) {
5157
+ const [status, setStatus] = useState("idle");
5158
+ const [session, setSession] = useState(null);
5159
+ const [runtime, setRuntime] = useState(null);
5160
+ const [client, setClient] = useState(null);
5161
+ const [error, setError] = useState(null);
5162
+ useEffect(() => {
5163
+ const {
5164
+ request,
5165
+ enabled = true,
5166
+ autoStart = true,
5167
+ onCreated,
5168
+ onRuntimeReady,
5169
+ onError,
5170
+ ...options
5171
+ } = config;
5172
+ if (!enabled || !request) {
5173
+ setStatus("idle");
5174
+ setSession(null);
5175
+ setRuntime(null);
5176
+ setClient(null);
5177
+ setError(null);
5178
+ return;
5179
+ }
5180
+ let disposed = false;
5181
+ let activeRuntime = null;
5182
+ setStatus("creating");
5183
+ setSession(null);
5184
+ setRuntime(null);
5185
+ setClient(null);
5186
+ setError(null);
5187
+ void createIVISessionRuntime(request, options).then(async (created) => {
5188
+ if (disposed) {
5189
+ created.runtime.stop();
5190
+ return;
5191
+ }
5192
+ activeRuntime = created.runtime;
5193
+ setSession(created.session);
5194
+ setRuntime(created.runtime);
5195
+ setClient(created.client);
5196
+ onCreated?.(created.session);
5197
+ onRuntimeReady?.(created.runtime, created.client);
5198
+ if (!autoStart) {
5199
+ setStatus("idle");
5200
+ return;
5201
+ }
5202
+ setStatus("connecting");
5203
+ await created.runtime.start();
5204
+ if (disposed) {
5205
+ created.runtime.stop();
5206
+ return;
5207
+ }
5208
+ setStatus("running");
5209
+ }).catch((caughtError) => {
5210
+ if (disposed) {
5211
+ return;
5212
+ }
5213
+ const normalizedError = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
5214
+ setError(normalizedError);
5215
+ setStatus("error");
5216
+ onError?.(caughtError);
5217
+ });
5218
+ return () => {
5219
+ disposed = true;
5220
+ activeRuntime?.stop();
5221
+ setStatus("stopped");
5222
+ };
5223
+ }, [config]);
5224
+ return {
5225
+ status,
5226
+ session,
5227
+ runtime,
5228
+ client,
5229
+ error
5230
+ };
5231
+ }
4577
5232
 
4578
- export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
5233
+ export { DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS, EMPTY_RUNTIME_STATE, IVILivekitPlayer, IVIStageView, IVISubtitleOverlay, IVITrackSlot, IVITrtcPlayer, IviCreateIVISessionError, IviFrontendSdk, IviRuntimeCoordinator, IviRuntimeDispatcher, LivekitSourceManager, TrtcSourceManager, createIVISession, createIVISessionRuntime, createRuntimeFromSession, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, normalizeCreateIVISessionResponse, toCpCreateIVISessionRequestBody, useIviSessionRuntime, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
4579
5234
  //# sourceMappingURL=index.js.map
4580
5235
  //# sourceMappingURL=index.js.map