@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/README.md +194 -1
- package/dist/index.cjs +667 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +152 -2
- package/dist/index.d.ts +152 -2
- package/dist/index.js +661 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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:
|
|
675
|
-
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;
|
|
@@ -1277,6 +1314,7 @@ var TrtcSourceManager = class {
|
|
|
1277
1314
|
startedVideoKeys: /* @__PURE__ */ new Set(),
|
|
1278
1315
|
startingVideoKeys: /* @__PURE__ */ new Set()
|
|
1279
1316
|
});
|
|
1317
|
+
this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
|
|
1280
1318
|
await this.ensureConnected(session);
|
|
1281
1319
|
const binding = session.views.get(viewId);
|
|
1282
1320
|
if (!binding) {
|
|
@@ -1292,6 +1330,7 @@ var TrtcSourceManager = class {
|
|
|
1292
1330
|
}
|
|
1293
1331
|
const binding = session.views.get(viewId);
|
|
1294
1332
|
if (binding?.sourceId === sourceId) {
|
|
1333
|
+
this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
|
|
1295
1334
|
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1296
1335
|
session.views.delete(viewId);
|
|
1297
1336
|
}
|
|
@@ -1309,6 +1348,24 @@ var TrtcSourceManager = class {
|
|
|
1309
1348
|
binding.muted = muted;
|
|
1310
1349
|
void this.applyAudioPolicy(session);
|
|
1311
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
|
+
}
|
|
1312
1369
|
unlinkSourceFromSession(sourceId, session) {
|
|
1313
1370
|
session.sourceIds.delete(sourceId);
|
|
1314
1371
|
this.sourceSessionKeys.delete(sourceId);
|
|
@@ -1483,6 +1540,7 @@ var TrtcSourceManager = class {
|
|
|
1483
1540
|
if (!parsed) {
|
|
1484
1541
|
continue;
|
|
1485
1542
|
}
|
|
1543
|
+
this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
|
|
1486
1544
|
await this.startRemoteVideoForBinding(
|
|
1487
1545
|
client,
|
|
1488
1546
|
parsed.userId,
|
|
@@ -1498,12 +1556,14 @@ var TrtcSourceManager = class {
|
|
|
1498
1556
|
}
|
|
1499
1557
|
binding.startingVideoKeys.add(remoteVideoKey);
|
|
1500
1558
|
try {
|
|
1559
|
+
this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
|
|
1501
1560
|
await client.startRemoteVideo({
|
|
1502
1561
|
userId,
|
|
1503
1562
|
streamType,
|
|
1504
1563
|
view: binding.container,
|
|
1505
1564
|
option: { fillMode: "contain" }
|
|
1506
1565
|
});
|
|
1566
|
+
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
|
|
1507
1567
|
binding.startedVideoKeys.add(remoteVideoKey);
|
|
1508
1568
|
enforceContainMedia(binding.container);
|
|
1509
1569
|
} catch (error) {
|
|
@@ -1670,7 +1730,7 @@ function isRuntimeTrtcSource(source) {
|
|
|
1670
1730
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1671
1731
|
}
|
|
1672
1732
|
function isSameTrtcConfig(a, b) {
|
|
1673
|
-
return a.app_id === b.app_id && a.user_id === b.user_id && a.
|
|
1733
|
+
return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
|
|
1674
1734
|
}
|
|
1675
1735
|
function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
1676
1736
|
const denoiser = resolveAIDenoiserOptions(aiDenoiser);
|
|
@@ -1678,7 +1738,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
|
1678
1738
|
appId: trtc.app_id,
|
|
1679
1739
|
roomId: trtc.room_id,
|
|
1680
1740
|
userId: trtc.user_id,
|
|
1681
|
-
userSig: trtc.user_sig,
|
|
1682
1741
|
aiDenoiser: {
|
|
1683
1742
|
enabled: denoiser.enabled,
|
|
1684
1743
|
mode: denoiser.mode,
|
|
@@ -1787,8 +1846,9 @@ function describeLivekitRoom(livekit) {
|
|
|
1787
1846
|
// src/runtime/managers/livekit-source-manager.ts
|
|
1788
1847
|
var TAG2 = "[IVI-LIVEKIT]";
|
|
1789
1848
|
var LivekitSourceManager = class {
|
|
1790
|
-
constructor(onLog) {
|
|
1849
|
+
constructor(onLog, onRemoteVideoAvailable) {
|
|
1791
1850
|
this.onLog = onLog;
|
|
1851
|
+
this.onRemoteVideoAvailable = onRemoteVideoAvailable;
|
|
1792
1852
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1793
1853
|
this.listeners = /* @__PURE__ */ new Map();
|
|
1794
1854
|
}
|
|
@@ -2015,6 +2075,7 @@ var LivekitSourceManager = class {
|
|
|
2015
2075
|
waiter(true);
|
|
2016
2076
|
}
|
|
2017
2077
|
session.remoteVideoWaiters.length = 0;
|
|
2078
|
+
this.onRemoteVideoAvailable?.(session.sourceId);
|
|
2018
2079
|
}
|
|
2019
2080
|
session.views.forEach((binding) => {
|
|
2020
2081
|
this.attachTrackToView(track, kind, trackKey, binding);
|
|
@@ -2210,6 +2271,7 @@ var IviRuntimeCoordinator = class {
|
|
|
2210
2271
|
this.userTextFlowCounter = 0;
|
|
2211
2272
|
this.waitingTracksListValidation = false;
|
|
2212
2273
|
this.waitingSourcesListValidation = false;
|
|
2274
|
+
this.bootstrapSource = null;
|
|
2213
2275
|
this.state = {
|
|
2214
2276
|
status: "idle",
|
|
2215
2277
|
session: null,
|
|
@@ -2218,7 +2280,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2218
2280
|
sources: /* @__PURE__ */ new Map(),
|
|
2219
2281
|
streams: /* @__PURE__ */ new Map(),
|
|
2220
2282
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2221
|
-
conversations: []
|
|
2283
|
+
conversations: [],
|
|
2284
|
+
bootstrap: null
|
|
2222
2285
|
};
|
|
2223
2286
|
this.client = client;
|
|
2224
2287
|
this.config = {
|
|
@@ -2227,10 +2290,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2227
2290
|
};
|
|
2228
2291
|
this.trtcSourceManager = new TrtcSourceManager(
|
|
2229
2292
|
this.config.onLog,
|
|
2230
|
-
(event) => this.
|
|
2293
|
+
(event) => this.onTrtcManagerEvent(event),
|
|
2231
2294
|
this.config.trtcAIDenoiser
|
|
2232
2295
|
);
|
|
2233
|
-
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog)
|
|
2296
|
+
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
|
|
2297
|
+
this.recomposeBootstrapState();
|
|
2298
|
+
});
|
|
2234
2299
|
this.sessionHandler = new SessionEventHandler(this.sessionManager, {
|
|
2235
2300
|
onSessionCreated: (event) => this.onSessionCreated(event),
|
|
2236
2301
|
onSessionEnded: (event) => this.onSessionEnded(event)
|
|
@@ -2311,6 +2376,22 @@ var IviRuntimeCoordinator = class {
|
|
|
2311
2376
|
emitLog(entry) {
|
|
2312
2377
|
this.config.onLog?.(entry);
|
|
2313
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
|
+
}
|
|
2314
2395
|
/**
|
|
2315
2396
|
* 编排一次"用户文本输入 -> 触发模型回复"链路。
|
|
2316
2397
|
*
|
|
@@ -2431,10 +2512,10 @@ var IviRuntimeCoordinator = class {
|
|
|
2431
2512
|
this.trackManager.reset();
|
|
2432
2513
|
this.sourceManager.reset();
|
|
2433
2514
|
this.streamManager.reset();
|
|
2434
|
-
this.trtcSourceManager.reset();
|
|
2435
|
-
this.livekitSourceManager.reset();
|
|
2436
2515
|
this.conversationManager.reset();
|
|
2437
2516
|
this.reset();
|
|
2517
|
+
this.ensureBootstrapSourceRegistered();
|
|
2518
|
+
this.syncPlaybackManagers();
|
|
2438
2519
|
const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
|
|
2439
2520
|
this.setState({
|
|
2440
2521
|
status: nextStatus,
|
|
@@ -2472,8 +2553,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2472
2553
|
onTracksChanged(listRefreshed) {
|
|
2473
2554
|
const nextStage = this.stageManager.getStage();
|
|
2474
2555
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2475
|
-
this.
|
|
2476
|
-
this.
|
|
2556
|
+
this.ensureBootstrapSourceRegistered();
|
|
2557
|
+
this.syncPlaybackManagers();
|
|
2477
2558
|
this.setState({
|
|
2478
2559
|
...this.state,
|
|
2479
2560
|
tracks: this.trackManager.getAll(),
|
|
@@ -2487,8 +2568,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2487
2568
|
applyLocalTrackTake(trackId) {
|
|
2488
2569
|
this.trackManager.applyTrackTook(trackId);
|
|
2489
2570
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2490
|
-
this.
|
|
2491
|
-
this.
|
|
2571
|
+
this.ensureBootstrapSourceRegistered();
|
|
2572
|
+
this.syncPlaybackManagers();
|
|
2492
2573
|
this.setState({
|
|
2493
2574
|
...this.state,
|
|
2494
2575
|
tracks: this.trackManager.getAll(),
|
|
@@ -2528,8 +2609,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2528
2609
|
}
|
|
2529
2610
|
onSourcesChanged(listRefreshed) {
|
|
2530
2611
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2531
|
-
this.
|
|
2532
|
-
this.
|
|
2612
|
+
this.ensureBootstrapSourceRegistered();
|
|
2613
|
+
this.syncPlaybackManagers();
|
|
2533
2614
|
this.setState({
|
|
2534
2615
|
...this.state,
|
|
2535
2616
|
sources: this.sourceManager.getAll()
|
|
@@ -2552,12 +2633,101 @@ var IviRuntimeCoordinator = class {
|
|
|
2552
2633
|
});
|
|
2553
2634
|
}
|
|
2554
2635
|
setState(nextState) {
|
|
2555
|
-
|
|
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)) {
|
|
2556
2646
|
return;
|
|
2557
2647
|
}
|
|
2558
|
-
this.state =
|
|
2648
|
+
this.state = composedNextState;
|
|
2559
2649
|
this.stateListeners.forEach((listener) => listener(this.state));
|
|
2560
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
|
+
}
|
|
2561
2731
|
emitEvent(event) {
|
|
2562
2732
|
this.progressUserTextToResponseFlows(event);
|
|
2563
2733
|
this.eventListeners.forEach((listener) => listener(event, this.state));
|
|
@@ -2566,6 +2736,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2566
2736
|
this.config.onTrtcEvent?.(event);
|
|
2567
2737
|
this.trtcEventListeners.forEach((listener) => listener(event));
|
|
2568
2738
|
}
|
|
2739
|
+
onTrtcManagerEvent(event) {
|
|
2740
|
+
this.emitTrtcEvent(event);
|
|
2741
|
+
if (event.type === "remote_video_available") {
|
|
2742
|
+
this.recomposeBootstrapState();
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2569
2745
|
resetStoppedState() {
|
|
2570
2746
|
this.rejectPendingUserTextToResponseFlows(
|
|
2571
2747
|
new Error("Runtime stopped before user text to response flow completed.")
|
|
@@ -2730,7 +2906,331 @@ function isLivekitPlaybackSource(source) {
|
|
|
2730
2906
|
if (!source || !source.playback) return false;
|
|
2731
2907
|
return isLivekitSourcePlayback(source.playback);
|
|
2732
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
|
|
2733
3230
|
var IviFrontendSdk = class {
|
|
3231
|
+
constructor(config = {}) {
|
|
3232
|
+
this.config = config;
|
|
3233
|
+
}
|
|
2734
3234
|
createClient(config) {
|
|
2735
3235
|
return new IviClient(config);
|
|
2736
3236
|
}
|
|
@@ -2738,6 +3238,25 @@ var IviFrontendSdk = class {
|
|
|
2738
3238
|
const client = this.createClient(clientConfig);
|
|
2739
3239
|
return new IviRuntimeCoordinator(client, runtimeConfig);
|
|
2740
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
|
+
}
|
|
2741
3260
|
};
|
|
2742
3261
|
var IviStageViewContext = createContext(null);
|
|
2743
3262
|
var EMPTY_RUNTIME_STATE = {
|
|
@@ -2748,7 +3267,8 @@ var EMPTY_RUNTIME_STATE = {
|
|
|
2748
3267
|
sources: /* @__PURE__ */ new Map(),
|
|
2749
3268
|
streams: /* @__PURE__ */ new Map(),
|
|
2750
3269
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2751
|
-
conversations: []
|
|
3270
|
+
conversations: [],
|
|
3271
|
+
bootstrap: null
|
|
2752
3272
|
};
|
|
2753
3273
|
function useRuntimeState(runtime) {
|
|
2754
3274
|
const [state, setState] = useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
|
|
@@ -2776,10 +3296,10 @@ function IVIStageView(props) {
|
|
|
2776
3296
|
const state = useRuntimeState(runtime);
|
|
2777
3297
|
const slotTrackMap = useMemo(() => {
|
|
2778
3298
|
return buildSlotTrackMapFromState(state);
|
|
2779
|
-
}, [state.stage]);
|
|
3299
|
+
}, [state.stage, state.bootstrap]);
|
|
2780
3300
|
const slotBindings = useMemo(() => {
|
|
2781
3301
|
return buildSlotBindingsFromState(state);
|
|
2782
|
-
}, [state.stage, state.tracks, state.sources]);
|
|
3302
|
+
}, [state.stage, state.tracks, state.sources, state.bootstrap]);
|
|
2783
3303
|
useEffect(() => {
|
|
2784
3304
|
onBindingsChange?.(slotBindings);
|
|
2785
3305
|
}, [slotBindings, onBindingsChange]);
|
|
@@ -2819,15 +3339,18 @@ function buildSlotTrackMapFromState(state) {
|
|
|
2819
3339
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2820
3340
|
map.set(item.slot, item.track_id);
|
|
2821
3341
|
});
|
|
3342
|
+
if (state.bootstrap?.active) {
|
|
3343
|
+
map.set(state.bootstrap.slot, state.bootstrap.trackId);
|
|
3344
|
+
}
|
|
2822
3345
|
return map;
|
|
2823
3346
|
}
|
|
2824
3347
|
function buildSlotBindingsFromState(state) {
|
|
2825
|
-
const
|
|
3348
|
+
const bindingsBySlot = /* @__PURE__ */ new Map();
|
|
2826
3349
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2827
3350
|
const track = state.tracks.get(item.track_id);
|
|
2828
3351
|
const sourceId = track?.active_source_id ?? null;
|
|
2829
3352
|
const source = sourceId ? state.sources.get(sourceId) : void 0;
|
|
2830
|
-
|
|
3353
|
+
bindingsBySlot.set(item.slot, {
|
|
2831
3354
|
slot: item.slot,
|
|
2832
3355
|
trackId: item.track_id,
|
|
2833
3356
|
track,
|
|
@@ -2835,7 +3358,18 @@ function buildSlotBindingsFromState(state) {
|
|
|
2835
3358
|
source
|
|
2836
3359
|
});
|
|
2837
3360
|
});
|
|
2838
|
-
|
|
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());
|
|
2839
3373
|
}
|
|
2840
3374
|
var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
|
|
2841
3375
|
var DEFAULT_VOLUME = 80;
|
|
@@ -3603,23 +4137,25 @@ function IVITrtcPlayer(props) {
|
|
|
3603
4137
|
const [loading, setLoading] = useState(true);
|
|
3604
4138
|
const [error, setError] = useState(null);
|
|
3605
4139
|
const mutedRef = useRef(muted);
|
|
4140
|
+
const attachedSourceIdRef = useRef(null);
|
|
3606
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]);
|
|
3607
4149
|
useEffect(() => {
|
|
3608
4150
|
const container = containerRef.current;
|
|
3609
4151
|
if (!container) {
|
|
3610
4152
|
return;
|
|
3611
4153
|
}
|
|
3612
4154
|
let disposed = false;
|
|
4155
|
+
attachedSourceIdRef.current = resolvedSourceId;
|
|
3613
4156
|
if (shouldManageSourceLifecycle) {
|
|
3614
4157
|
manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
|
|
3615
4158
|
}
|
|
3616
|
-
const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
|
|
3617
|
-
if (disposed) {
|
|
3618
|
-
return;
|
|
3619
|
-
}
|
|
3620
|
-
setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
|
|
3621
|
-
setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
|
|
3622
|
-
});
|
|
3623
4159
|
void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
|
|
3624
4160
|
if (disposed) {
|
|
3625
4161
|
return;
|
|
@@ -3629,15 +4165,15 @@ function IVITrtcPlayer(props) {
|
|
|
3629
4165
|
});
|
|
3630
4166
|
return () => {
|
|
3631
4167
|
disposed = true;
|
|
3632
|
-
|
|
3633
|
-
manager.detachView(
|
|
4168
|
+
const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
|
|
4169
|
+
manager.detachView(attachedSourceId, viewIdRef.current);
|
|
3634
4170
|
if (shouldManageSourceLifecycle) {
|
|
3635
|
-
manager.removeSource(
|
|
4171
|
+
manager.removeSource(attachedSourceId);
|
|
3636
4172
|
}
|
|
4173
|
+
attachedSourceIdRef.current = null;
|
|
3637
4174
|
};
|
|
3638
4175
|
}, [
|
|
3639
4176
|
manager,
|
|
3640
|
-
resolvedSourceId,
|
|
3641
4177
|
shouldManageSourceLifecycle,
|
|
3642
4178
|
trtc.app_id,
|
|
3643
4179
|
trtc.room_id,
|
|
@@ -3645,6 +4181,15 @@ function IVITrtcPlayer(props) {
|
|
|
3645
4181
|
trtc.user_sig,
|
|
3646
4182
|
trtcAIDenoiser
|
|
3647
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]);
|
|
3648
4193
|
useEffect(() => {
|
|
3649
4194
|
manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
|
|
3650
4195
|
}, [manager, muted, resolvedSourceId]);
|
|
@@ -4152,13 +4697,7 @@ function getSourceRenderKey(sourceId, source) {
|
|
|
4152
4697
|
if (!trtc) {
|
|
4153
4698
|
return sourceId;
|
|
4154
4699
|
}
|
|
4155
|
-
return [
|
|
4156
|
-
"trtc",
|
|
4157
|
-
trtc.app_id ?? "",
|
|
4158
|
-
trtc.room_id ?? "",
|
|
4159
|
-
trtc.user_id ?? "",
|
|
4160
|
-
trtc.user_sig ?? ""
|
|
4161
|
-
].join(":");
|
|
4700
|
+
return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
|
|
4162
4701
|
}
|
|
4163
4702
|
function TrackSlotMediaContent(props) {
|
|
4164
4703
|
const {
|
|
@@ -4614,7 +5153,83 @@ function getClientLogTag(category) {
|
|
|
4614
5153
|
if (category === "reconnect") return "[IVI-RECONNECT]";
|
|
4615
5154
|
return "[IVI-CLIENT]";
|
|
4616
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
|
+
}
|
|
4617
5232
|
|
|
4618
|
-
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 };
|
|
4619
5234
|
//# sourceMappingURL=index.js.map
|
|
4620
5235
|
//# sourceMappingURL=index.js.map
|