@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/README.md +194 -1
- package/dist/index.cjs +708 -46
- 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 +702 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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:
|
|
677
|
-
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;
|
|
@@ -1107,6 +1144,8 @@ var ConversationManager = class {
|
|
|
1107
1144
|
|
|
1108
1145
|
// src/runtime/managers/trtc-source-manager.ts
|
|
1109
1146
|
var TAG = "[IVI-TRTC]";
|
|
1147
|
+
var TRTC_VIEW_ATTR = "data-ivi-trtc-view";
|
|
1148
|
+
var TRTC_MEDIA_STYLE_ID = "ivi-trtc-media-style";
|
|
1110
1149
|
var DEFAULT_DENOISER_OPTIONS = {
|
|
1111
1150
|
enabled: true,
|
|
1112
1151
|
mode: "normal"
|
|
@@ -1268,6 +1307,8 @@ var TrtcSourceManager = class {
|
|
|
1268
1307
|
if (!session) {
|
|
1269
1308
|
throw new Error(`TRTC source session not found: ${sourceId}`);
|
|
1270
1309
|
}
|
|
1310
|
+
ensureTrtcMediaStyleSheet();
|
|
1311
|
+
container.setAttribute(TRTC_VIEW_ATTR, "");
|
|
1271
1312
|
session.views.set(viewId, {
|
|
1272
1313
|
sourceId,
|
|
1273
1314
|
container,
|
|
@@ -1275,6 +1316,7 @@ var TrtcSourceManager = class {
|
|
|
1275
1316
|
startedVideoKeys: /* @__PURE__ */ new Set(),
|
|
1276
1317
|
startingVideoKeys: /* @__PURE__ */ new Set()
|
|
1277
1318
|
});
|
|
1319
|
+
this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
|
|
1278
1320
|
await this.ensureConnected(session);
|
|
1279
1321
|
const binding = session.views.get(viewId);
|
|
1280
1322
|
if (!binding) {
|
|
@@ -1290,6 +1332,8 @@ var TrtcSourceManager = class {
|
|
|
1290
1332
|
}
|
|
1291
1333
|
const binding = session.views.get(viewId);
|
|
1292
1334
|
if (binding?.sourceId === sourceId) {
|
|
1335
|
+
this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
|
|
1336
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1293
1337
|
session.views.delete(viewId);
|
|
1294
1338
|
}
|
|
1295
1339
|
void this.applyAudioPolicy(session);
|
|
@@ -1306,11 +1350,30 @@ var TrtcSourceManager = class {
|
|
|
1306
1350
|
binding.muted = muted;
|
|
1307
1351
|
void this.applyAudioPolicy(session);
|
|
1308
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
|
+
}
|
|
1309
1371
|
unlinkSourceFromSession(sourceId, session) {
|
|
1310
1372
|
session.sourceIds.delete(sourceId);
|
|
1311
1373
|
this.sourceSessionKeys.delete(sourceId);
|
|
1312
1374
|
session.views.forEach((binding, viewId) => {
|
|
1313
1375
|
if (binding.sourceId === sourceId) {
|
|
1376
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1314
1377
|
session.views.delete(viewId);
|
|
1315
1378
|
}
|
|
1316
1379
|
});
|
|
@@ -1479,6 +1542,7 @@ var TrtcSourceManager = class {
|
|
|
1479
1542
|
if (!parsed) {
|
|
1480
1543
|
continue;
|
|
1481
1544
|
}
|
|
1545
|
+
this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
|
|
1482
1546
|
await this.startRemoteVideoForBinding(
|
|
1483
1547
|
client,
|
|
1484
1548
|
parsed.userId,
|
|
@@ -1494,12 +1558,14 @@ var TrtcSourceManager = class {
|
|
|
1494
1558
|
}
|
|
1495
1559
|
binding.startingVideoKeys.add(remoteVideoKey);
|
|
1496
1560
|
try {
|
|
1561
|
+
this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
|
|
1497
1562
|
await client.startRemoteVideo({
|
|
1498
1563
|
userId,
|
|
1499
1564
|
streamType,
|
|
1500
1565
|
view: binding.container,
|
|
1501
1566
|
option: { fillMode: "contain" }
|
|
1502
1567
|
});
|
|
1568
|
+
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
|
|
1503
1569
|
binding.startedVideoKeys.add(remoteVideoKey);
|
|
1504
1570
|
enforceContainMedia(binding.container);
|
|
1505
1571
|
} catch (error) {
|
|
@@ -1630,11 +1696,43 @@ var TrtcSourceManager = class {
|
|
|
1630
1696
|
this.onTrtcEvent?.(event);
|
|
1631
1697
|
}
|
|
1632
1698
|
};
|
|
1699
|
+
function ensureTrtcMediaStyleSheet() {
|
|
1700
|
+
if (typeof document === "undefined") {
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
if (document.getElementById(TRTC_MEDIA_STYLE_ID)) {
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
const style = document.createElement("style");
|
|
1707
|
+
style.id = TRTC_MEDIA_STYLE_ID;
|
|
1708
|
+
style.textContent = `
|
|
1709
|
+
[${TRTC_VIEW_ATTR}] [id^="player_"],
|
|
1710
|
+
[${TRTC_VIEW_ATTR}] [id^="video_"],
|
|
1711
|
+
[${TRTC_VIEW_ATTR}] [id^="audio_"],
|
|
1712
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1713
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1714
|
+
width: 100% !important;
|
|
1715
|
+
height: 100% !important;
|
|
1716
|
+
max-width: 100% !important;
|
|
1717
|
+
max-height: 100% !important;
|
|
1718
|
+
background: transparent !important;
|
|
1719
|
+
background-color: transparent !important;
|
|
1720
|
+
background-image: none !important;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1724
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1725
|
+
object-fit: contain !important;
|
|
1726
|
+
display: block !important;
|
|
1727
|
+
}
|
|
1728
|
+
`;
|
|
1729
|
+
document.head.appendChild(style);
|
|
1730
|
+
}
|
|
1633
1731
|
function isRuntimeTrtcSource(source) {
|
|
1634
1732
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1635
1733
|
}
|
|
1636
1734
|
function isSameTrtcConfig(a, b) {
|
|
1637
|
-
return a.app_id === b.app_id && a.user_id === b.user_id && a.
|
|
1735
|
+
return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
|
|
1638
1736
|
}
|
|
1639
1737
|
function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
1640
1738
|
const denoiser = resolveAIDenoiserOptions(aiDenoiser);
|
|
@@ -1642,7 +1740,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
|
1642
1740
|
appId: trtc.app_id,
|
|
1643
1741
|
roomId: trtc.room_id,
|
|
1644
1742
|
userId: trtc.user_id,
|
|
1645
|
-
userSig: trtc.user_sig,
|
|
1646
1743
|
aiDenoiser: {
|
|
1647
1744
|
enabled: denoiser.enabled,
|
|
1648
1745
|
mode: denoiser.mode,
|
|
@@ -1721,6 +1818,9 @@ function enforceContainMedia(container) {
|
|
|
1721
1818
|
element.style.setProperty("max-height", "100%", "important");
|
|
1722
1819
|
element.style.setProperty("object-fit", "contain", "important");
|
|
1723
1820
|
element.style.setProperty("display", "block", "important");
|
|
1821
|
+
element.style.setProperty("background", "transparent", "important");
|
|
1822
|
+
element.style.setProperty("background-color", "transparent", "important");
|
|
1823
|
+
element.style.setProperty("background-image", "none", "important");
|
|
1724
1824
|
});
|
|
1725
1825
|
}
|
|
1726
1826
|
|
|
@@ -1748,8 +1848,9 @@ function describeLivekitRoom(livekit) {
|
|
|
1748
1848
|
// src/runtime/managers/livekit-source-manager.ts
|
|
1749
1849
|
var TAG2 = "[IVI-LIVEKIT]";
|
|
1750
1850
|
var LivekitSourceManager = class {
|
|
1751
|
-
constructor(onLog) {
|
|
1851
|
+
constructor(onLog, onRemoteVideoAvailable) {
|
|
1752
1852
|
this.onLog = onLog;
|
|
1853
|
+
this.onRemoteVideoAvailable = onRemoteVideoAvailable;
|
|
1753
1854
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1754
1855
|
this.listeners = /* @__PURE__ */ new Map();
|
|
1755
1856
|
}
|
|
@@ -1976,6 +2077,7 @@ var LivekitSourceManager = class {
|
|
|
1976
2077
|
waiter(true);
|
|
1977
2078
|
}
|
|
1978
2079
|
session.remoteVideoWaiters.length = 0;
|
|
2080
|
+
this.onRemoteVideoAvailable?.(session.sourceId);
|
|
1979
2081
|
}
|
|
1980
2082
|
session.views.forEach((binding) => {
|
|
1981
2083
|
this.attachTrackToView(track, kind, trackKey, binding);
|
|
@@ -2171,6 +2273,7 @@ var IviRuntimeCoordinator = class {
|
|
|
2171
2273
|
this.userTextFlowCounter = 0;
|
|
2172
2274
|
this.waitingTracksListValidation = false;
|
|
2173
2275
|
this.waitingSourcesListValidation = false;
|
|
2276
|
+
this.bootstrapSource = null;
|
|
2174
2277
|
this.state = {
|
|
2175
2278
|
status: "idle",
|
|
2176
2279
|
session: null,
|
|
@@ -2179,7 +2282,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2179
2282
|
sources: /* @__PURE__ */ new Map(),
|
|
2180
2283
|
streams: /* @__PURE__ */ new Map(),
|
|
2181
2284
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2182
|
-
conversations: []
|
|
2285
|
+
conversations: [],
|
|
2286
|
+
bootstrap: null
|
|
2183
2287
|
};
|
|
2184
2288
|
this.client = client;
|
|
2185
2289
|
this.config = {
|
|
@@ -2188,10 +2292,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2188
2292
|
};
|
|
2189
2293
|
this.trtcSourceManager = new TrtcSourceManager(
|
|
2190
2294
|
this.config.onLog,
|
|
2191
|
-
(event) => this.
|
|
2295
|
+
(event) => this.onTrtcManagerEvent(event),
|
|
2192
2296
|
this.config.trtcAIDenoiser
|
|
2193
2297
|
);
|
|
2194
|
-
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog)
|
|
2298
|
+
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog, () => {
|
|
2299
|
+
this.recomposeBootstrapState();
|
|
2300
|
+
});
|
|
2195
2301
|
this.sessionHandler = new SessionEventHandler(this.sessionManager, {
|
|
2196
2302
|
onSessionCreated: (event) => this.onSessionCreated(event),
|
|
2197
2303
|
onSessionEnded: (event) => this.onSessionEnded(event)
|
|
@@ -2272,6 +2378,22 @@ var IviRuntimeCoordinator = class {
|
|
|
2272
2378
|
emitLog(entry) {
|
|
2273
2379
|
this.config.onLog?.(entry);
|
|
2274
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
|
+
}
|
|
2275
2397
|
/**
|
|
2276
2398
|
* 编排一次"用户文本输入 -> 触发模型回复"链路。
|
|
2277
2399
|
*
|
|
@@ -2392,10 +2514,10 @@ var IviRuntimeCoordinator = class {
|
|
|
2392
2514
|
this.trackManager.reset();
|
|
2393
2515
|
this.sourceManager.reset();
|
|
2394
2516
|
this.streamManager.reset();
|
|
2395
|
-
this.trtcSourceManager.reset();
|
|
2396
|
-
this.livekitSourceManager.reset();
|
|
2397
2517
|
this.conversationManager.reset();
|
|
2398
2518
|
this.reset();
|
|
2519
|
+
this.ensureBootstrapSourceRegistered();
|
|
2520
|
+
this.syncPlaybackManagers();
|
|
2399
2521
|
const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
|
|
2400
2522
|
this.setState({
|
|
2401
2523
|
status: nextStatus,
|
|
@@ -2433,8 +2555,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2433
2555
|
onTracksChanged(listRefreshed) {
|
|
2434
2556
|
const nextStage = this.stageManager.getStage();
|
|
2435
2557
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2436
|
-
this.
|
|
2437
|
-
this.
|
|
2558
|
+
this.ensureBootstrapSourceRegistered();
|
|
2559
|
+
this.syncPlaybackManagers();
|
|
2438
2560
|
this.setState({
|
|
2439
2561
|
...this.state,
|
|
2440
2562
|
tracks: this.trackManager.getAll(),
|
|
@@ -2448,8 +2570,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2448
2570
|
applyLocalTrackTake(trackId) {
|
|
2449
2571
|
this.trackManager.applyTrackTook(trackId);
|
|
2450
2572
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2451
|
-
this.
|
|
2452
|
-
this.
|
|
2573
|
+
this.ensureBootstrapSourceRegistered();
|
|
2574
|
+
this.syncPlaybackManagers();
|
|
2453
2575
|
this.setState({
|
|
2454
2576
|
...this.state,
|
|
2455
2577
|
tracks: this.trackManager.getAll(),
|
|
@@ -2489,8 +2611,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2489
2611
|
}
|
|
2490
2612
|
onSourcesChanged(listRefreshed) {
|
|
2491
2613
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2492
|
-
this.
|
|
2493
|
-
this.
|
|
2614
|
+
this.ensureBootstrapSourceRegistered();
|
|
2615
|
+
this.syncPlaybackManagers();
|
|
2494
2616
|
this.setState({
|
|
2495
2617
|
...this.state,
|
|
2496
2618
|
sources: this.sourceManager.getAll()
|
|
@@ -2513,12 +2635,101 @@ var IviRuntimeCoordinator = class {
|
|
|
2513
2635
|
});
|
|
2514
2636
|
}
|
|
2515
2637
|
setState(nextState) {
|
|
2516
|
-
|
|
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)) {
|
|
2517
2648
|
return;
|
|
2518
2649
|
}
|
|
2519
|
-
this.state =
|
|
2650
|
+
this.state = composedNextState;
|
|
2520
2651
|
this.stateListeners.forEach((listener) => listener(this.state));
|
|
2521
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
|
+
}
|
|
2522
2733
|
emitEvent(event) {
|
|
2523
2734
|
this.progressUserTextToResponseFlows(event);
|
|
2524
2735
|
this.eventListeners.forEach((listener) => listener(event, this.state));
|
|
@@ -2527,6 +2738,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2527
2738
|
this.config.onTrtcEvent?.(event);
|
|
2528
2739
|
this.trtcEventListeners.forEach((listener) => listener(event));
|
|
2529
2740
|
}
|
|
2741
|
+
onTrtcManagerEvent(event) {
|
|
2742
|
+
this.emitTrtcEvent(event);
|
|
2743
|
+
if (event.type === "remote_video_available") {
|
|
2744
|
+
this.recomposeBootstrapState();
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2530
2747
|
resetStoppedState() {
|
|
2531
2748
|
this.rejectPendingUserTextToResponseFlows(
|
|
2532
2749
|
new Error("Runtime stopped before user text to response flow completed.")
|
|
@@ -2691,7 +2908,331 @@ function isLivekitPlaybackSource(source) {
|
|
|
2691
2908
|
if (!source || !source.playback) return false;
|
|
2692
2909
|
return isLivekitSourcePlayback(source.playback);
|
|
2693
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
|
|
2694
3232
|
var IviFrontendSdk = class {
|
|
3233
|
+
constructor(config = {}) {
|
|
3234
|
+
this.config = config;
|
|
3235
|
+
}
|
|
2695
3236
|
createClient(config) {
|
|
2696
3237
|
return new iviSdkTs.IviClient(config);
|
|
2697
3238
|
}
|
|
@@ -2699,6 +3240,25 @@ var IviFrontendSdk = class {
|
|
|
2699
3240
|
const client = this.createClient(clientConfig);
|
|
2700
3241
|
return new IviRuntimeCoordinator(client, runtimeConfig);
|
|
2701
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
|
+
}
|
|
2702
3262
|
};
|
|
2703
3263
|
var IviStageViewContext = react.createContext(null);
|
|
2704
3264
|
var EMPTY_RUNTIME_STATE = {
|
|
@@ -2709,7 +3269,8 @@ var EMPTY_RUNTIME_STATE = {
|
|
|
2709
3269
|
sources: /* @__PURE__ */ new Map(),
|
|
2710
3270
|
streams: /* @__PURE__ */ new Map(),
|
|
2711
3271
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2712
|
-
conversations: []
|
|
3272
|
+
conversations: [],
|
|
3273
|
+
bootstrap: null
|
|
2713
3274
|
};
|
|
2714
3275
|
function useRuntimeState(runtime) {
|
|
2715
3276
|
const [state, setState] = react.useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
|
|
@@ -2737,10 +3298,10 @@ function IVIStageView(props) {
|
|
|
2737
3298
|
const state = useRuntimeState(runtime);
|
|
2738
3299
|
const slotTrackMap = react.useMemo(() => {
|
|
2739
3300
|
return buildSlotTrackMapFromState(state);
|
|
2740
|
-
}, [state.stage]);
|
|
3301
|
+
}, [state.stage, state.bootstrap]);
|
|
2741
3302
|
const slotBindings = react.useMemo(() => {
|
|
2742
3303
|
return buildSlotBindingsFromState(state);
|
|
2743
|
-
}, [state.stage, state.tracks, state.sources]);
|
|
3304
|
+
}, [state.stage, state.tracks, state.sources, state.bootstrap]);
|
|
2744
3305
|
react.useEffect(() => {
|
|
2745
3306
|
onBindingsChange?.(slotBindings);
|
|
2746
3307
|
}, [slotBindings, onBindingsChange]);
|
|
@@ -2780,15 +3341,18 @@ function buildSlotTrackMapFromState(state) {
|
|
|
2780
3341
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2781
3342
|
map.set(item.slot, item.track_id);
|
|
2782
3343
|
});
|
|
3344
|
+
if (state.bootstrap?.active) {
|
|
3345
|
+
map.set(state.bootstrap.slot, state.bootstrap.trackId);
|
|
3346
|
+
}
|
|
2783
3347
|
return map;
|
|
2784
3348
|
}
|
|
2785
3349
|
function buildSlotBindingsFromState(state) {
|
|
2786
|
-
const
|
|
3350
|
+
const bindingsBySlot = /* @__PURE__ */ new Map();
|
|
2787
3351
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2788
3352
|
const track = state.tracks.get(item.track_id);
|
|
2789
3353
|
const sourceId = track?.active_source_id ?? null;
|
|
2790
3354
|
const source = sourceId ? state.sources.get(sourceId) : void 0;
|
|
2791
|
-
|
|
3355
|
+
bindingsBySlot.set(item.slot, {
|
|
2792
3356
|
slot: item.slot,
|
|
2793
3357
|
trackId: item.track_id,
|
|
2794
3358
|
track,
|
|
@@ -2796,7 +3360,18 @@ function buildSlotBindingsFromState(state) {
|
|
|
2796
3360
|
source
|
|
2797
3361
|
});
|
|
2798
3362
|
});
|
|
2799
|
-
|
|
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());
|
|
2800
3375
|
}
|
|
2801
3376
|
var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
|
|
2802
3377
|
var DEFAULT_VOLUME = 80;
|
|
@@ -3564,23 +4139,25 @@ function IVITrtcPlayer(props) {
|
|
|
3564
4139
|
const [loading, setLoading] = react.useState(true);
|
|
3565
4140
|
const [error, setError] = react.useState(null);
|
|
3566
4141
|
const mutedRef = react.useRef(muted);
|
|
4142
|
+
const attachedSourceIdRef = react.useRef(null);
|
|
3567
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]);
|
|
3568
4151
|
react.useEffect(() => {
|
|
3569
4152
|
const container = containerRef.current;
|
|
3570
4153
|
if (!container) {
|
|
3571
4154
|
return;
|
|
3572
4155
|
}
|
|
3573
4156
|
let disposed = false;
|
|
4157
|
+
attachedSourceIdRef.current = resolvedSourceId;
|
|
3574
4158
|
if (shouldManageSourceLifecycle) {
|
|
3575
4159
|
manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
|
|
3576
4160
|
}
|
|
3577
|
-
const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
|
|
3578
|
-
if (disposed) {
|
|
3579
|
-
return;
|
|
3580
|
-
}
|
|
3581
|
-
setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
|
|
3582
|
-
setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
|
|
3583
|
-
});
|
|
3584
4161
|
void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
|
|
3585
4162
|
if (disposed) {
|
|
3586
4163
|
return;
|
|
@@ -3590,15 +4167,15 @@ function IVITrtcPlayer(props) {
|
|
|
3590
4167
|
});
|
|
3591
4168
|
return () => {
|
|
3592
4169
|
disposed = true;
|
|
3593
|
-
|
|
3594
|
-
manager.detachView(
|
|
4170
|
+
const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
|
|
4171
|
+
manager.detachView(attachedSourceId, viewIdRef.current);
|
|
3595
4172
|
if (shouldManageSourceLifecycle) {
|
|
3596
|
-
manager.removeSource(
|
|
4173
|
+
manager.removeSource(attachedSourceId);
|
|
3597
4174
|
}
|
|
4175
|
+
attachedSourceIdRef.current = null;
|
|
3598
4176
|
};
|
|
3599
4177
|
}, [
|
|
3600
4178
|
manager,
|
|
3601
|
-
resolvedSourceId,
|
|
3602
4179
|
shouldManageSourceLifecycle,
|
|
3603
4180
|
trtc.app_id,
|
|
3604
4181
|
trtc.room_id,
|
|
@@ -3606,6 +4183,15 @@ function IVITrtcPlayer(props) {
|
|
|
3606
4183
|
trtc.user_sig,
|
|
3607
4184
|
trtcAIDenoiser
|
|
3608
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]);
|
|
3609
4195
|
react.useEffect(() => {
|
|
3610
4196
|
manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
|
|
3611
4197
|
}, [manager, muted, resolvedSourceId]);
|
|
@@ -3618,7 +4204,6 @@ function IVITrtcPlayer(props) {
|
|
|
3618
4204
|
height: "100%",
|
|
3619
4205
|
minWidth: 0,
|
|
3620
4206
|
minHeight: 0,
|
|
3621
|
-
backgroundColor: "#000",
|
|
3622
4207
|
position: "relative",
|
|
3623
4208
|
...style
|
|
3624
4209
|
},
|
|
@@ -4114,13 +4699,7 @@ function getSourceRenderKey(sourceId, source) {
|
|
|
4114
4699
|
if (!trtc) {
|
|
4115
4700
|
return sourceId;
|
|
4116
4701
|
}
|
|
4117
|
-
return [
|
|
4118
|
-
"trtc",
|
|
4119
|
-
trtc.app_id ?? "",
|
|
4120
|
-
trtc.room_id ?? "",
|
|
4121
|
-
trtc.user_id ?? "",
|
|
4122
|
-
trtc.user_sig ?? ""
|
|
4123
|
-
].join(":");
|
|
4702
|
+
return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
|
|
4124
4703
|
}
|
|
4125
4704
|
function TrackSlotMediaContent(props) {
|
|
4126
4705
|
const {
|
|
@@ -4576,6 +5155,82 @@ function getClientLogTag(category) {
|
|
|
4576
5155
|
if (category === "reconnect") return "[IVI-RECONNECT]";
|
|
4577
5156
|
return "[IVI-CLIENT]";
|
|
4578
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
|
+
}
|
|
4579
5234
|
|
|
4580
5235
|
exports.DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
|
|
4581
5236
|
exports.EMPTY_RUNTIME_STATE = EMPTY_RUNTIME_STATE;
|
|
@@ -4584,14 +5239,21 @@ exports.IVIStageView = IVIStageView;
|
|
|
4584
5239
|
exports.IVISubtitleOverlay = IVISubtitleOverlay;
|
|
4585
5240
|
exports.IVITrackSlot = IVITrackSlot;
|
|
4586
5241
|
exports.IVITrtcPlayer = IVITrtcPlayer;
|
|
5242
|
+
exports.IviCreateIVISessionError = IviCreateIVISessionError;
|
|
4587
5243
|
exports.IviFrontendSdk = IviFrontendSdk;
|
|
4588
5244
|
exports.IviRuntimeCoordinator = IviRuntimeCoordinator;
|
|
4589
5245
|
exports.IviRuntimeDispatcher = IviRuntimeDispatcher;
|
|
4590
5246
|
exports.LivekitSourceManager = LivekitSourceManager;
|
|
4591
5247
|
exports.TrtcSourceManager = TrtcSourceManager;
|
|
5248
|
+
exports.createIVISession = createIVISession;
|
|
5249
|
+
exports.createIVISessionRuntime = createIVISessionRuntime;
|
|
5250
|
+
exports.createRuntimeFromSession = createRuntimeFromSession;
|
|
4592
5251
|
exports.isLivekitSourcePlayback = isLivekitSourcePlayback;
|
|
4593
5252
|
exports.isReadyLivekitRuntimeSource = isReadyLivekitRuntimeSource;
|
|
4594
5253
|
exports.isSameLivekitConfig = isSameLivekitConfig;
|
|
5254
|
+
exports.normalizeCreateIVISessionResponse = normalizeCreateIVISessionResponse;
|
|
5255
|
+
exports.toCpCreateIVISessionRequestBody = toCpCreateIVISessionRequestBody;
|
|
5256
|
+
exports.useIviSessionRuntime = useIviSessionRuntime;
|
|
4595
5257
|
exports.useIviStageView = useIviStageView;
|
|
4596
5258
|
exports.useIviSubtitles = useIviSubtitles;
|
|
4597
5259
|
exports.useManagedIviRuntime = useManagedIviRuntime;
|