@vivix-ai/ivi-frontend-sdk 0.3.8 → 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.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var iviSdkTs = require('@vivix-ai/ivi-sdk-ts');
4
+ var websocket = require('@vivix-ai/ivi-sdk-ts/transports/websocket');
4
5
  var react = require('react');
5
6
  var jsxRuntime = require('react/jsx-runtime');
6
7
 
@@ -622,7 +623,9 @@ var SourceManager = class {
622
623
  durationMs: previous?.durationMs,
623
624
  hasAudio: previous?.hasAudio,
624
625
  error: previous?.status === "failed" ? previous.error : void 0,
625
- preload: previous?.preload
626
+ preload: previous?.preload,
627
+ origin: previous?.origin,
628
+ provisional: previous?.provisional ? false : previous?.provisional
626
629
  });
627
630
  this.sources = next;
628
631
  }
@@ -637,7 +640,38 @@ var SourceManager = class {
637
640
  height: payload.height,
638
641
  durationMs: payload.durationMs,
639
642
  hasAudio: payload.hasAudio,
640
- preload: previous?.preload
643
+ preload: previous?.preload,
644
+ origin: previous?.origin,
645
+ provisional: previous?.provisional ? false : previous?.provisional
646
+ });
647
+ this.sources = next;
648
+ }
649
+ upsertBootstrapReady(payload) {
650
+ const previous = this.sources.get(payload.sourceId);
651
+ const next = new Map(this.sources);
652
+ const source = {
653
+ source_id: payload.sourceId,
654
+ kind: payload.source?.kind ?? (payload.streamId ? "generation_stream" : "stream"),
655
+ stream_id: payload.source?.stream_id ?? payload.streamId,
656
+ asset_type: payload.source?.asset_type ?? "video",
657
+ url: payload.source?.url,
658
+ generation: payload.source?.generation,
659
+ metadata: {
660
+ ...payload.source?.metadata ?? {},
661
+ label: payload.source?.metadata?.label ?? "prebuilt"
662
+ }
663
+ };
664
+ next.set(payload.sourceId, {
665
+ source,
666
+ status: "ready",
667
+ playback: previous?.playback ?? payload.playback,
668
+ width: previous?.width ?? payload.width,
669
+ height: previous?.height ?? payload.height,
670
+ durationMs: previous?.durationMs ?? payload.durationMs,
671
+ hasAudio: previous?.hasAudio ?? payload.hasAudio,
672
+ preload: previous?.preload ?? { autoclearAfterPlay: true },
673
+ origin: previous?.origin ?? "prebuilt-session-response",
674
+ provisional: previous?.provisional ?? true
641
675
  });
642
676
  this.sources = next;
643
677
  }
@@ -671,11 +705,14 @@ var SourceManager = class {
671
705
  const next = /* @__PURE__ */ new Map();
672
706
  sources.forEach((source) => {
673
707
  const previous = this.sources.get(source.source_id);
708
+ const playback = source.playback ?? previous?.playback;
674
709
  next.set(source.source_id, {
675
710
  source,
676
- status: source.playback ? "ready" : "created",
677
- playback: source.playback,
678
- preload: previous?.preload
711
+ status: playback ? "ready" : "created",
712
+ playback,
713
+ preload: previous?.preload,
714
+ origin: previous?.origin,
715
+ provisional: source.playback && previous?.provisional ? false : previous?.provisional
679
716
  });
680
717
  });
681
718
  this.sources = next;
@@ -1279,6 +1316,7 @@ var TrtcSourceManager = class {
1279
1316
  startedVideoKeys: /* @__PURE__ */ new Set(),
1280
1317
  startingVideoKeys: /* @__PURE__ */ new Set()
1281
1318
  });
1319
+ this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
1282
1320
  await this.ensureConnected(session);
1283
1321
  const binding = session.views.get(viewId);
1284
1322
  if (!binding) {
@@ -1294,6 +1332,7 @@ var TrtcSourceManager = class {
1294
1332
  }
1295
1333
  const binding = session.views.get(viewId);
1296
1334
  if (binding?.sourceId === sourceId) {
1335
+ this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
1297
1336
  binding.container.removeAttribute(TRTC_VIEW_ATTR);
1298
1337
  session.views.delete(viewId);
1299
1338
  }
@@ -1311,6 +1350,24 @@ var TrtcSourceManager = class {
1311
1350
  binding.muted = muted;
1312
1351
  void this.applyAudioPolicy(session);
1313
1352
  }
1353
+ reassignView(fromSourceId, toSourceId, viewId) {
1354
+ if (fromSourceId === toSourceId) {
1355
+ return true;
1356
+ }
1357
+ const fromSession = this.getSession(fromSourceId);
1358
+ const toSession = this.getSession(toSourceId);
1359
+ if (!fromSession || !toSession || fromSession !== toSession) {
1360
+ return false;
1361
+ }
1362
+ const binding = fromSession.views.get(viewId);
1363
+ if (!binding || binding.sourceId !== fromSourceId) {
1364
+ return false;
1365
+ }
1366
+ this.log("info", `\u8FC1\u79FB\u89C6\u56FE source=${fromSourceId}->${toSourceId} view=${viewId}`);
1367
+ binding.sourceId = toSourceId;
1368
+ void this.applyAudioPolicy(fromSession);
1369
+ return true;
1370
+ }
1314
1371
  unlinkSourceFromSession(sourceId, session) {
1315
1372
  session.sourceIds.delete(sourceId);
1316
1373
  this.sourceSessionKeys.delete(sourceId);
@@ -1485,6 +1542,7 @@ var TrtcSourceManager = class {
1485
1542
  if (!parsed) {
1486
1543
  continue;
1487
1544
  }
1545
+ this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
1488
1546
  await this.startRemoteVideoForBinding(
1489
1547
  client,
1490
1548
  parsed.userId,
@@ -1500,12 +1558,14 @@ var TrtcSourceManager = class {
1500
1558
  }
1501
1559
  binding.startingVideoKeys.add(remoteVideoKey);
1502
1560
  try {
1561
+ this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
1503
1562
  await client.startRemoteVideo({
1504
1563
  userId,
1505
1564
  streamType,
1506
1565
  view: binding.container,
1507
1566
  option: { fillMode: "contain" }
1508
1567
  });
1568
+ this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
1509
1569
  binding.startedVideoKeys.add(remoteVideoKey);
1510
1570
  enforceContainMedia(binding.container);
1511
1571
  } catch (error) {
@@ -1672,7 +1732,7 @@ function isRuntimeTrtcSource(source) {
1672
1732
  return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
1673
1733
  }
1674
1734
  function isSameTrtcConfig(a, b) {
1675
- 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;
1735
+ return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
1676
1736
  }
1677
1737
  function buildTrtcSessionKey(trtc, aiDenoiser) {
1678
1738
  const denoiser = resolveAIDenoiserOptions(aiDenoiser);
@@ -1680,7 +1740,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
1680
1740
  appId: trtc.app_id,
1681
1741
  roomId: trtc.room_id,
1682
1742
  userId: trtc.user_id,
1683
- userSig: trtc.user_sig,
1684
1743
  aiDenoiser: {
1685
1744
  enabled: denoiser.enabled,
1686
1745
  mode: denoiser.mode,
@@ -1789,8 +1848,9 @@ function describeLivekitRoom(livekit) {
1789
1848
  // src/runtime/managers/livekit-source-manager.ts
1790
1849
  var TAG2 = "[IVI-LIVEKIT]";
1791
1850
  var LivekitSourceManager = class {
1792
- constructor(onLog) {
1851
+ constructor(onLog, onRemoteVideoAvailable) {
1793
1852
  this.onLog = onLog;
1853
+ this.onRemoteVideoAvailable = onRemoteVideoAvailable;
1794
1854
  this.sessions = /* @__PURE__ */ new Map();
1795
1855
  this.listeners = /* @__PURE__ */ new Map();
1796
1856
  }
@@ -2017,6 +2077,7 @@ var LivekitSourceManager = class {
2017
2077
  waiter(true);
2018
2078
  }
2019
2079
  session.remoteVideoWaiters.length = 0;
2080
+ this.onRemoteVideoAvailable?.(session.sourceId);
2020
2081
  }
2021
2082
  session.views.forEach((binding) => {
2022
2083
  this.attachTrackToView(track, kind, trackKey, binding);
@@ -2212,6 +2273,7 @@ var IviRuntimeCoordinator = class {
2212
2273
  this.userTextFlowCounter = 0;
2213
2274
  this.waitingTracksListValidation = false;
2214
2275
  this.waitingSourcesListValidation = false;
2276
+ this.bootstrapSource = null;
2215
2277
  this.state = {
2216
2278
  status: "idle",
2217
2279
  session: null,
@@ -2220,7 +2282,8 @@ var IviRuntimeCoordinator = class {
2220
2282
  sources: /* @__PURE__ */ new Map(),
2221
2283
  streams: /* @__PURE__ */ new Map(),
2222
2284
  conversationItems: /* @__PURE__ */ new Map(),
2223
- conversations: []
2285
+ conversations: [],
2286
+ bootstrap: null
2224
2287
  };
2225
2288
  this.client = client;
2226
2289
  this.config = {
@@ -2229,10 +2292,12 @@ var IviRuntimeCoordinator = class {
2229
2292
  };
2230
2293
  this.trtcSourceManager = new TrtcSourceManager(
2231
2294
  this.config.onLog,
2232
- (event) => this.emitTrtcEvent(event),
2295
+ (event) => this.onTrtcManagerEvent(event),
2233
2296
  this.config.trtcAIDenoiser
2234
2297
  );
2235
- this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
2298
+ this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
2299
+ this.recomposeBootstrapState();
2300
+ });
2236
2301
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2237
2302
  onSessionCreated: (event) => this.onSessionCreated(event),
2238
2303
  onSessionEnded: (event) => this.onSessionEnded(event)
@@ -2313,6 +2378,22 @@ var IviRuntimeCoordinator = class {
2313
2378
  emitLog(entry) {
2314
2379
  this.config.onLog?.(entry);
2315
2380
  }
2381
+ setBootstrapSource(source) {
2382
+ const previous = this.bootstrapSource;
2383
+ if (previous && (!source || previous.sourceId !== source.sourceId)) {
2384
+ const existing = this.sourceManager.get(previous.sourceId);
2385
+ if (existing?.origin === "prebuilt-session-response" && existing.provisional) {
2386
+ this.sourceManager.remove(previous.sourceId);
2387
+ }
2388
+ }
2389
+ this.bootstrapSource = source;
2390
+ this.ensureBootstrapSourceRegistered();
2391
+ this.syncPlaybackManagers();
2392
+ this.setState({
2393
+ ...this.state,
2394
+ sources: this.sourceManager.getAll()
2395
+ });
2396
+ }
2316
2397
  /**
2317
2398
  * 编排一次"用户文本输入 -> 触发模型回复"链路。
2318
2399
  *
@@ -2433,10 +2514,10 @@ var IviRuntimeCoordinator = class {
2433
2514
  this.trackManager.reset();
2434
2515
  this.sourceManager.reset();
2435
2516
  this.streamManager.reset();
2436
- this.trtcSourceManager.reset();
2437
- this.livekitSourceManager.reset();
2438
2517
  this.conversationManager.reset();
2439
2518
  this.reset();
2519
+ this.ensureBootstrapSourceRegistered();
2520
+ this.syncPlaybackManagers();
2440
2521
  const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
2441
2522
  this.setState({
2442
2523
  status: nextStatus,
@@ -2474,8 +2555,8 @@ var IviRuntimeCoordinator = class {
2474
2555
  onTracksChanged(listRefreshed) {
2475
2556
  const nextStage = this.stageManager.getStage();
2476
2557
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2477
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2478
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2558
+ this.ensureBootstrapSourceRegistered();
2559
+ this.syncPlaybackManagers();
2479
2560
  this.setState({
2480
2561
  ...this.state,
2481
2562
  tracks: this.trackManager.getAll(),
@@ -2489,8 +2570,8 @@ var IviRuntimeCoordinator = class {
2489
2570
  applyLocalTrackTake(trackId) {
2490
2571
  this.trackManager.applyTrackTook(trackId);
2491
2572
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2492
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2493
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2573
+ this.ensureBootstrapSourceRegistered();
2574
+ this.syncPlaybackManagers();
2494
2575
  this.setState({
2495
2576
  ...this.state,
2496
2577
  tracks: this.trackManager.getAll(),
@@ -2530,8 +2611,8 @@ var IviRuntimeCoordinator = class {
2530
2611
  }
2531
2612
  onSourcesChanged(listRefreshed) {
2532
2613
  this.sourceManager.syncWithTracks(this.trackManager.getAll());
2533
- this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2534
- this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2614
+ this.ensureBootstrapSourceRegistered();
2615
+ this.syncPlaybackManagers();
2535
2616
  this.setState({
2536
2617
  ...this.state,
2537
2618
  sources: this.sourceManager.getAll()
@@ -2554,12 +2635,101 @@ var IviRuntimeCoordinator = class {
2554
2635
  });
2555
2636
  }
2556
2637
  setState(nextState) {
2557
- 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) {
2638
+ let resolvedNextState = nextState;
2639
+ if (this.bootstrapSource && nextState.status !== "stopped" && !nextState.sources.has(this.bootstrapSource.sourceId)) {
2640
+ this.ensureBootstrapSourceRegistered();
2641
+ resolvedNextState = {
2642
+ ...nextState,
2643
+ sources: this.sourceManager.getAll()
2644
+ };
2645
+ }
2646
+ const composedNextState = this.composeBootstrapState(resolvedNextState);
2647
+ 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)) {
2558
2648
  return;
2559
2649
  }
2560
- this.state = nextState;
2650
+ this.state = composedNextState;
2561
2651
  this.stateListeners.forEach((listener) => listener(this.state));
2562
2652
  }
2653
+ composeBootstrapState(nextState) {
2654
+ const bootstrap = this.bootstrapSource;
2655
+ if (!bootstrap) {
2656
+ return nextState.bootstrap === null || nextState.bootstrap === void 0 ? nextState : { ...nextState, bootstrap: null };
2657
+ }
2658
+ const shouldExpose = this.shouldExposeBootstrap(nextState, bootstrap);
2659
+ const bootstrapState = {
2660
+ active: shouldExpose,
2661
+ slot: bootstrap.slot,
2662
+ trackId: bootstrap.trackId,
2663
+ sourceId: bootstrap.sourceId,
2664
+ streamId: bootstrap.streamId
2665
+ };
2666
+ if (!shouldExpose) {
2667
+ return {
2668
+ ...nextState,
2669
+ bootstrap: bootstrapState
2670
+ };
2671
+ }
2672
+ const tracks = new Map(nextState.tracks);
2673
+ tracks.set(bootstrap.trackId, {
2674
+ track_id: bootstrap.trackId,
2675
+ active_source_id: bootstrap.sourceId
2676
+ });
2677
+ return {
2678
+ ...nextState,
2679
+ tracks,
2680
+ bootstrap: bootstrapState
2681
+ };
2682
+ }
2683
+ shouldExposeBootstrap(state, bootstrap) {
2684
+ if (state.status === "stopped") {
2685
+ return false;
2686
+ }
2687
+ const bootstrapRuntimeSource = state.sources.get(bootstrap.sourceId);
2688
+ if (!isReadyRenderableSource(bootstrapRuntimeSource)) {
2689
+ return false;
2690
+ }
2691
+ const layer = (state.stage?.composition ?? []).find((item) => item.slot === bootstrap.slot);
2692
+ if (!layer) {
2693
+ return true;
2694
+ }
2695
+ const track = state.tracks.get(layer.track_id);
2696
+ const activeSourceId = track?.active_source_id;
2697
+ if (!activeSourceId || activeSourceId === bootstrap.sourceId) {
2698
+ return true;
2699
+ }
2700
+ return !this.isReadyForBootstrapHandoff(activeSourceId, state.sources.get(activeSourceId));
2701
+ }
2702
+ isReadyForBootstrapHandoff(sourceId, source) {
2703
+ if (!isReadyRenderableSource(source)) {
2704
+ return false;
2705
+ }
2706
+ if (isTrtcPlaybackSource(source)) {
2707
+ return this.trtcSourceManager.hasRemoteVideoAvailable(sourceId);
2708
+ }
2709
+ if (isLivekitPlaybackSource(source)) {
2710
+ return this.livekitSourceManager.hasRemoteVideoAvailable(sourceId);
2711
+ }
2712
+ return true;
2713
+ }
2714
+ recomposeBootstrapState() {
2715
+ if (!this.bootstrapSource) {
2716
+ return;
2717
+ }
2718
+ this.setState({ ...this.state });
2719
+ }
2720
+ ensureBootstrapSourceRegistered() {
2721
+ if (!this.bootstrapSource) {
2722
+ return;
2723
+ }
2724
+ if (this.sourceManager.has(this.bootstrapSource.sourceId)) {
2725
+ return;
2726
+ }
2727
+ this.sourceManager.upsertBootstrapReady(this.bootstrapSource);
2728
+ }
2729
+ syncPlaybackManagers() {
2730
+ this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2731
+ this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
2732
+ }
2563
2733
  emitEvent(event) {
2564
2734
  this.progressUserTextToResponseFlows(event);
2565
2735
  this.eventListeners.forEach((listener) => listener(event, this.state));
@@ -2568,6 +2738,12 @@ var IviRuntimeCoordinator = class {
2568
2738
  this.config.onTrtcEvent?.(event);
2569
2739
  this.trtcEventListeners.forEach((listener) => listener(event));
2570
2740
  }
2741
+ onTrtcManagerEvent(event) {
2742
+ this.emitTrtcEvent(event);
2743
+ if (event.type === "remote_video_available") {
2744
+ this.recomposeBootstrapState();
2745
+ }
2746
+ }
2571
2747
  resetStoppedState() {
2572
2748
  this.rejectPendingUserTextToResponseFlows(
2573
2749
  new Error("Runtime stopped before user text to response flow completed.")
@@ -2732,7 +2908,331 @@ function isLivekitPlaybackSource(source) {
2732
2908
  if (!source || !source.playback) return false;
2733
2909
  return isLivekitSourcePlayback(source.playback);
2734
2910
  }
2911
+ function isReadyRenderableSource(source) {
2912
+ return Boolean(source && source.status === "ready" && source.playback);
2913
+ }
2914
+ function isSameBootstrapState(a, b) {
2915
+ if (!a && !b) return true;
2916
+ if (!a || !b) return false;
2917
+ return a.active === b.active && a.slot === b.slot && a.trackId === b.trackId && a.sourceId === b.sourceId && a.streamId === b.streamId;
2918
+ }
2919
+ var IviCreateIVISessionError = class extends Error {
2920
+ constructor(response, body) {
2921
+ super(`CreateIVISession failed with ${response.status} ${response.statusText}`);
2922
+ this.name = "IviCreateIVISessionError";
2923
+ this.status = response.status;
2924
+ this.statusText = response.statusText;
2925
+ this.body = body;
2926
+ }
2927
+ };
2928
+ async function createIVISession(request, options) {
2929
+ const fetchImpl = options.fetch ?? globalThis.fetch;
2930
+ if (!fetchImpl) {
2931
+ throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
2932
+ }
2933
+ const requestBody = toCpCreateIVISessionRequestBody(request);
2934
+ const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
2935
+ method: "POST",
2936
+ headers: await buildHeaders(options.headers),
2937
+ body: JSON.stringify(requestBody),
2938
+ credentials: options.credentials,
2939
+ signal: options.signal
2940
+ });
2941
+ const responseBody = await readJsonResponse(response);
2942
+ if (!response.ok) {
2943
+ throw new IviCreateIVISessionError(response, responseBody);
2944
+ }
2945
+ return normalizeCreateIVISessionResponse(responseBody, request, requestBody);
2946
+ }
2947
+ function createRuntimeFromSession(session, options = {}) {
2948
+ const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
2949
+ const client = new iviSdkTs.IviClient({
2950
+ ...options.clientConfig ?? {},
2951
+ transport: new websocket.WebSocketTransport({
2952
+ url: websocketUrl,
2953
+ sessionId: session.iviSessionId,
2954
+ protocols: options.websocket?.protocols,
2955
+ socketFactory: options.websocket?.socketFactory
2956
+ })
2957
+ });
2958
+ const runtime = new IviRuntimeCoordinator(client, options.runtimeConfig);
2959
+ if (options.fastStart !== false && session.prebuiltPlayback) {
2960
+ runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
2961
+ }
2962
+ return {
2963
+ session,
2964
+ client,
2965
+ runtime
2966
+ };
2967
+ }
2968
+ async function createIVISessionRuntime(request, options) {
2969
+ const session = await createIVISession(request, options.http);
2970
+ return createRuntimeFromSession(session, options);
2971
+ }
2972
+ function toCpCreateIVISessionRequestBody(request) {
2973
+ const body = { ...request };
2974
+ delete body.idempotencyKey;
2975
+ delete body.streamType;
2976
+ delete body.iviVersion;
2977
+ delete body.sessionParameters;
2978
+ delete body.enableDecoderPublish;
2979
+ delete body.sessionRecording;
2980
+ delete body.prebuiltCharacters;
2981
+ delete body.prebuiltStream;
2982
+ if (request.idempotencyKey !== void 0) body.idempotency_key = request.idempotencyKey;
2983
+ if (request.streamType !== void 0) body.stream_type = request.streamType;
2984
+ if (request.iviVersion !== void 0) body.ivi_version = request.iviVersion;
2985
+ if (request.sessionParameters !== void 0) body.session_parameters = request.sessionParameters;
2986
+ if (request.enableDecoderPublish !== void 0) body.enable_decoder_publish = request.enableDecoderPublish;
2987
+ if (request.sessionRecording !== void 0) {
2988
+ body.session_recording = {
2989
+ enabled: request.sessionRecording.enabled,
2990
+ aspect_ratio: request.sessionRecording.aspectRatio
2991
+ };
2992
+ }
2993
+ if (request.prebuiltCharacters !== void 0) {
2994
+ body.prebuilt_characters = request.prebuiltCharacters.map((character) => ({
2995
+ character_id: character.characterId,
2996
+ character_payload: serializePrebuiltPayload(character.characterPayload)
2997
+ }));
2998
+ }
2999
+ if (request.prebuiltStream !== void 0) {
3000
+ body.prebuilt_stream = {
3001
+ stream_id: request.prebuiltStream.streamId,
3002
+ mode: request.prebuiltStream.mode,
3003
+ stream_config: serializePrebuiltPayload(request.prebuiltStream.streamConfig)
3004
+ };
3005
+ }
3006
+ return body;
3007
+ }
3008
+ function normalizeCreateIVISessionResponse(responseBody, request = {}, requestBody = toCpCreateIVISessionRequestBody(request)) {
3009
+ const payload = unwrapResponsePayload(responseBody);
3010
+ const iviSessionId = getString(payload, "ivi_session_id") ?? getString(payload, "iviSessionId");
3011
+ const endpoint = getString(payload, "endpoint");
3012
+ if (!iviSessionId) {
3013
+ throw new Error("CreateIVISession response missing ivi_session_id.");
3014
+ }
3015
+ if (!endpoint) {
3016
+ throw new Error("CreateIVISession response missing endpoint.");
3017
+ }
3018
+ const trtcInfo = normalizeTrtcInfo(getObjectValue(payload, "trtc_info") ?? getObjectValue(payload, "trtcInfo"));
3019
+ const livekitInfo = normalizeLivekitInfo(getObjectValue(payload, "livekit_info") ?? getObjectValue(payload, "livekitInfo"));
3020
+ const prebuildInfo = getObjectValue(payload, "prebuild_trtc_info") ?? getObjectValue(payload, "prebuildTrtcInfo");
3021
+ const prebuildTrtcInfo = normalizeTrtcInfo(
3022
+ getObjectValue(prebuildInfo, "trtc_info") ?? getObjectValue(prebuildInfo, "trtcInfo")
3023
+ );
3024
+ const prebuildLivekitInfo = normalizeLivekitInfo(
3025
+ getObjectValue(prebuildInfo, "livekit_info") ?? getObjectValue(prebuildInfo, "livekitInfo")
3026
+ );
3027
+ const prebuiltPlayback = buildPrebuiltPlayback(
3028
+ request,
3029
+ requestBody,
3030
+ prebuildTrtcInfo,
3031
+ prebuildLivekitInfo
3032
+ );
3033
+ return {
3034
+ iviSessionId,
3035
+ endpoint,
3036
+ tokenInfo: getObjectValue(payload, "token_info") ?? getObjectValue(payload, "tokenInfo"),
3037
+ trtcInfo,
3038
+ livekitInfo,
3039
+ prebuildTrtcInfo: prebuildTrtcInfo || prebuildLivekitInfo ? {
3040
+ trtcInfo: prebuildTrtcInfo,
3041
+ livekitInfo: prebuildLivekitInfo
3042
+ } : void 0,
3043
+ prebuiltPlayback,
3044
+ raw: responseBody,
3045
+ requestBody
3046
+ };
3047
+ }
3048
+ function resolveCreateIVISessionUrl(options) {
3049
+ if (options.url) return options.url;
3050
+ if (!options.baseUrl) {
3051
+ throw new Error("CreateIVISession requires options.baseUrl or options.url.");
3052
+ }
3053
+ const path = options.path ?? "/v1/ivi/sessions";
3054
+ return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
3055
+ }
3056
+ async function buildHeaders(headers) {
3057
+ const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
3058
+ const next = new Headers(resolvedHeaders);
3059
+ if (!next.has("content-type")) {
3060
+ next.set("content-type", "application/json");
3061
+ }
3062
+ return next;
3063
+ }
3064
+ async function readJsonResponse(response) {
3065
+ const text = await response.text();
3066
+ if (!text) {
3067
+ return null;
3068
+ }
3069
+ try {
3070
+ return JSON.parse(text);
3071
+ } catch {
3072
+ return text;
3073
+ }
3074
+ }
3075
+ function buildWebSocketUrl(endpoint, options) {
3076
+ const rawUrl = options?.url ?? endpoint;
3077
+ if (!options?.query) {
3078
+ return rawUrl;
3079
+ }
3080
+ const url = new URL(rawUrl);
3081
+ Object.entries(options.query).forEach(([key, value]) => {
3082
+ if (value === void 0 || value === null) return;
3083
+ url.searchParams.set(key, value);
3084
+ });
3085
+ return url.toString();
3086
+ }
3087
+ function toBootstrapSource(prebuiltPlayback, options) {
3088
+ return {
3089
+ sourceId: prebuiltPlayback.sourceId,
3090
+ streamId: prebuiltPlayback.streamId,
3091
+ slot: options.bootstrapSlot ?? "main",
3092
+ trackId: options.bootstrapTrackId ?? "__ivi_bootstrap_track",
3093
+ playback: prebuiltPlayback.playback,
3094
+ source: {
3095
+ source_id: prebuiltPlayback.sourceId,
3096
+ kind: prebuiltPlayback.streamId ? "generation_stream" : "stream",
3097
+ stream_id: prebuiltPlayback.streamId,
3098
+ asset_type: "video",
3099
+ metadata: {
3100
+ label: "prebuilt"
3101
+ }
3102
+ }
3103
+ };
3104
+ }
3105
+ function buildPrebuiltPlayback(request, requestBody, trtcInfo, livekitInfo) {
3106
+ if (!trtcInfo && !livekitInfo) {
3107
+ return void 0;
3108
+ }
3109
+ const streamId = resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo);
3110
+ const sourceId = request.prebuiltStream?.sourceId ?? streamId;
3111
+ if (!sourceId) {
3112
+ return void 0;
3113
+ }
3114
+ if (trtcInfo) {
3115
+ return {
3116
+ sourceId,
3117
+ streamId,
3118
+ kind: "trtc",
3119
+ playback: {
3120
+ type: "trtc",
3121
+ trtc: trtcInfo
3122
+ },
3123
+ provisional: true
3124
+ };
3125
+ }
3126
+ if (livekitInfo) {
3127
+ return {
3128
+ sourceId,
3129
+ streamId,
3130
+ kind: "livekit",
3131
+ playback: {
3132
+ type: "livekit",
3133
+ livekit: livekitInfo
3134
+ },
3135
+ provisional: true
3136
+ };
3137
+ }
3138
+ return void 0;
3139
+ }
3140
+ function resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo) {
3141
+ const prebuiltStream = getObjectValue(requestBody, "prebuilt_stream");
3142
+ return request.prebuiltStream?.streamId ?? getString(prebuiltStream, "stream_id") ?? getString(prebuiltStream, "streamId") ?? trtcInfo?.room_id ?? livekitInfo?.room;
3143
+ }
3144
+ function normalizeTrtcInfo(value) {
3145
+ if (!isRecord(value)) {
3146
+ return void 0;
3147
+ }
3148
+ const appId = getString(value, "app_id") ?? getString(value, "appId");
3149
+ const roomId = getString(value, "room_id") ?? getString(value, "roomId");
3150
+ const userId = getString(value, "user_id") ?? getString(value, "userId");
3151
+ const userSig = getString(value, "user_sig") ?? getString(value, "userSig");
3152
+ if (!appId || !roomId || !userId || !userSig) {
3153
+ return void 0;
3154
+ }
3155
+ const normalized = {
3156
+ ...value,
3157
+ app_id: appId,
3158
+ room_id: roomId,
3159
+ user_id: userId,
3160
+ user_sig: userSig
3161
+ };
3162
+ delete normalized.secret_key;
3163
+ delete normalized.secretKey;
3164
+ return normalized;
3165
+ }
3166
+ function normalizeLivekitInfo(value) {
3167
+ if (!isRecord(value)) {
3168
+ return void 0;
3169
+ }
3170
+ const wsUrl = getString(value, "ws_url") ?? getString(value, "url") ?? getString(value, "wsUrl");
3171
+ const token = getString(value, "token") ?? getString(value, "access_token") ?? getString(value, "accessToken");
3172
+ if (!wsUrl || !token) {
3173
+ return void 0;
3174
+ }
3175
+ return {
3176
+ ws_url: wsUrl,
3177
+ token,
3178
+ room: getString(value, "room") ?? getString(value, "room_name") ?? getString(value, "roomName"),
3179
+ identity: getString(value, "identity")
3180
+ };
3181
+ }
3182
+ function serializePrebuiltPayload(value) {
3183
+ if (typeof value === "string") {
3184
+ return value;
3185
+ }
3186
+ return encodeJsonBase64(value);
3187
+ }
3188
+ function encodeJsonBase64(value) {
3189
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
3190
+ let binary = "";
3191
+ bytes.forEach((byte) => {
3192
+ binary += String.fromCharCode(byte);
3193
+ });
3194
+ if (typeof btoa === "function") {
3195
+ return btoa(binary);
3196
+ }
3197
+ const maybeBuffer = globalThis.Buffer;
3198
+ if (maybeBuffer) {
3199
+ return maybeBuffer.from(bytes).toString("base64");
3200
+ }
3201
+ throw new Error("Base64 encoder is not available in this runtime.");
3202
+ }
3203
+ function unwrapResponsePayload(value) {
3204
+ if (!isRecord(value)) {
3205
+ return {};
3206
+ }
3207
+ const data = getObjectValue(value, "data");
3208
+ return data ?? value;
3209
+ }
3210
+ function getObjectValue(value, key) {
3211
+ if (!isRecord(value)) {
3212
+ return void 0;
3213
+ }
3214
+ const nested = value[key];
3215
+ return isRecord(nested) ? nested : void 0;
3216
+ }
3217
+ function getString(value, key) {
3218
+ if (!isRecord(value)) {
3219
+ return void 0;
3220
+ }
3221
+ const nested = value[key];
3222
+ return typeof nested === "string" && nested.trim().length > 0 ? nested.trim() : void 0;
3223
+ }
3224
+ function isRecord(value) {
3225
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3226
+ }
3227
+ function ensureTrailingSlash(value) {
3228
+ return value.endsWith("/") ? value : `${value}/`;
3229
+ }
3230
+
3231
+ // src/sdk.ts
2735
3232
  var IviFrontendSdk = class {
3233
+ constructor(config = {}) {
3234
+ this.config = config;
3235
+ }
2736
3236
  createClient(config) {
2737
3237
  return new iviSdkTs.IviClient(config);
2738
3238
  }
@@ -2740,6 +3240,25 @@ var IviFrontendSdk = class {
2740
3240
  const client = this.createClient(clientConfig);
2741
3241
  return new IviRuntimeCoordinator(client, runtimeConfig);
2742
3242
  }
3243
+ createIVISession(request, options) {
3244
+ return createIVISession(request, {
3245
+ ...this.config.http ?? {},
3246
+ ...options ?? {}
3247
+ });
3248
+ }
3249
+ createRuntimeFromSession(session, options) {
3250
+ return createRuntimeFromSession(session, options);
3251
+ }
3252
+ createIVISessionRuntime(request, options) {
3253
+ const http = {
3254
+ ...this.config.http ?? {},
3255
+ ...options?.http ?? {}
3256
+ };
3257
+ return createIVISessionRuntime(request, {
3258
+ ...options ?? {},
3259
+ http
3260
+ });
3261
+ }
2743
3262
  };
2744
3263
  var IviStageViewContext = react.createContext(null);
2745
3264
  var EMPTY_RUNTIME_STATE = {
@@ -2750,7 +3269,8 @@ var EMPTY_RUNTIME_STATE = {
2750
3269
  sources: /* @__PURE__ */ new Map(),
2751
3270
  streams: /* @__PURE__ */ new Map(),
2752
3271
  conversationItems: /* @__PURE__ */ new Map(),
2753
- conversations: []
3272
+ conversations: [],
3273
+ bootstrap: null
2754
3274
  };
2755
3275
  function useRuntimeState(runtime) {
2756
3276
  const [state, setState] = react.useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
@@ -2778,10 +3298,10 @@ function IVIStageView(props) {
2778
3298
  const state = useRuntimeState(runtime);
2779
3299
  const slotTrackMap = react.useMemo(() => {
2780
3300
  return buildSlotTrackMapFromState(state);
2781
- }, [state.stage]);
3301
+ }, [state.stage, state.bootstrap]);
2782
3302
  const slotBindings = react.useMemo(() => {
2783
3303
  return buildSlotBindingsFromState(state);
2784
- }, [state.stage, state.tracks, state.sources]);
3304
+ }, [state.stage, state.tracks, state.sources, state.bootstrap]);
2785
3305
  react.useEffect(() => {
2786
3306
  onBindingsChange?.(slotBindings);
2787
3307
  }, [slotBindings, onBindingsChange]);
@@ -2821,15 +3341,18 @@ function buildSlotTrackMapFromState(state) {
2821
3341
  (state.stage?.composition ?? []).forEach((item) => {
2822
3342
  map.set(item.slot, item.track_id);
2823
3343
  });
3344
+ if (state.bootstrap?.active) {
3345
+ map.set(state.bootstrap.slot, state.bootstrap.trackId);
3346
+ }
2824
3347
  return map;
2825
3348
  }
2826
3349
  function buildSlotBindingsFromState(state) {
2827
- const bindings = [];
3350
+ const bindingsBySlot = /* @__PURE__ */ new Map();
2828
3351
  (state.stage?.composition ?? []).forEach((item) => {
2829
3352
  const track = state.tracks.get(item.track_id);
2830
3353
  const sourceId = track?.active_source_id ?? null;
2831
3354
  const source = sourceId ? state.sources.get(sourceId) : void 0;
2832
- bindings.push({
3355
+ bindingsBySlot.set(item.slot, {
2833
3356
  slot: item.slot,
2834
3357
  trackId: item.track_id,
2835
3358
  track,
@@ -2837,7 +3360,18 @@ function buildSlotBindingsFromState(state) {
2837
3360
  source
2838
3361
  });
2839
3362
  });
2840
- return bindings;
3363
+ if (state.bootstrap?.active) {
3364
+ const track = state.tracks.get(state.bootstrap.trackId);
3365
+ const source = state.sources.get(state.bootstrap.sourceId);
3366
+ bindingsBySlot.set(state.bootstrap.slot, {
3367
+ slot: state.bootstrap.slot,
3368
+ trackId: state.bootstrap.trackId,
3369
+ track,
3370
+ sourceId: state.bootstrap.sourceId,
3371
+ source
3372
+ });
3373
+ }
3374
+ return Array.from(bindingsBySlot.values());
2841
3375
  }
2842
3376
  var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
2843
3377
  var DEFAULT_VOLUME = 80;
@@ -3605,23 +4139,25 @@ function IVITrtcPlayer(props) {
3605
4139
  const [loading, setLoading] = react.useState(true);
3606
4140
  const [error, setError] = react.useState(null);
3607
4141
  const mutedRef = react.useRef(muted);
4142
+ const attachedSourceIdRef = react.useRef(null);
3608
4143
  mutedRef.current = muted;
4144
+ react.useEffect(() => {
4145
+ const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
4146
+ setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
4147
+ setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
4148
+ });
4149
+ return unsubscribe;
4150
+ }, [manager, resolvedSourceId]);
3609
4151
  react.useEffect(() => {
3610
4152
  const container = containerRef.current;
3611
4153
  if (!container) {
3612
4154
  return;
3613
4155
  }
3614
4156
  let disposed = false;
4157
+ attachedSourceIdRef.current = resolvedSourceId;
3615
4158
  if (shouldManageSourceLifecycle) {
3616
4159
  manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
3617
4160
  }
3618
- const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
3619
- if (disposed) {
3620
- return;
3621
- }
3622
- setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
3623
- setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
3624
- });
3625
4161
  void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
3626
4162
  if (disposed) {
3627
4163
  return;
@@ -3631,15 +4167,15 @@ function IVITrtcPlayer(props) {
3631
4167
  });
3632
4168
  return () => {
3633
4169
  disposed = true;
3634
- unsubscribe();
3635
- manager.detachView(resolvedSourceId, viewIdRef.current);
4170
+ const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
4171
+ manager.detachView(attachedSourceId, viewIdRef.current);
3636
4172
  if (shouldManageSourceLifecycle) {
3637
- manager.removeSource(resolvedSourceId);
4173
+ manager.removeSource(attachedSourceId);
3638
4174
  }
4175
+ attachedSourceIdRef.current = null;
3639
4176
  };
3640
4177
  }, [
3641
4178
  manager,
3642
- resolvedSourceId,
3643
4179
  shouldManageSourceLifecycle,
3644
4180
  trtc.app_id,
3645
4181
  trtc.room_id,
@@ -3647,6 +4183,15 @@ function IVITrtcPlayer(props) {
3647
4183
  trtc.user_sig,
3648
4184
  trtcAIDenoiser
3649
4185
  ]);
4186
+ react.useEffect(() => {
4187
+ const previousSourceId = attachedSourceIdRef.current;
4188
+ if (!previousSourceId || previousSourceId === resolvedSourceId) {
4189
+ return;
4190
+ }
4191
+ if (manager.reassignView(previousSourceId, resolvedSourceId, viewIdRef.current)) {
4192
+ attachedSourceIdRef.current = resolvedSourceId;
4193
+ }
4194
+ }, [manager, resolvedSourceId]);
3650
4195
  react.useEffect(() => {
3651
4196
  manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
3652
4197
  }, [manager, muted, resolvedSourceId]);
@@ -4154,13 +4699,7 @@ function getSourceRenderKey(sourceId, source) {
4154
4699
  if (!trtc) {
4155
4700
  return sourceId;
4156
4701
  }
4157
- return [
4158
- "trtc",
4159
- trtc.app_id ?? "",
4160
- trtc.room_id ?? "",
4161
- trtc.user_id ?? "",
4162
- trtc.user_sig ?? ""
4163
- ].join(":");
4702
+ return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
4164
4703
  }
4165
4704
  function TrackSlotMediaContent(props) {
4166
4705
  const {
@@ -4616,6 +5155,82 @@ function getClientLogTag(category) {
4616
5155
  if (category === "reconnect") return "[IVI-RECONNECT]";
4617
5156
  return "[IVI-CLIENT]";
4618
5157
  }
5158
+ function useIviSessionRuntime(config) {
5159
+ const [status, setStatus] = react.useState("idle");
5160
+ const [session, setSession] = react.useState(null);
5161
+ const [runtime, setRuntime] = react.useState(null);
5162
+ const [client, setClient] = react.useState(null);
5163
+ const [error, setError] = react.useState(null);
5164
+ react.useEffect(() => {
5165
+ const {
5166
+ request,
5167
+ enabled = true,
5168
+ autoStart = true,
5169
+ onCreated,
5170
+ onRuntimeReady,
5171
+ onError,
5172
+ ...options
5173
+ } = config;
5174
+ if (!enabled || !request) {
5175
+ setStatus("idle");
5176
+ setSession(null);
5177
+ setRuntime(null);
5178
+ setClient(null);
5179
+ setError(null);
5180
+ return;
5181
+ }
5182
+ let disposed = false;
5183
+ let activeRuntime = null;
5184
+ setStatus("creating");
5185
+ setSession(null);
5186
+ setRuntime(null);
5187
+ setClient(null);
5188
+ setError(null);
5189
+ void createIVISessionRuntime(request, options).then(async (created) => {
5190
+ if (disposed) {
5191
+ created.runtime.stop();
5192
+ return;
5193
+ }
5194
+ activeRuntime = created.runtime;
5195
+ setSession(created.session);
5196
+ setRuntime(created.runtime);
5197
+ setClient(created.client);
5198
+ onCreated?.(created.session);
5199
+ onRuntimeReady?.(created.runtime, created.client);
5200
+ if (!autoStart) {
5201
+ setStatus("idle");
5202
+ return;
5203
+ }
5204
+ setStatus("connecting");
5205
+ await created.runtime.start();
5206
+ if (disposed) {
5207
+ created.runtime.stop();
5208
+ return;
5209
+ }
5210
+ setStatus("running");
5211
+ }).catch((caughtError) => {
5212
+ if (disposed) {
5213
+ return;
5214
+ }
5215
+ const normalizedError = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
5216
+ setError(normalizedError);
5217
+ setStatus("error");
5218
+ onError?.(caughtError);
5219
+ });
5220
+ return () => {
5221
+ disposed = true;
5222
+ activeRuntime?.stop();
5223
+ setStatus("stopped");
5224
+ };
5225
+ }, [config]);
5226
+ return {
5227
+ status,
5228
+ session,
5229
+ runtime,
5230
+ client,
5231
+ error
5232
+ };
5233
+ }
4619
5234
 
4620
5235
  exports.DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
4621
5236
  exports.EMPTY_RUNTIME_STATE = EMPTY_RUNTIME_STATE;
@@ -4624,14 +5239,21 @@ exports.IVIStageView = IVIStageView;
4624
5239
  exports.IVISubtitleOverlay = IVISubtitleOverlay;
4625
5240
  exports.IVITrackSlot = IVITrackSlot;
4626
5241
  exports.IVITrtcPlayer = IVITrtcPlayer;
5242
+ exports.IviCreateIVISessionError = IviCreateIVISessionError;
4627
5243
  exports.IviFrontendSdk = IviFrontendSdk;
4628
5244
  exports.IviRuntimeCoordinator = IviRuntimeCoordinator;
4629
5245
  exports.IviRuntimeDispatcher = IviRuntimeDispatcher;
4630
5246
  exports.LivekitSourceManager = LivekitSourceManager;
4631
5247
  exports.TrtcSourceManager = TrtcSourceManager;
5248
+ exports.createIVISession = createIVISession;
5249
+ exports.createIVISessionRuntime = createIVISessionRuntime;
5250
+ exports.createRuntimeFromSession = createRuntimeFromSession;
4632
5251
  exports.isLivekitSourcePlayback = isLivekitSourcePlayback;
4633
5252
  exports.isReadyLivekitRuntimeSource = isReadyLivekitRuntimeSource;
4634
5253
  exports.isSameLivekitConfig = isSameLivekitConfig;
5254
+ exports.normalizeCreateIVISessionResponse = normalizeCreateIVISessionResponse;
5255
+ exports.toCpCreateIVISessionRequestBody = toCpCreateIVISessionRequestBody;
5256
+ exports.useIviSessionRuntime = useIviSessionRuntime;
4635
5257
  exports.useIviStageView = useIviStageView;
4636
5258
  exports.useIviSubtitles = useIviSubtitles;
4637
5259
  exports.useManagedIviRuntime = useManagedIviRuntime;