@vivix-ai/ivi-frontend-sdk 0.3.8 → 0.3.10
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 +237 -1
- package/dist/index.cjs +1045 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -3
- package/dist/index.d.ts +185 -3
- package/dist/index.js +1039 -61
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
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 { propagation, SpanKind, trace, SpanStatusCode, context } from '@opentelemetry/api';
|
|
3
|
+
import { WebSocketTransport } from '@vivix-ai/ivi-sdk-ts/transports/websocket';
|
|
2
4
|
import { createContext, useState, useEffect, useMemo, useRef, useContext, useCallback, useId } from 'react';
|
|
3
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
6
|
|
|
@@ -620,7 +622,9 @@ var SourceManager = class {
|
|
|
620
622
|
durationMs: previous?.durationMs,
|
|
621
623
|
hasAudio: previous?.hasAudio,
|
|
622
624
|
error: previous?.status === "failed" ? previous.error : void 0,
|
|
623
|
-
preload: previous?.preload
|
|
625
|
+
preload: previous?.preload,
|
|
626
|
+
origin: previous?.origin,
|
|
627
|
+
provisional: previous?.provisional ? false : previous?.provisional
|
|
624
628
|
});
|
|
625
629
|
this.sources = next;
|
|
626
630
|
}
|
|
@@ -635,7 +639,38 @@ var SourceManager = class {
|
|
|
635
639
|
height: payload.height,
|
|
636
640
|
durationMs: payload.durationMs,
|
|
637
641
|
hasAudio: payload.hasAudio,
|
|
638
|
-
preload: previous?.preload
|
|
642
|
+
preload: previous?.preload,
|
|
643
|
+
origin: previous?.origin,
|
|
644
|
+
provisional: previous?.provisional ? false : previous?.provisional
|
|
645
|
+
});
|
|
646
|
+
this.sources = next;
|
|
647
|
+
}
|
|
648
|
+
upsertBootstrapReady(payload) {
|
|
649
|
+
const previous = this.sources.get(payload.sourceId);
|
|
650
|
+
const next = new Map(this.sources);
|
|
651
|
+
const source = {
|
|
652
|
+
source_id: payload.sourceId,
|
|
653
|
+
kind: payload.source?.kind ?? (payload.streamId ? "generation_stream" : "stream"),
|
|
654
|
+
stream_id: payload.source?.stream_id ?? payload.streamId,
|
|
655
|
+
asset_type: payload.source?.asset_type ?? "video",
|
|
656
|
+
url: payload.source?.url,
|
|
657
|
+
generation: payload.source?.generation,
|
|
658
|
+
metadata: {
|
|
659
|
+
...payload.source?.metadata ?? {},
|
|
660
|
+
label: payload.source?.metadata?.label ?? "prebuilt"
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
next.set(payload.sourceId, {
|
|
664
|
+
source,
|
|
665
|
+
status: "ready",
|
|
666
|
+
playback: previous?.playback ?? payload.playback,
|
|
667
|
+
width: previous?.width ?? payload.width,
|
|
668
|
+
height: previous?.height ?? payload.height,
|
|
669
|
+
durationMs: previous?.durationMs ?? payload.durationMs,
|
|
670
|
+
hasAudio: previous?.hasAudio ?? payload.hasAudio,
|
|
671
|
+
preload: previous?.preload ?? { autoclearAfterPlay: true },
|
|
672
|
+
origin: previous?.origin ?? "prebuilt-session-response",
|
|
673
|
+
provisional: previous?.provisional ?? true
|
|
639
674
|
});
|
|
640
675
|
this.sources = next;
|
|
641
676
|
}
|
|
@@ -669,11 +704,14 @@ var SourceManager = class {
|
|
|
669
704
|
const next = /* @__PURE__ */ new Map();
|
|
670
705
|
sources.forEach((source) => {
|
|
671
706
|
const previous = this.sources.get(source.source_id);
|
|
707
|
+
const playback = source.playback ?? previous?.playback;
|
|
672
708
|
next.set(source.source_id, {
|
|
673
709
|
source,
|
|
674
|
-
status:
|
|
675
|
-
playback
|
|
676
|
-
preload: previous?.preload
|
|
710
|
+
status: playback ? "ready" : "created",
|
|
711
|
+
playback,
|
|
712
|
+
preload: previous?.preload,
|
|
713
|
+
origin: previous?.origin,
|
|
714
|
+
provisional: source.playback && previous?.provisional ? false : previous?.provisional
|
|
677
715
|
});
|
|
678
716
|
});
|
|
679
717
|
this.sources = next;
|
|
@@ -1102,6 +1140,94 @@ var ConversationManager = class {
|
|
|
1102
1140
|
];
|
|
1103
1141
|
}
|
|
1104
1142
|
};
|
|
1143
|
+
var DEFAULT_TRACER_NAME = "@vivix-ai/ivi-frontend-sdk";
|
|
1144
|
+
function getIviTelemetryTracer(options) {
|
|
1145
|
+
return options?.tracer ?? trace.getTracer(DEFAULT_TRACER_NAME);
|
|
1146
|
+
}
|
|
1147
|
+
function getIviTelemetryContext(options) {
|
|
1148
|
+
return options?.context ?? context.active();
|
|
1149
|
+
}
|
|
1150
|
+
function startIviTelemetrySpan(name, options, attributes) {
|
|
1151
|
+
const tracer = getIviTelemetryTracer(options);
|
|
1152
|
+
const parentContext = getIviTelemetryContext(options);
|
|
1153
|
+
const span = tracer.startSpan(
|
|
1154
|
+
name,
|
|
1155
|
+
{
|
|
1156
|
+
kind: SpanKind.INTERNAL,
|
|
1157
|
+
attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
|
|
1158
|
+
},
|
|
1159
|
+
parentContext
|
|
1160
|
+
);
|
|
1161
|
+
return {
|
|
1162
|
+
span,
|
|
1163
|
+
context: trace.setSpan(parentContext, span)
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function endIviTelemetrySpan(span, attributes) {
|
|
1167
|
+
const cleaned = cleanIviTelemetryAttributes(attributes);
|
|
1168
|
+
if (cleaned) {
|
|
1169
|
+
span.setAttributes(cleaned);
|
|
1170
|
+
}
|
|
1171
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1172
|
+
span.end();
|
|
1173
|
+
}
|
|
1174
|
+
function failIviTelemetrySpan(span, error, attributes) {
|
|
1175
|
+
const cleaned = cleanIviTelemetryAttributes(attributes);
|
|
1176
|
+
if (cleaned) {
|
|
1177
|
+
span.setAttributes(cleaned);
|
|
1178
|
+
}
|
|
1179
|
+
recordIviTelemetryError(span, error);
|
|
1180
|
+
span.end();
|
|
1181
|
+
}
|
|
1182
|
+
function recordIviTelemetrySpan(name, options, attributes, error) {
|
|
1183
|
+
const { span } = startIviTelemetrySpan(name, options, attributes);
|
|
1184
|
+
if (error === void 0) {
|
|
1185
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
1186
|
+
} else {
|
|
1187
|
+
recordIviTelemetryError(span, error);
|
|
1188
|
+
}
|
|
1189
|
+
span.end();
|
|
1190
|
+
}
|
|
1191
|
+
function withIviTelemetryContext(options, context, attributes) {
|
|
1192
|
+
return {
|
|
1193
|
+
...options,
|
|
1194
|
+
context,
|
|
1195
|
+
attributes: mergeIviTelemetryAttributes(options?.attributes, attributes)
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function mergeIviTelemetryAttributes(base, next) {
|
|
1199
|
+
return cleanIviTelemetryAttributes({
|
|
1200
|
+
...base ?? {},
|
|
1201
|
+
...next ?? {}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
function cleanIviTelemetryAttributes(attributes) {
|
|
1205
|
+
const cleaned = {};
|
|
1206
|
+
Object.entries(attributes ?? {}).forEach(([key, value]) => {
|
|
1207
|
+
if (isIviTelemetryAttributeValue(value)) {
|
|
1208
|
+
cleaned[key] = value;
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
return Object.keys(cleaned).length > 0 ? cleaned : void 0;
|
|
1212
|
+
}
|
|
1213
|
+
function recordIviTelemetryError(span, error) {
|
|
1214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1215
|
+
span.recordException(error instanceof Error ? error : message);
|
|
1216
|
+
span.setStatus({
|
|
1217
|
+
code: SpanStatusCode.ERROR,
|
|
1218
|
+
message
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
function isIviTelemetryAttributeValue(value) {
|
|
1222
|
+
if (typeof value === "string" || typeof value === "boolean") return true;
|
|
1223
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1224
|
+
if (!Array.isArray(value)) return false;
|
|
1225
|
+
const nonNullValues = value.filter((item) => item !== null && item !== void 0);
|
|
1226
|
+
if (nonNullValues.length === 0) return true;
|
|
1227
|
+
const firstType = typeof nonNullValues[0];
|
|
1228
|
+
if (firstType !== "string" && firstType !== "number" && firstType !== "boolean") return false;
|
|
1229
|
+
return nonNullValues.every((item) => typeof item === firstType && (typeof item !== "number" || Number.isFinite(item)));
|
|
1230
|
+
}
|
|
1105
1231
|
|
|
1106
1232
|
// src/runtime/managers/trtc-source-manager.ts
|
|
1107
1233
|
var TAG = "[IVI-TRTC]";
|
|
@@ -1112,10 +1238,11 @@ var DEFAULT_DENOISER_OPTIONS = {
|
|
|
1112
1238
|
mode: "normal"
|
|
1113
1239
|
};
|
|
1114
1240
|
var TrtcSourceManager = class {
|
|
1115
|
-
constructor(onLog, onTrtcEvent, aiDenoiser) {
|
|
1241
|
+
constructor(onLog, onTrtcEvent, aiDenoiser, telemetry) {
|
|
1116
1242
|
this.onLog = onLog;
|
|
1117
1243
|
this.onTrtcEvent = onTrtcEvent;
|
|
1118
1244
|
this.aiDenoiser = aiDenoiser;
|
|
1245
|
+
this.telemetry = telemetry;
|
|
1119
1246
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1120
1247
|
this.sourceSessionKeys = /* @__PURE__ */ new Map();
|
|
1121
1248
|
this.listeners = /* @__PURE__ */ new Map();
|
|
@@ -1277,6 +1404,7 @@ var TrtcSourceManager = class {
|
|
|
1277
1404
|
startedVideoKeys: /* @__PURE__ */ new Set(),
|
|
1278
1405
|
startingVideoKeys: /* @__PURE__ */ new Set()
|
|
1279
1406
|
});
|
|
1407
|
+
this.log("info", `\u7ED1\u5B9A\u89C6\u56FE source=${sourceId} view=${viewId} views=${session.views.size}`);
|
|
1280
1408
|
await this.ensureConnected(session);
|
|
1281
1409
|
const binding = session.views.get(viewId);
|
|
1282
1410
|
if (!binding) {
|
|
@@ -1292,6 +1420,7 @@ var TrtcSourceManager = class {
|
|
|
1292
1420
|
}
|
|
1293
1421
|
const binding = session.views.get(viewId);
|
|
1294
1422
|
if (binding?.sourceId === sourceId) {
|
|
1423
|
+
this.log("info", `\u89E3\u7ED1\u89C6\u56FE source=${sourceId} view=${viewId}`);
|
|
1295
1424
|
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1296
1425
|
session.views.delete(viewId);
|
|
1297
1426
|
}
|
|
@@ -1309,6 +1438,24 @@ var TrtcSourceManager = class {
|
|
|
1309
1438
|
binding.muted = muted;
|
|
1310
1439
|
void this.applyAudioPolicy(session);
|
|
1311
1440
|
}
|
|
1441
|
+
reassignView(fromSourceId, toSourceId, viewId) {
|
|
1442
|
+
if (fromSourceId === toSourceId) {
|
|
1443
|
+
return true;
|
|
1444
|
+
}
|
|
1445
|
+
const fromSession = this.getSession(fromSourceId);
|
|
1446
|
+
const toSession = this.getSession(toSourceId);
|
|
1447
|
+
if (!fromSession || !toSession || fromSession !== toSession) {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
const binding = fromSession.views.get(viewId);
|
|
1451
|
+
if (!binding || binding.sourceId !== fromSourceId) {
|
|
1452
|
+
return false;
|
|
1453
|
+
}
|
|
1454
|
+
this.log("info", `\u8FC1\u79FB\u89C6\u56FE source=${fromSourceId}->${toSourceId} view=${viewId}`);
|
|
1455
|
+
binding.sourceId = toSourceId;
|
|
1456
|
+
void this.applyAudioPolicy(fromSession);
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1312
1459
|
unlinkSourceFromSession(sourceId, session) {
|
|
1313
1460
|
session.sourceIds.delete(sourceId);
|
|
1314
1461
|
this.sourceSessionKeys.delete(sourceId);
|
|
@@ -1391,6 +1538,11 @@ var TrtcSourceManager = class {
|
|
|
1391
1538
|
const client = TRTC.create();
|
|
1392
1539
|
const onRemoteVideoAvailable = (event) => {
|
|
1393
1540
|
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
|
|
1541
|
+
recordIviTelemetrySpan(
|
|
1542
|
+
"ivi.media.trtc.remote_video_available",
|
|
1543
|
+
this.telemetry,
|
|
1544
|
+
buildTrtcMediaTelemetryAttributes(session.sourceId, event.userId, event.streamType)
|
|
1545
|
+
);
|
|
1394
1546
|
const remoteVideoKey = buildRemoteVideoKey(event.userId, String(event.streamType));
|
|
1395
1547
|
session.remoteVideoStreams.add(remoteVideoKey);
|
|
1396
1548
|
if (!session.hasEverReceivedRemoteVideo) {
|
|
@@ -1483,6 +1635,7 @@ var TrtcSourceManager = class {
|
|
|
1483
1635
|
if (!parsed) {
|
|
1484
1636
|
continue;
|
|
1485
1637
|
}
|
|
1638
|
+
this.log("info", `\u56DE\u653E\u5DF2\u77E5\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} userId=${parsed.userId} streamType=${parsed.streamType}`);
|
|
1486
1639
|
await this.startRemoteVideoForBinding(
|
|
1487
1640
|
client,
|
|
1488
1641
|
parsed.userId,
|
|
@@ -1497,20 +1650,42 @@ var TrtcSourceManager = class {
|
|
|
1497
1650
|
return;
|
|
1498
1651
|
}
|
|
1499
1652
|
binding.startingVideoKeys.add(remoteVideoKey);
|
|
1653
|
+
const telemetryAttributes = buildTrtcMediaTelemetryAttributes(
|
|
1654
|
+
binding.sourceId,
|
|
1655
|
+
userId,
|
|
1656
|
+
streamType
|
|
1657
|
+
);
|
|
1658
|
+
const telemetrySpan = startIviTelemetrySpan(
|
|
1659
|
+
"ivi.media.trtc.start_remote_video",
|
|
1660
|
+
this.telemetry,
|
|
1661
|
+
telemetryAttributes
|
|
1662
|
+
);
|
|
1500
1663
|
try {
|
|
1664
|
+
this.log("info", `\u5F00\u59CB\u6E32\u67D3\u8FDC\u7AEF\u89C6\u9891 source=${binding.sourceId} view=${remoteVideoKey} userId=${userId} streamType=${streamType}`);
|
|
1501
1665
|
await client.startRemoteVideo({
|
|
1502
1666
|
userId,
|
|
1503
1667
|
streamType,
|
|
1504
1668
|
view: binding.container,
|
|
1505
1669
|
option: { fillMode: "contain" }
|
|
1506
1670
|
});
|
|
1671
|
+
endIviTelemetrySpan(telemetrySpan.span);
|
|
1672
|
+
recordIviTelemetrySpan(
|
|
1673
|
+
"ivi.media.trtc.remote_video_rendered",
|
|
1674
|
+
this.telemetry,
|
|
1675
|
+
telemetryAttributes
|
|
1676
|
+
);
|
|
1677
|
+
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5B8C\u6210 source=${binding.sourceId} userId=${userId} streamType=${streamType}`);
|
|
1507
1678
|
binding.startedVideoKeys.add(remoteVideoKey);
|
|
1508
1679
|
enforceContainMedia(binding.container);
|
|
1509
1680
|
} catch (error) {
|
|
1510
1681
|
if (error instanceof Error && /already started|OPERATION_ABORT/i.test(error.message)) {
|
|
1511
1682
|
binding.startedVideoKeys.add(remoteVideoKey);
|
|
1683
|
+
endIviTelemetrySpan(telemetrySpan.span, {
|
|
1684
|
+
"ivi.media.trtc.start_remote_video.result": "already_started"
|
|
1685
|
+
});
|
|
1512
1686
|
return;
|
|
1513
1687
|
}
|
|
1688
|
+
failIviTelemetrySpan(telemetrySpan.span, error);
|
|
1514
1689
|
this.log("warn", `\u8FDC\u7AEF\u89C6\u9891\u6E32\u67D3\u5931\u8D25 userId=${userId} streamType=${streamType}`, error);
|
|
1515
1690
|
} finally {
|
|
1516
1691
|
binding.startingVideoKeys.delete(remoteVideoKey);
|
|
@@ -1670,7 +1845,7 @@ function isRuntimeTrtcSource(source) {
|
|
|
1670
1845
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1671
1846
|
}
|
|
1672
1847
|
function isSameTrtcConfig(a, b) {
|
|
1673
|
-
return a.app_id === b.app_id && a.user_id === b.user_id && a.
|
|
1848
|
+
return a.app_id === b.app_id && a.user_id === b.user_id && a.room_id === b.room_id;
|
|
1674
1849
|
}
|
|
1675
1850
|
function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
1676
1851
|
const denoiser = resolveAIDenoiserOptions(aiDenoiser);
|
|
@@ -1678,7 +1853,6 @@ function buildTrtcSessionKey(trtc, aiDenoiser) {
|
|
|
1678
1853
|
appId: trtc.app_id,
|
|
1679
1854
|
roomId: trtc.room_id,
|
|
1680
1855
|
userId: trtc.user_id,
|
|
1681
|
-
userSig: trtc.user_sig,
|
|
1682
1856
|
aiDenoiser: {
|
|
1683
1857
|
enabled: denoiser.enabled,
|
|
1684
1858
|
mode: denoiser.mode,
|
|
@@ -1725,6 +1899,13 @@ function getAIDenoiserModeValue(mode) {
|
|
|
1725
1899
|
function buildRemoteVideoKey(userId, streamType) {
|
|
1726
1900
|
return `${userId}::${streamType}`;
|
|
1727
1901
|
}
|
|
1902
|
+
function buildTrtcMediaTelemetryAttributes(sourceId, userId, streamType) {
|
|
1903
|
+
return {
|
|
1904
|
+
"ivi.source.id": sourceId,
|
|
1905
|
+
"ivi.trtc.user_id": userId,
|
|
1906
|
+
"ivi.trtc.stream_type": String(streamType)
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1728
1909
|
function parseRemoteVideoKey(key) {
|
|
1729
1910
|
const separatorIndex = key.indexOf("::");
|
|
1730
1911
|
if (separatorIndex < 0) {
|
|
@@ -1787,8 +1968,10 @@ function describeLivekitRoom(livekit) {
|
|
|
1787
1968
|
// src/runtime/managers/livekit-source-manager.ts
|
|
1788
1969
|
var TAG2 = "[IVI-LIVEKIT]";
|
|
1789
1970
|
var LivekitSourceManager = class {
|
|
1790
|
-
constructor(onLog) {
|
|
1971
|
+
constructor(onLog, onRemoteVideoAvailable, telemetry) {
|
|
1791
1972
|
this.onLog = onLog;
|
|
1973
|
+
this.onRemoteVideoAvailable = onRemoteVideoAvailable;
|
|
1974
|
+
this.telemetry = telemetry;
|
|
1792
1975
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1793
1976
|
this.listeners = /* @__PURE__ */ new Map();
|
|
1794
1977
|
}
|
|
@@ -2010,11 +2193,21 @@ var LivekitSourceManager = class {
|
|
|
2010
2193
|
kind
|
|
2011
2194
|
});
|
|
2012
2195
|
if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
|
|
2196
|
+
recordIviTelemetrySpan(
|
|
2197
|
+
"ivi.media.livekit.remote_video_available",
|
|
2198
|
+
this.telemetry,
|
|
2199
|
+
{
|
|
2200
|
+
"ivi.source.id": session.sourceId,
|
|
2201
|
+
"ivi.livekit.participant_identity": participant.identity,
|
|
2202
|
+
"ivi.livekit.track_sid": track.sid
|
|
2203
|
+
}
|
|
2204
|
+
);
|
|
2013
2205
|
session.hasEverReceivedRemoteVideo = true;
|
|
2014
2206
|
for (const waiter of session.remoteVideoWaiters) {
|
|
2015
2207
|
waiter(true);
|
|
2016
2208
|
}
|
|
2017
2209
|
session.remoteVideoWaiters.length = 0;
|
|
2210
|
+
this.onRemoteVideoAvailable?.(session.sourceId);
|
|
2018
2211
|
}
|
|
2019
2212
|
session.views.forEach((binding) => {
|
|
2020
2213
|
this.attachTrackToView(track, kind, trackKey, binding);
|
|
@@ -2210,6 +2403,7 @@ var IviRuntimeCoordinator = class {
|
|
|
2210
2403
|
this.userTextFlowCounter = 0;
|
|
2211
2404
|
this.waitingTracksListValidation = false;
|
|
2212
2405
|
this.waitingSourcesListValidation = false;
|
|
2406
|
+
this.bootstrapSource = null;
|
|
2213
2407
|
this.state = {
|
|
2214
2408
|
status: "idle",
|
|
2215
2409
|
session: null,
|
|
@@ -2218,7 +2412,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2218
2412
|
sources: /* @__PURE__ */ new Map(),
|
|
2219
2413
|
streams: /* @__PURE__ */ new Map(),
|
|
2220
2414
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2221
|
-
conversations: []
|
|
2415
|
+
conversations: [],
|
|
2416
|
+
bootstrap: null
|
|
2222
2417
|
};
|
|
2223
2418
|
this.client = client;
|
|
2224
2419
|
this.config = {
|
|
@@ -2227,10 +2422,17 @@ var IviRuntimeCoordinator = class {
|
|
|
2227
2422
|
};
|
|
2228
2423
|
this.trtcSourceManager = new TrtcSourceManager(
|
|
2229
2424
|
this.config.onLog,
|
|
2230
|
-
(event) => this.
|
|
2231
|
-
this.config.trtcAIDenoiser
|
|
2425
|
+
(event) => this.onTrtcManagerEvent(event),
|
|
2426
|
+
this.config.trtcAIDenoiser,
|
|
2427
|
+
this.config.telemetry
|
|
2428
|
+
);
|
|
2429
|
+
this.livekitSourceManager = new LivekitSourceManager(
|
|
2430
|
+
this.config.onLog,
|
|
2431
|
+
() => {
|
|
2432
|
+
this.recomposeBootstrapState();
|
|
2433
|
+
},
|
|
2434
|
+
this.config.telemetry
|
|
2232
2435
|
);
|
|
2233
|
-
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
|
|
2234
2436
|
this.sessionHandler = new SessionEventHandler(this.sessionManager, {
|
|
2235
2437
|
onSessionCreated: (event) => this.onSessionCreated(event),
|
|
2236
2438
|
onSessionEnded: (event) => this.onSessionEnded(event)
|
|
@@ -2267,15 +2469,27 @@ var IviRuntimeCoordinator = class {
|
|
|
2267
2469
|
* 启动后状态会从 idle/stopped 进入 connecting,连接成功后由事件驱动进入后续状态。
|
|
2268
2470
|
*/
|
|
2269
2471
|
async start() {
|
|
2270
|
-
this.
|
|
2271
|
-
|
|
2272
|
-
|
|
2472
|
+
const telemetrySpan = startIviTelemetrySpan("ivi.runtime.start", this.config.telemetry, {
|
|
2473
|
+
"ivi.runtime.status": this.state.status,
|
|
2474
|
+
"ivi.session.id": this.state.session?.id
|
|
2273
2475
|
});
|
|
2274
|
-
this.dispatcher.start();
|
|
2275
2476
|
try {
|
|
2477
|
+
this.setState({
|
|
2478
|
+
...this.state,
|
|
2479
|
+
status: "connecting"
|
|
2480
|
+
});
|
|
2481
|
+
this.dispatcher.start();
|
|
2276
2482
|
await this.client.connect();
|
|
2483
|
+
endIviTelemetrySpan(telemetrySpan.span, {
|
|
2484
|
+
"ivi.runtime.status": this.state.status,
|
|
2485
|
+
"ivi.session.id": this.state.session?.id
|
|
2486
|
+
});
|
|
2277
2487
|
} catch (error) {
|
|
2278
2488
|
this.stop();
|
|
2489
|
+
failIviTelemetrySpan(telemetrySpan.span, error, {
|
|
2490
|
+
"ivi.runtime.status": this.state.status,
|
|
2491
|
+
"ivi.session.id": this.state.session?.id
|
|
2492
|
+
});
|
|
2279
2493
|
throw error;
|
|
2280
2494
|
}
|
|
2281
2495
|
}
|
|
@@ -2283,13 +2497,31 @@ var IviRuntimeCoordinator = class {
|
|
|
2283
2497
|
* 停止协调器并断开实时连接,同时重置运行态数据为 stopped。
|
|
2284
2498
|
*/
|
|
2285
2499
|
stop() {
|
|
2286
|
-
this.
|
|
2287
|
-
|
|
2288
|
-
|
|
2500
|
+
const telemetrySpan = startIviTelemetrySpan("ivi.runtime.stop", this.config.telemetry, {
|
|
2501
|
+
"ivi.runtime.status": this.state.status,
|
|
2502
|
+
"ivi.session.id": this.state.session?.id
|
|
2503
|
+
});
|
|
2504
|
+
try {
|
|
2505
|
+
this.dispatcher.stop();
|
|
2506
|
+
this.client.disconnect();
|
|
2507
|
+
this.resetStoppedState();
|
|
2508
|
+
endIviTelemetrySpan(telemetrySpan.span, {
|
|
2509
|
+
"ivi.runtime.status": this.state.status
|
|
2510
|
+
});
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
failIviTelemetrySpan(telemetrySpan.span, error, {
|
|
2513
|
+
"ivi.runtime.status": this.state.status,
|
|
2514
|
+
"ivi.session.id": this.state.session?.id
|
|
2515
|
+
});
|
|
2516
|
+
throw error;
|
|
2517
|
+
}
|
|
2289
2518
|
}
|
|
2290
2519
|
getState() {
|
|
2291
2520
|
return this.state;
|
|
2292
2521
|
}
|
|
2522
|
+
getTelemetryOptions() {
|
|
2523
|
+
return this.config.telemetry;
|
|
2524
|
+
}
|
|
2293
2525
|
onStateChange(listener) {
|
|
2294
2526
|
this.stateListeners.add(listener);
|
|
2295
2527
|
return () => {
|
|
@@ -2311,6 +2543,22 @@ var IviRuntimeCoordinator = class {
|
|
|
2311
2543
|
emitLog(entry) {
|
|
2312
2544
|
this.config.onLog?.(entry);
|
|
2313
2545
|
}
|
|
2546
|
+
setBootstrapSource(source) {
|
|
2547
|
+
const previous = this.bootstrapSource;
|
|
2548
|
+
if (previous && (!source || previous.sourceId !== source.sourceId)) {
|
|
2549
|
+
const existing = this.sourceManager.get(previous.sourceId);
|
|
2550
|
+
if (existing?.origin === "prebuilt-session-response" && existing.provisional) {
|
|
2551
|
+
this.sourceManager.remove(previous.sourceId);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
this.bootstrapSource = source;
|
|
2555
|
+
this.ensureBootstrapSourceRegistered();
|
|
2556
|
+
this.syncPlaybackManagers();
|
|
2557
|
+
this.setState({
|
|
2558
|
+
...this.state,
|
|
2559
|
+
sources: this.sourceManager.getAll()
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2314
2562
|
/**
|
|
2315
2563
|
* 编排一次"用户文本输入 -> 触发模型回复"链路。
|
|
2316
2564
|
*
|
|
@@ -2431,10 +2679,10 @@ var IviRuntimeCoordinator = class {
|
|
|
2431
2679
|
this.trackManager.reset();
|
|
2432
2680
|
this.sourceManager.reset();
|
|
2433
2681
|
this.streamManager.reset();
|
|
2434
|
-
this.trtcSourceManager.reset();
|
|
2435
|
-
this.livekitSourceManager.reset();
|
|
2436
2682
|
this.conversationManager.reset();
|
|
2437
2683
|
this.reset();
|
|
2684
|
+
this.ensureBootstrapSourceRegistered();
|
|
2685
|
+
this.syncPlaybackManagers();
|
|
2438
2686
|
const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
|
|
2439
2687
|
this.setState({
|
|
2440
2688
|
status: nextStatus,
|
|
@@ -2472,8 +2720,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2472
2720
|
onTracksChanged(listRefreshed) {
|
|
2473
2721
|
const nextStage = this.stageManager.getStage();
|
|
2474
2722
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2475
|
-
this.
|
|
2476
|
-
this.
|
|
2723
|
+
this.ensureBootstrapSourceRegistered();
|
|
2724
|
+
this.syncPlaybackManagers();
|
|
2477
2725
|
this.setState({
|
|
2478
2726
|
...this.state,
|
|
2479
2727
|
tracks: this.trackManager.getAll(),
|
|
@@ -2487,8 +2735,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2487
2735
|
applyLocalTrackTake(trackId) {
|
|
2488
2736
|
this.trackManager.applyTrackTook(trackId);
|
|
2489
2737
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2490
|
-
this.
|
|
2491
|
-
this.
|
|
2738
|
+
this.ensureBootstrapSourceRegistered();
|
|
2739
|
+
this.syncPlaybackManagers();
|
|
2492
2740
|
this.setState({
|
|
2493
2741
|
...this.state,
|
|
2494
2742
|
tracks: this.trackManager.getAll(),
|
|
@@ -2528,8 +2776,8 @@ var IviRuntimeCoordinator = class {
|
|
|
2528
2776
|
}
|
|
2529
2777
|
onSourcesChanged(listRefreshed) {
|
|
2530
2778
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
2531
|
-
this.
|
|
2532
|
-
this.
|
|
2779
|
+
this.ensureBootstrapSourceRegistered();
|
|
2780
|
+
this.syncPlaybackManagers();
|
|
2533
2781
|
this.setState({
|
|
2534
2782
|
...this.state,
|
|
2535
2783
|
sources: this.sourceManager.getAll()
|
|
@@ -2552,12 +2800,110 @@ var IviRuntimeCoordinator = class {
|
|
|
2552
2800
|
});
|
|
2553
2801
|
}
|
|
2554
2802
|
setState(nextState) {
|
|
2555
|
-
|
|
2803
|
+
let resolvedNextState = nextState;
|
|
2804
|
+
if (this.bootstrapSource && nextState.status !== "stopped" && !nextState.sources.has(this.bootstrapSource.sourceId)) {
|
|
2805
|
+
this.ensureBootstrapSourceRegistered();
|
|
2806
|
+
resolvedNextState = {
|
|
2807
|
+
...nextState,
|
|
2808
|
+
sources: this.sourceManager.getAll()
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
const composedNextState = this.composeBootstrapState(resolvedNextState);
|
|
2812
|
+
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
2813
|
return;
|
|
2557
2814
|
}
|
|
2558
|
-
this.state
|
|
2815
|
+
const previousState = this.state;
|
|
2816
|
+
this.state = composedNextState;
|
|
2817
|
+
if (previousState.status !== composedNextState.status) {
|
|
2818
|
+
recordIviTelemetrySpan("ivi.runtime.state_change", this.config.telemetry, {
|
|
2819
|
+
"ivi.runtime.status.previous": previousState.status,
|
|
2820
|
+
"ivi.runtime.status.next": composedNextState.status,
|
|
2821
|
+
"ivi.runtime.state_change.reason": "state_update",
|
|
2822
|
+
"ivi.session.id": composedNextState.session?.id ?? previousState.session?.id
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2559
2825
|
this.stateListeners.forEach((listener) => listener(this.state));
|
|
2560
2826
|
}
|
|
2827
|
+
composeBootstrapState(nextState) {
|
|
2828
|
+
const bootstrap = this.bootstrapSource;
|
|
2829
|
+
if (!bootstrap) {
|
|
2830
|
+
return nextState.bootstrap === null || nextState.bootstrap === void 0 ? nextState : { ...nextState, bootstrap: null };
|
|
2831
|
+
}
|
|
2832
|
+
const shouldExpose = this.shouldExposeBootstrap(nextState, bootstrap);
|
|
2833
|
+
const bootstrapState = {
|
|
2834
|
+
active: shouldExpose,
|
|
2835
|
+
slot: bootstrap.slot,
|
|
2836
|
+
trackId: bootstrap.trackId,
|
|
2837
|
+
sourceId: bootstrap.sourceId,
|
|
2838
|
+
streamId: bootstrap.streamId
|
|
2839
|
+
};
|
|
2840
|
+
if (!shouldExpose) {
|
|
2841
|
+
return {
|
|
2842
|
+
...nextState,
|
|
2843
|
+
bootstrap: bootstrapState
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
const tracks = new Map(nextState.tracks);
|
|
2847
|
+
tracks.set(bootstrap.trackId, {
|
|
2848
|
+
track_id: bootstrap.trackId,
|
|
2849
|
+
active_source_id: bootstrap.sourceId
|
|
2850
|
+
});
|
|
2851
|
+
return {
|
|
2852
|
+
...nextState,
|
|
2853
|
+
tracks,
|
|
2854
|
+
bootstrap: bootstrapState
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
shouldExposeBootstrap(state, bootstrap) {
|
|
2858
|
+
if (state.status === "stopped") {
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
const bootstrapRuntimeSource = state.sources.get(bootstrap.sourceId);
|
|
2862
|
+
if (!isReadyRenderableSource(bootstrapRuntimeSource)) {
|
|
2863
|
+
return false;
|
|
2864
|
+
}
|
|
2865
|
+
const layer = (state.stage?.composition ?? []).find((item) => item.slot === bootstrap.slot);
|
|
2866
|
+
if (!layer) {
|
|
2867
|
+
return true;
|
|
2868
|
+
}
|
|
2869
|
+
const track = state.tracks.get(layer.track_id);
|
|
2870
|
+
const activeSourceId = track?.active_source_id;
|
|
2871
|
+
if (!activeSourceId || activeSourceId === bootstrap.sourceId) {
|
|
2872
|
+
return true;
|
|
2873
|
+
}
|
|
2874
|
+
return !this.isReadyForBootstrapHandoff(activeSourceId, state.sources.get(activeSourceId));
|
|
2875
|
+
}
|
|
2876
|
+
isReadyForBootstrapHandoff(sourceId, source) {
|
|
2877
|
+
if (!isReadyRenderableSource(source)) {
|
|
2878
|
+
return false;
|
|
2879
|
+
}
|
|
2880
|
+
if (isTrtcPlaybackSource(source)) {
|
|
2881
|
+
return this.trtcSourceManager.hasRemoteVideoAvailable(sourceId);
|
|
2882
|
+
}
|
|
2883
|
+
if (isLivekitPlaybackSource(source)) {
|
|
2884
|
+
return this.livekitSourceManager.hasRemoteVideoAvailable(sourceId);
|
|
2885
|
+
}
|
|
2886
|
+
return true;
|
|
2887
|
+
}
|
|
2888
|
+
recomposeBootstrapState() {
|
|
2889
|
+
if (!this.bootstrapSource) {
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
this.setState({ ...this.state });
|
|
2893
|
+
}
|
|
2894
|
+
ensureBootstrapSourceRegistered() {
|
|
2895
|
+
if (!this.bootstrapSource) {
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
if (this.sourceManager.has(this.bootstrapSource.sourceId)) {
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
this.sourceManager.upsertBootstrapReady(this.bootstrapSource);
|
|
2902
|
+
}
|
|
2903
|
+
syncPlaybackManagers() {
|
|
2904
|
+
this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
2905
|
+
this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
2906
|
+
}
|
|
2561
2907
|
emitEvent(event) {
|
|
2562
2908
|
this.progressUserTextToResponseFlows(event);
|
|
2563
2909
|
this.eventListeners.forEach((listener) => listener(event, this.state));
|
|
@@ -2566,6 +2912,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2566
2912
|
this.config.onTrtcEvent?.(event);
|
|
2567
2913
|
this.trtcEventListeners.forEach((listener) => listener(event));
|
|
2568
2914
|
}
|
|
2915
|
+
onTrtcManagerEvent(event) {
|
|
2916
|
+
this.emitTrtcEvent(event);
|
|
2917
|
+
if (event.type === "remote_video_available") {
|
|
2918
|
+
this.recomposeBootstrapState();
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2569
2921
|
resetStoppedState() {
|
|
2570
2922
|
this.rejectPendingUserTextToResponseFlows(
|
|
2571
2923
|
new Error("Runtime stopped before user text to response flow completed.")
|
|
@@ -2730,15 +3082,481 @@ function isLivekitPlaybackSource(source) {
|
|
|
2730
3082
|
if (!source || !source.playback) return false;
|
|
2731
3083
|
return isLivekitSourcePlayback(source.playback);
|
|
2732
3084
|
}
|
|
3085
|
+
function isReadyRenderableSource(source) {
|
|
3086
|
+
return Boolean(source && source.status === "ready" && source.playback);
|
|
3087
|
+
}
|
|
3088
|
+
function isSameBootstrapState(a, b) {
|
|
3089
|
+
if (!a && !b) return true;
|
|
3090
|
+
if (!a || !b) return false;
|
|
3091
|
+
return a.active === b.active && a.slot === b.slot && a.trackId === b.trackId && a.sourceId === b.sourceId && a.streamId === b.streamId;
|
|
3092
|
+
}
|
|
3093
|
+
var IviCreateIVISessionError = class extends Error {
|
|
3094
|
+
constructor(response, body) {
|
|
3095
|
+
super(`CreateIVISession failed with ${response.status} ${response.statusText}`);
|
|
3096
|
+
this.name = "IviCreateIVISessionError";
|
|
3097
|
+
this.status = response.status;
|
|
3098
|
+
this.statusText = response.statusText;
|
|
3099
|
+
this.body = body;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
async function createIVISession(request, options) {
|
|
3103
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
3104
|
+
if (!fetchImpl) {
|
|
3105
|
+
throw new Error("fetch is not available. Pass options.fetch to createIVISession.");
|
|
3106
|
+
}
|
|
3107
|
+
const telemetrySpan = startIviTelemetrySpan(
|
|
3108
|
+
"ivi.session.create",
|
|
3109
|
+
options.telemetry,
|
|
3110
|
+
buildCreateSessionTelemetryAttributes(request)
|
|
3111
|
+
);
|
|
3112
|
+
try {
|
|
3113
|
+
const requestBody = toCpCreateIVISessionRequestBody(request);
|
|
3114
|
+
const headers = await buildHeaders(options.headers);
|
|
3115
|
+
injectIviTelemetryHeaders(headers, telemetrySpan.context);
|
|
3116
|
+
const response = await fetchImpl(resolveCreateIVISessionUrl(options), {
|
|
3117
|
+
method: "POST",
|
|
3118
|
+
headers,
|
|
3119
|
+
body: JSON.stringify(requestBody),
|
|
3120
|
+
credentials: options.credentials,
|
|
3121
|
+
signal: options.signal
|
|
3122
|
+
});
|
|
3123
|
+
const responseBody = await readJsonResponse(response);
|
|
3124
|
+
if (!response.ok) {
|
|
3125
|
+
const error = new IviCreateIVISessionError(response, responseBody);
|
|
3126
|
+
failIviTelemetrySpan(telemetrySpan.span, error, {
|
|
3127
|
+
"http.response.status_code": response.status,
|
|
3128
|
+
"http.response.status_text": response.statusText
|
|
3129
|
+
});
|
|
3130
|
+
throw error;
|
|
3131
|
+
}
|
|
3132
|
+
const result = normalizeCreateIVISessionResponse(responseBody, request, requestBody);
|
|
3133
|
+
endIviTelemetrySpan(telemetrySpan.span, {
|
|
3134
|
+
"http.response.status_code": response.status,
|
|
3135
|
+
"ivi.session.id": result.iviSessionId,
|
|
3136
|
+
"ivi.prebuild.playback.kind": result.prebuiltPlayback?.kind
|
|
3137
|
+
});
|
|
3138
|
+
return result;
|
|
3139
|
+
} catch (error) {
|
|
3140
|
+
if (!(error instanceof IviCreateIVISessionError)) {
|
|
3141
|
+
failIviTelemetrySpan(telemetrySpan.span, error);
|
|
3142
|
+
}
|
|
3143
|
+
throw error;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
function createRuntimeFromSession(session, options = {}) {
|
|
3147
|
+
const telemetrySpan = startIviTelemetrySpan("ivi.runtime.create", options.telemetry, {
|
|
3148
|
+
"ivi.session.id": session.iviSessionId,
|
|
3149
|
+
"ivi.prebuild.playback.kind": session.prebuiltPlayback?.kind
|
|
3150
|
+
});
|
|
3151
|
+
try {
|
|
3152
|
+
const websocketUrl = buildWebSocketUrl(session.endpoint, options.websocket);
|
|
3153
|
+
const childTelemetry = withIviTelemetryContext(options.telemetry, telemetrySpan.context, {
|
|
3154
|
+
"ivi.session.id": session.iviSessionId
|
|
3155
|
+
});
|
|
3156
|
+
const clientTelemetry = mergeIviTelemetryOptions(
|
|
3157
|
+
options.clientConfig?.telemetry,
|
|
3158
|
+
childTelemetry
|
|
3159
|
+
);
|
|
3160
|
+
const runtimeTelemetry = mergeIviTelemetryOptions(
|
|
3161
|
+
options.runtimeConfig?.telemetry,
|
|
3162
|
+
childTelemetry
|
|
3163
|
+
);
|
|
3164
|
+
const client = new IviClient({
|
|
3165
|
+
...options.clientConfig ?? {},
|
|
3166
|
+
telemetry: clientTelemetry,
|
|
3167
|
+
transport: new WebSocketTransport({
|
|
3168
|
+
url: websocketUrl,
|
|
3169
|
+
sessionId: session.iviSessionId,
|
|
3170
|
+
protocols: options.websocket?.protocols,
|
|
3171
|
+
socketFactory: options.websocket?.socketFactory
|
|
3172
|
+
})
|
|
3173
|
+
});
|
|
3174
|
+
const runtime = new IviRuntimeCoordinator(client, {
|
|
3175
|
+
...options.runtimeConfig ?? {},
|
|
3176
|
+
telemetry: runtimeTelemetry
|
|
3177
|
+
});
|
|
3178
|
+
if (options.fastStart !== false && session.prebuiltPlayback) {
|
|
3179
|
+
runtime.setBootstrapSource(toBootstrapSource(session.prebuiltPlayback, options));
|
|
3180
|
+
}
|
|
3181
|
+
endIviTelemetrySpan(telemetrySpan.span);
|
|
3182
|
+
return {
|
|
3183
|
+
session,
|
|
3184
|
+
client,
|
|
3185
|
+
runtime
|
|
3186
|
+
};
|
|
3187
|
+
} catch (error) {
|
|
3188
|
+
failIviTelemetrySpan(telemetrySpan.span, error);
|
|
3189
|
+
throw error;
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
async function createIVISessionRuntime(request, options) {
|
|
3193
|
+
const telemetry = mergeIviTelemetryOptions(options.telemetry, options.http.telemetry);
|
|
3194
|
+
const session = await createIVISession(request, {
|
|
3195
|
+
...options.http,
|
|
3196
|
+
telemetry
|
|
3197
|
+
});
|
|
3198
|
+
return createRuntimeFromSession(session, {
|
|
3199
|
+
...options,
|
|
3200
|
+
telemetry
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
function toCpCreateIVISessionRequestBody(request) {
|
|
3204
|
+
const body = { ...request };
|
|
3205
|
+
delete body.idempotencyKey;
|
|
3206
|
+
delete body.streamType;
|
|
3207
|
+
delete body.iviVersion;
|
|
3208
|
+
delete body.sessionParameters;
|
|
3209
|
+
delete body.enableDecoderPublish;
|
|
3210
|
+
delete body.sessionRecording;
|
|
3211
|
+
delete body.prebuiltCharacters;
|
|
3212
|
+
delete body.prebuiltStream;
|
|
3213
|
+
if (request.idempotencyKey !== void 0) body.idempotency_key = request.idempotencyKey;
|
|
3214
|
+
if (request.streamType !== void 0) body.stream_type = request.streamType;
|
|
3215
|
+
if (request.iviVersion !== void 0) body.ivi_version = request.iviVersion;
|
|
3216
|
+
if (request.sessionParameters !== void 0) body.session_parameters = request.sessionParameters;
|
|
3217
|
+
if (request.enableDecoderPublish !== void 0) body.enable_decoder_publish = request.enableDecoderPublish;
|
|
3218
|
+
if (request.sessionRecording !== void 0) {
|
|
3219
|
+
body.session_recording = {
|
|
3220
|
+
enabled: request.sessionRecording.enabled,
|
|
3221
|
+
aspect_ratio: request.sessionRecording.aspectRatio
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
if (request.prebuiltCharacters !== void 0) {
|
|
3225
|
+
body.prebuilt_characters = request.prebuiltCharacters.map((character) => ({
|
|
3226
|
+
character_id: character.characterId,
|
|
3227
|
+
character_payload: serializePrebuiltPayload(character.characterPayload)
|
|
3228
|
+
}));
|
|
3229
|
+
}
|
|
3230
|
+
if (request.prebuiltStream !== void 0) {
|
|
3231
|
+
body.prebuilt_stream = {
|
|
3232
|
+
stream_id: request.prebuiltStream.streamId,
|
|
3233
|
+
mode: request.prebuiltStream.mode,
|
|
3234
|
+
stream_config: serializePrebuiltPayload(request.prebuiltStream.streamConfig)
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
return body;
|
|
3238
|
+
}
|
|
3239
|
+
function normalizeCreateIVISessionResponse(responseBody, request = {}, requestBody = toCpCreateIVISessionRequestBody(request)) {
|
|
3240
|
+
const payload = unwrapResponsePayload(responseBody);
|
|
3241
|
+
const iviSessionId = getString(payload, "ivi_session_id") ?? getString(payload, "iviSessionId");
|
|
3242
|
+
const endpoint = getString(payload, "endpoint");
|
|
3243
|
+
if (!iviSessionId) {
|
|
3244
|
+
throw new Error("CreateIVISession response missing ivi_session_id.");
|
|
3245
|
+
}
|
|
3246
|
+
if (!endpoint) {
|
|
3247
|
+
throw new Error("CreateIVISession response missing endpoint.");
|
|
3248
|
+
}
|
|
3249
|
+
const trtcInfo = normalizeTrtcInfo(getObjectValue(payload, "trtc_info") ?? getObjectValue(payload, "trtcInfo"));
|
|
3250
|
+
const livekitInfo = normalizeLivekitInfo(getObjectValue(payload, "livekit_info") ?? getObjectValue(payload, "livekitInfo"));
|
|
3251
|
+
const prebuildInfo = getObjectValue(payload, "prebuild_trtc_info") ?? getObjectValue(payload, "prebuildTrtcInfo");
|
|
3252
|
+
const prebuildTrtcInfo = normalizeTrtcInfo(
|
|
3253
|
+
getObjectValue(prebuildInfo, "trtc_info") ?? getObjectValue(prebuildInfo, "trtcInfo")
|
|
3254
|
+
);
|
|
3255
|
+
const prebuildLivekitInfo = normalizeLivekitInfo(
|
|
3256
|
+
getObjectValue(prebuildInfo, "livekit_info") ?? getObjectValue(prebuildInfo, "livekitInfo")
|
|
3257
|
+
);
|
|
3258
|
+
const prebuiltPlayback = buildPrebuiltPlayback(
|
|
3259
|
+
request,
|
|
3260
|
+
requestBody,
|
|
3261
|
+
prebuildTrtcInfo,
|
|
3262
|
+
prebuildLivekitInfo
|
|
3263
|
+
);
|
|
3264
|
+
return {
|
|
3265
|
+
iviSessionId,
|
|
3266
|
+
endpoint,
|
|
3267
|
+
tokenInfo: getObjectValue(payload, "token_info") ?? getObjectValue(payload, "tokenInfo"),
|
|
3268
|
+
trtcInfo,
|
|
3269
|
+
livekitInfo,
|
|
3270
|
+
prebuildTrtcInfo: prebuildTrtcInfo || prebuildLivekitInfo ? {
|
|
3271
|
+
trtcInfo: prebuildTrtcInfo,
|
|
3272
|
+
livekitInfo: prebuildLivekitInfo
|
|
3273
|
+
} : void 0,
|
|
3274
|
+
prebuiltPlayback,
|
|
3275
|
+
raw: responseBody,
|
|
3276
|
+
requestBody
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
function resolveCreateIVISessionUrl(options) {
|
|
3280
|
+
if (options.url) return options.url;
|
|
3281
|
+
if (!options.baseUrl) {
|
|
3282
|
+
throw new Error("CreateIVISession requires options.baseUrl or options.url.");
|
|
3283
|
+
}
|
|
3284
|
+
const path = options.path ?? "/v1/ivi/sessions";
|
|
3285
|
+
return new URL(path, ensureTrailingSlash(options.baseUrl)).toString();
|
|
3286
|
+
}
|
|
3287
|
+
function buildCreateSessionTelemetryAttributes(request) {
|
|
3288
|
+
return {
|
|
3289
|
+
"ivi.version": request.iviVersion,
|
|
3290
|
+
"ivi.stream.type": request.streamType,
|
|
3291
|
+
"ivi.prebuild.enabled": Boolean(
|
|
3292
|
+
request.prebuiltStream || request.prebuiltCharacters && request.prebuiltCharacters.length > 0
|
|
3293
|
+
)
|
|
3294
|
+
};
|
|
3295
|
+
}
|
|
3296
|
+
function mergeIviTelemetryOptions(explicit, inherited) {
|
|
3297
|
+
if (!explicit && !inherited) {
|
|
3298
|
+
return void 0;
|
|
3299
|
+
}
|
|
3300
|
+
return {
|
|
3301
|
+
tracer: explicit?.tracer ?? inherited?.tracer,
|
|
3302
|
+
context: explicit?.context ?? inherited?.context,
|
|
3303
|
+
attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
async function buildHeaders(headers) {
|
|
3307
|
+
const resolvedHeaders = typeof headers === "function" ? await headers() : headers;
|
|
3308
|
+
const next = new Headers(resolvedHeaders);
|
|
3309
|
+
if (!next.has("content-type")) {
|
|
3310
|
+
next.set("content-type", "application/json");
|
|
3311
|
+
}
|
|
3312
|
+
return next;
|
|
3313
|
+
}
|
|
3314
|
+
var headersTextMapSetter = {
|
|
3315
|
+
set(carrier, key, value) {
|
|
3316
|
+
carrier.set(key, value);
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
function injectIviTelemetryHeaders(headers, context) {
|
|
3320
|
+
propagation.inject(context, headers, headersTextMapSetter);
|
|
3321
|
+
}
|
|
3322
|
+
async function readJsonResponse(response) {
|
|
3323
|
+
const text = await response.text();
|
|
3324
|
+
if (!text) {
|
|
3325
|
+
return null;
|
|
3326
|
+
}
|
|
3327
|
+
try {
|
|
3328
|
+
return JSON.parse(text);
|
|
3329
|
+
} catch {
|
|
3330
|
+
return text;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
function buildWebSocketUrl(endpoint, options) {
|
|
3334
|
+
const rawUrl = options?.url ?? endpoint;
|
|
3335
|
+
if (!options?.query) {
|
|
3336
|
+
return rawUrl;
|
|
3337
|
+
}
|
|
3338
|
+
const url = new URL(rawUrl);
|
|
3339
|
+
Object.entries(options.query).forEach(([key, value]) => {
|
|
3340
|
+
if (value === void 0 || value === null) return;
|
|
3341
|
+
url.searchParams.set(key, value);
|
|
3342
|
+
});
|
|
3343
|
+
return url.toString();
|
|
3344
|
+
}
|
|
3345
|
+
function toBootstrapSource(prebuiltPlayback, options) {
|
|
3346
|
+
return {
|
|
3347
|
+
sourceId: prebuiltPlayback.sourceId,
|
|
3348
|
+
streamId: prebuiltPlayback.streamId,
|
|
3349
|
+
slot: options.bootstrapSlot ?? "main",
|
|
3350
|
+
trackId: options.bootstrapTrackId ?? "__ivi_bootstrap_track",
|
|
3351
|
+
playback: prebuiltPlayback.playback,
|
|
3352
|
+
source: {
|
|
3353
|
+
source_id: prebuiltPlayback.sourceId,
|
|
3354
|
+
kind: prebuiltPlayback.streamId ? "generation_stream" : "stream",
|
|
3355
|
+
stream_id: prebuiltPlayback.streamId,
|
|
3356
|
+
asset_type: "video",
|
|
3357
|
+
metadata: {
|
|
3358
|
+
label: "prebuilt"
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
function buildPrebuiltPlayback(request, requestBody, trtcInfo, livekitInfo) {
|
|
3364
|
+
if (!trtcInfo && !livekitInfo) {
|
|
3365
|
+
return void 0;
|
|
3366
|
+
}
|
|
3367
|
+
const streamId = resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo);
|
|
3368
|
+
const sourceId = request.prebuiltStream?.sourceId ?? streamId;
|
|
3369
|
+
if (!sourceId) {
|
|
3370
|
+
return void 0;
|
|
3371
|
+
}
|
|
3372
|
+
if (trtcInfo) {
|
|
3373
|
+
return {
|
|
3374
|
+
sourceId,
|
|
3375
|
+
streamId,
|
|
3376
|
+
kind: "trtc",
|
|
3377
|
+
playback: {
|
|
3378
|
+
type: "trtc",
|
|
3379
|
+
trtc: trtcInfo
|
|
3380
|
+
},
|
|
3381
|
+
provisional: true
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
if (livekitInfo) {
|
|
3385
|
+
return {
|
|
3386
|
+
sourceId,
|
|
3387
|
+
streamId,
|
|
3388
|
+
kind: "livekit",
|
|
3389
|
+
playback: {
|
|
3390
|
+
type: "livekit",
|
|
3391
|
+
livekit: livekitInfo
|
|
3392
|
+
},
|
|
3393
|
+
provisional: true
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
return void 0;
|
|
3397
|
+
}
|
|
3398
|
+
function resolvePrebuiltStreamId(request, requestBody, trtcInfo, livekitInfo) {
|
|
3399
|
+
const prebuiltStream = getObjectValue(requestBody, "prebuilt_stream");
|
|
3400
|
+
return request.prebuiltStream?.streamId ?? getString(prebuiltStream, "stream_id") ?? getString(prebuiltStream, "streamId") ?? trtcInfo?.room_id ?? livekitInfo?.room;
|
|
3401
|
+
}
|
|
3402
|
+
function normalizeTrtcInfo(value) {
|
|
3403
|
+
if (!isRecord(value)) {
|
|
3404
|
+
return void 0;
|
|
3405
|
+
}
|
|
3406
|
+
const appId = getString(value, "app_id") ?? getString(value, "appId");
|
|
3407
|
+
const roomId = getString(value, "room_id") ?? getString(value, "roomId");
|
|
3408
|
+
const userId = getString(value, "user_id") ?? getString(value, "userId");
|
|
3409
|
+
const userSig = getString(value, "user_sig") ?? getString(value, "userSig");
|
|
3410
|
+
if (!appId || !roomId || !userId || !userSig) {
|
|
3411
|
+
return void 0;
|
|
3412
|
+
}
|
|
3413
|
+
const normalized = {
|
|
3414
|
+
...value,
|
|
3415
|
+
app_id: appId,
|
|
3416
|
+
room_id: roomId,
|
|
3417
|
+
user_id: userId,
|
|
3418
|
+
user_sig: userSig
|
|
3419
|
+
};
|
|
3420
|
+
delete normalized.secret_key;
|
|
3421
|
+
delete normalized.secretKey;
|
|
3422
|
+
return normalized;
|
|
3423
|
+
}
|
|
3424
|
+
function normalizeLivekitInfo(value) {
|
|
3425
|
+
if (!isRecord(value)) {
|
|
3426
|
+
return void 0;
|
|
3427
|
+
}
|
|
3428
|
+
const wsUrl = getString(value, "ws_url") ?? getString(value, "url") ?? getString(value, "wsUrl");
|
|
3429
|
+
const token = getString(value, "token") ?? getString(value, "access_token") ?? getString(value, "accessToken");
|
|
3430
|
+
if (!wsUrl || !token) {
|
|
3431
|
+
return void 0;
|
|
3432
|
+
}
|
|
3433
|
+
return {
|
|
3434
|
+
ws_url: wsUrl,
|
|
3435
|
+
token,
|
|
3436
|
+
room: getString(value, "room") ?? getString(value, "room_name") ?? getString(value, "roomName"),
|
|
3437
|
+
identity: getString(value, "identity")
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
function serializePrebuiltPayload(value) {
|
|
3441
|
+
if (typeof value === "string") {
|
|
3442
|
+
return value;
|
|
3443
|
+
}
|
|
3444
|
+
return encodeJsonBase64(value);
|
|
3445
|
+
}
|
|
3446
|
+
function encodeJsonBase64(value) {
|
|
3447
|
+
const bytes = new TextEncoder().encode(JSON.stringify(value));
|
|
3448
|
+
let binary = "";
|
|
3449
|
+
bytes.forEach((byte) => {
|
|
3450
|
+
binary += String.fromCharCode(byte);
|
|
3451
|
+
});
|
|
3452
|
+
if (typeof btoa === "function") {
|
|
3453
|
+
return btoa(binary);
|
|
3454
|
+
}
|
|
3455
|
+
const maybeBuffer = globalThis.Buffer;
|
|
3456
|
+
if (maybeBuffer) {
|
|
3457
|
+
return maybeBuffer.from(bytes).toString("base64");
|
|
3458
|
+
}
|
|
3459
|
+
throw new Error("Base64 encoder is not available in this runtime.");
|
|
3460
|
+
}
|
|
3461
|
+
function unwrapResponsePayload(value) {
|
|
3462
|
+
if (!isRecord(value)) {
|
|
3463
|
+
return {};
|
|
3464
|
+
}
|
|
3465
|
+
const data = getObjectValue(value, "data");
|
|
3466
|
+
return data ?? value;
|
|
3467
|
+
}
|
|
3468
|
+
function getObjectValue(value, key) {
|
|
3469
|
+
if (!isRecord(value)) {
|
|
3470
|
+
return void 0;
|
|
3471
|
+
}
|
|
3472
|
+
const nested = value[key];
|
|
3473
|
+
return isRecord(nested) ? nested : void 0;
|
|
3474
|
+
}
|
|
3475
|
+
function getString(value, key) {
|
|
3476
|
+
if (!isRecord(value)) {
|
|
3477
|
+
return void 0;
|
|
3478
|
+
}
|
|
3479
|
+
const nested = value[key];
|
|
3480
|
+
return typeof nested === "string" && nested.trim().length > 0 ? nested.trim() : void 0;
|
|
3481
|
+
}
|
|
3482
|
+
function isRecord(value) {
|
|
3483
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3484
|
+
}
|
|
3485
|
+
function ensureTrailingSlash(value) {
|
|
3486
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
// src/sdk.ts
|
|
2733
3490
|
var IviFrontendSdk = class {
|
|
3491
|
+
constructor(config = {}) {
|
|
3492
|
+
this.config = config;
|
|
3493
|
+
}
|
|
2734
3494
|
createClient(config) {
|
|
2735
|
-
return new IviClient(
|
|
3495
|
+
return new IviClient({
|
|
3496
|
+
...config,
|
|
3497
|
+
telemetry: mergeSdkTelemetryOptions(config.telemetry, this.config.telemetry)
|
|
3498
|
+
});
|
|
2736
3499
|
}
|
|
2737
3500
|
createRuntimeCoordinator(clientConfig, runtimeConfig) {
|
|
2738
3501
|
const client = this.createClient(clientConfig);
|
|
2739
|
-
return new IviRuntimeCoordinator(client,
|
|
3502
|
+
return new IviRuntimeCoordinator(client, {
|
|
3503
|
+
...runtimeConfig ?? {},
|
|
3504
|
+
telemetry: mergeSdkTelemetryOptions(runtimeConfig?.telemetry, this.config.telemetry)
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
createIVISession(request, options) {
|
|
3508
|
+
const telemetry = mergeSdkTelemetryChain(
|
|
3509
|
+
this.config.telemetry,
|
|
3510
|
+
this.config.http?.telemetry,
|
|
3511
|
+
options?.telemetry
|
|
3512
|
+
);
|
|
3513
|
+
return createIVISession(request, {
|
|
3514
|
+
...this.config.http ?? {},
|
|
3515
|
+
...options ?? {},
|
|
3516
|
+
telemetry
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3519
|
+
createRuntimeFromSession(session, options) {
|
|
3520
|
+
return createRuntimeFromSession(session, {
|
|
3521
|
+
...options ?? {},
|
|
3522
|
+
telemetry: mergeSdkTelemetryChain(this.config.telemetry, options?.telemetry)
|
|
3523
|
+
});
|
|
3524
|
+
}
|
|
3525
|
+
createIVISessionRuntime(request, options) {
|
|
3526
|
+
const telemetry = mergeSdkTelemetryChain(
|
|
3527
|
+
this.config.telemetry,
|
|
3528
|
+
this.config.http?.telemetry,
|
|
3529
|
+
options?.http?.telemetry,
|
|
3530
|
+
options?.telemetry
|
|
3531
|
+
);
|
|
3532
|
+
const http = {
|
|
3533
|
+
...this.config.http ?? {},
|
|
3534
|
+
...options?.http ?? {},
|
|
3535
|
+
telemetry
|
|
3536
|
+
};
|
|
3537
|
+
return createIVISessionRuntime(request, {
|
|
3538
|
+
...options ?? {},
|
|
3539
|
+
telemetry,
|
|
3540
|
+
http
|
|
3541
|
+
});
|
|
2740
3542
|
}
|
|
2741
3543
|
};
|
|
3544
|
+
function mergeSdkTelemetryChain(...items) {
|
|
3545
|
+
return items.reduce(
|
|
3546
|
+
(merged, item) => mergeSdkTelemetryOptions(item, merged),
|
|
3547
|
+
void 0
|
|
3548
|
+
);
|
|
3549
|
+
}
|
|
3550
|
+
function mergeSdkTelemetryOptions(explicit, inherited) {
|
|
3551
|
+
if (!explicit && !inherited) {
|
|
3552
|
+
return void 0;
|
|
3553
|
+
}
|
|
3554
|
+
return {
|
|
3555
|
+
tracer: explicit?.tracer ?? inherited?.tracer,
|
|
3556
|
+
context: explicit?.context ?? inherited?.context,
|
|
3557
|
+
attributes: mergeIviTelemetryAttributes(inherited?.attributes, explicit?.attributes)
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
2742
3560
|
var IviStageViewContext = createContext(null);
|
|
2743
3561
|
var EMPTY_RUNTIME_STATE = {
|
|
2744
3562
|
status: "idle",
|
|
@@ -2748,7 +3566,8 @@ var EMPTY_RUNTIME_STATE = {
|
|
|
2748
3566
|
sources: /* @__PURE__ */ new Map(),
|
|
2749
3567
|
streams: /* @__PURE__ */ new Map(),
|
|
2750
3568
|
conversationItems: /* @__PURE__ */ new Map(),
|
|
2751
|
-
conversations: []
|
|
3569
|
+
conversations: [],
|
|
3570
|
+
bootstrap: null
|
|
2752
3571
|
};
|
|
2753
3572
|
function useRuntimeState(runtime) {
|
|
2754
3573
|
const [state, setState] = useState(() => runtime?.getState() ?? EMPTY_RUNTIME_STATE);
|
|
@@ -2776,10 +3595,10 @@ function IVIStageView(props) {
|
|
|
2776
3595
|
const state = useRuntimeState(runtime);
|
|
2777
3596
|
const slotTrackMap = useMemo(() => {
|
|
2778
3597
|
return buildSlotTrackMapFromState(state);
|
|
2779
|
-
}, [state.stage]);
|
|
3598
|
+
}, [state.stage, state.bootstrap]);
|
|
2780
3599
|
const slotBindings = useMemo(() => {
|
|
2781
3600
|
return buildSlotBindingsFromState(state);
|
|
2782
|
-
}, [state.stage, state.tracks, state.sources]);
|
|
3601
|
+
}, [state.stage, state.tracks, state.sources, state.bootstrap]);
|
|
2783
3602
|
useEffect(() => {
|
|
2784
3603
|
onBindingsChange?.(slotBindings);
|
|
2785
3604
|
}, [slotBindings, onBindingsChange]);
|
|
@@ -2819,15 +3638,18 @@ function buildSlotTrackMapFromState(state) {
|
|
|
2819
3638
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2820
3639
|
map.set(item.slot, item.track_id);
|
|
2821
3640
|
});
|
|
3641
|
+
if (state.bootstrap?.active) {
|
|
3642
|
+
map.set(state.bootstrap.slot, state.bootstrap.trackId);
|
|
3643
|
+
}
|
|
2822
3644
|
return map;
|
|
2823
3645
|
}
|
|
2824
3646
|
function buildSlotBindingsFromState(state) {
|
|
2825
|
-
const
|
|
3647
|
+
const bindingsBySlot = /* @__PURE__ */ new Map();
|
|
2826
3648
|
(state.stage?.composition ?? []).forEach((item) => {
|
|
2827
3649
|
const track = state.tracks.get(item.track_id);
|
|
2828
3650
|
const sourceId = track?.active_source_id ?? null;
|
|
2829
3651
|
const source = sourceId ? state.sources.get(sourceId) : void 0;
|
|
2830
|
-
|
|
3652
|
+
bindingsBySlot.set(item.slot, {
|
|
2831
3653
|
slot: item.slot,
|
|
2832
3654
|
trackId: item.track_id,
|
|
2833
3655
|
track,
|
|
@@ -2835,7 +3657,18 @@ function buildSlotBindingsFromState(state) {
|
|
|
2835
3657
|
source
|
|
2836
3658
|
});
|
|
2837
3659
|
});
|
|
2838
|
-
|
|
3660
|
+
if (state.bootstrap?.active) {
|
|
3661
|
+
const track = state.tracks.get(state.bootstrap.trackId);
|
|
3662
|
+
const source = state.sources.get(state.bootstrap.sourceId);
|
|
3663
|
+
bindingsBySlot.set(state.bootstrap.slot, {
|
|
3664
|
+
slot: state.bootstrap.slot,
|
|
3665
|
+
trackId: state.bootstrap.trackId,
|
|
3666
|
+
track,
|
|
3667
|
+
sourceId: state.bootstrap.sourceId,
|
|
3668
|
+
source
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
return Array.from(bindingsBySlot.values());
|
|
2839
3672
|
}
|
|
2840
3673
|
var VOLUME_STORAGE_KEY = "ivi-volume-preferences";
|
|
2841
3674
|
var DEFAULT_VOLUME = 80;
|
|
@@ -3603,23 +4436,25 @@ function IVITrtcPlayer(props) {
|
|
|
3603
4436
|
const [loading, setLoading] = useState(true);
|
|
3604
4437
|
const [error, setError] = useState(null);
|
|
3605
4438
|
const mutedRef = useRef(muted);
|
|
4439
|
+
const attachedSourceIdRef = useRef(null);
|
|
3606
4440
|
mutedRef.current = muted;
|
|
4441
|
+
useEffect(() => {
|
|
4442
|
+
const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
|
|
4443
|
+
setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
|
|
4444
|
+
setError(snapshot.status === "error" ? snapshot.error ?? "TRTC \u62C9\u6D41\u5931\u8D25" : null);
|
|
4445
|
+
});
|
|
4446
|
+
return unsubscribe;
|
|
4447
|
+
}, [manager, resolvedSourceId]);
|
|
3607
4448
|
useEffect(() => {
|
|
3608
4449
|
const container = containerRef.current;
|
|
3609
4450
|
if (!container) {
|
|
3610
4451
|
return;
|
|
3611
4452
|
}
|
|
3612
4453
|
let disposed = false;
|
|
4454
|
+
attachedSourceIdRef.current = resolvedSourceId;
|
|
3613
4455
|
if (shouldManageSourceLifecycle) {
|
|
3614
4456
|
manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
|
|
3615
4457
|
}
|
|
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
4458
|
void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
|
|
3624
4459
|
if (disposed) {
|
|
3625
4460
|
return;
|
|
@@ -3629,15 +4464,15 @@ function IVITrtcPlayer(props) {
|
|
|
3629
4464
|
});
|
|
3630
4465
|
return () => {
|
|
3631
4466
|
disposed = true;
|
|
3632
|
-
|
|
3633
|
-
manager.detachView(
|
|
4467
|
+
const attachedSourceId = attachedSourceIdRef.current ?? resolvedSourceId;
|
|
4468
|
+
manager.detachView(attachedSourceId, viewIdRef.current);
|
|
3634
4469
|
if (shouldManageSourceLifecycle) {
|
|
3635
|
-
manager.removeSource(
|
|
4470
|
+
manager.removeSource(attachedSourceId);
|
|
3636
4471
|
}
|
|
4472
|
+
attachedSourceIdRef.current = null;
|
|
3637
4473
|
};
|
|
3638
4474
|
}, [
|
|
3639
4475
|
manager,
|
|
3640
|
-
resolvedSourceId,
|
|
3641
4476
|
shouldManageSourceLifecycle,
|
|
3642
4477
|
trtc.app_id,
|
|
3643
4478
|
trtc.room_id,
|
|
@@ -3645,6 +4480,15 @@ function IVITrtcPlayer(props) {
|
|
|
3645
4480
|
trtc.user_sig,
|
|
3646
4481
|
trtcAIDenoiser
|
|
3647
4482
|
]);
|
|
4483
|
+
useEffect(() => {
|
|
4484
|
+
const previousSourceId = attachedSourceIdRef.current;
|
|
4485
|
+
if (!previousSourceId || previousSourceId === resolvedSourceId) {
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
if (manager.reassignView(previousSourceId, resolvedSourceId, viewIdRef.current)) {
|
|
4489
|
+
attachedSourceIdRef.current = resolvedSourceId;
|
|
4490
|
+
}
|
|
4491
|
+
}, [manager, resolvedSourceId]);
|
|
3648
4492
|
useEffect(() => {
|
|
3649
4493
|
manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
|
|
3650
4494
|
}, [manager, muted, resolvedSourceId]);
|
|
@@ -3913,7 +4757,16 @@ function makeLoadPolicy(label, onLog) {
|
|
|
3913
4757
|
};
|
|
3914
4758
|
}
|
|
3915
4759
|
function IVIHlsVideo(props) {
|
|
3916
|
-
const {
|
|
4760
|
+
const {
|
|
4761
|
+
url,
|
|
4762
|
+
sourceId,
|
|
4763
|
+
videoProps,
|
|
4764
|
+
style,
|
|
4765
|
+
aggressivePreload = false,
|
|
4766
|
+
paused = false,
|
|
4767
|
+
onLog,
|
|
4768
|
+
telemetry
|
|
4769
|
+
} = props;
|
|
3917
4770
|
const videoRef = useRef(null);
|
|
3918
4771
|
const pausedRef = useRef(paused);
|
|
3919
4772
|
useEffect(() => {
|
|
@@ -3950,6 +4803,7 @@ function IVIHlsVideo(props) {
|
|
|
3950
4803
|
const onVideoError = () => {
|
|
3951
4804
|
const el = videoRef.current;
|
|
3952
4805
|
const mediaErr = el?.error;
|
|
4806
|
+
recordVideoTelemetry("ivi.media.video.error", telemetry, sourceId, "hls", mediaErr);
|
|
3953
4807
|
emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
|
|
3954
4808
|
code: mediaErr?.code,
|
|
3955
4809
|
message: mediaErr?.message,
|
|
@@ -3959,6 +4813,13 @@ function IVIHlsVideo(props) {
|
|
|
3959
4813
|
});
|
|
3960
4814
|
};
|
|
3961
4815
|
const onVideoStalled = () => {
|
|
4816
|
+
recordVideoTelemetry(
|
|
4817
|
+
"ivi.media.video.stalled",
|
|
4818
|
+
telemetry,
|
|
4819
|
+
sourceId,
|
|
4820
|
+
"hls",
|
|
4821
|
+
new Error("<video> stalled")
|
|
4822
|
+
);
|
|
3962
4823
|
emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
|
|
3963
4824
|
currentTime: videoRef.current?.currentTime,
|
|
3964
4825
|
readyState: videoRef.current?.readyState
|
|
@@ -4101,7 +4962,7 @@ function IVIHlsVideo(props) {
|
|
|
4101
4962
|
hlsInstance?.destroy();
|
|
4102
4963
|
hlsInstance = null;
|
|
4103
4964
|
};
|
|
4104
|
-
}, [url, aggressivePreload, onLog]);
|
|
4965
|
+
}, [url, sourceId, aggressivePreload, onLog, telemetry]);
|
|
4105
4966
|
return /* @__PURE__ */ jsx(
|
|
4106
4967
|
"video",
|
|
4107
4968
|
{
|
|
@@ -4114,6 +4975,17 @@ function IVIHlsVideo(props) {
|
|
|
4114
4975
|
}
|
|
4115
4976
|
);
|
|
4116
4977
|
}
|
|
4978
|
+
function recordVideoTelemetry(name, telemetry, sourceId, kind, error) {
|
|
4979
|
+
recordIviTelemetrySpan(
|
|
4980
|
+
name,
|
|
4981
|
+
telemetry,
|
|
4982
|
+
{
|
|
4983
|
+
"ivi.source.id": sourceId,
|
|
4984
|
+
"ivi.media.kind": kind
|
|
4985
|
+
},
|
|
4986
|
+
error
|
|
4987
|
+
);
|
|
4988
|
+
}
|
|
4117
4989
|
function isM3u8Url(url) {
|
|
4118
4990
|
return /\.m3u8(?:$|[?#])/i.test(url);
|
|
4119
4991
|
}
|
|
@@ -4152,13 +5024,7 @@ function getSourceRenderKey(sourceId, source) {
|
|
|
4152
5024
|
if (!trtc) {
|
|
4153
5025
|
return sourceId;
|
|
4154
5026
|
}
|
|
4155
|
-
return [
|
|
4156
|
-
"trtc",
|
|
4157
|
-
trtc.app_id ?? "",
|
|
4158
|
-
trtc.room_id ?? "",
|
|
4159
|
-
trtc.user_id ?? "",
|
|
4160
|
-
trtc.user_sig ?? ""
|
|
4161
|
-
].join(":");
|
|
5027
|
+
return ["trtc", trtc.app_id ?? "", trtc.room_id ?? ""].join(":");
|
|
4162
5028
|
}
|
|
4163
5029
|
function TrackSlotMediaContent(props) {
|
|
4164
5030
|
const {
|
|
@@ -4185,6 +5051,7 @@ function TrackSlotMediaContent(props) {
|
|
|
4185
5051
|
source,
|
|
4186
5052
|
isPreloading: !isActive
|
|
4187
5053
|
};
|
|
5054
|
+
const telemetry = runtime?.getTelemetryOptions();
|
|
4188
5055
|
const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
|
|
4189
5056
|
const shouldMute = !isActive;
|
|
4190
5057
|
if (renderMedia) return renderMedia(renderContext);
|
|
@@ -4256,9 +5123,11 @@ function TrackSlotMediaContent(props) {
|
|
|
4256
5123
|
IVIHlsVideo,
|
|
4257
5124
|
{
|
|
4258
5125
|
url: playbackUrl,
|
|
5126
|
+
sourceId: slotSourceId,
|
|
4259
5127
|
videoProps: mergedVideoProps,
|
|
4260
5128
|
style: videoStyle,
|
|
4261
|
-
paused: shouldPause
|
|
5129
|
+
paused: shouldPause,
|
|
5130
|
+
telemetry
|
|
4262
5131
|
}
|
|
4263
5132
|
);
|
|
4264
5133
|
}
|
|
@@ -4280,6 +5149,7 @@ function TrackSlotMediaContent(props) {
|
|
|
4280
5149
|
SlotVideo,
|
|
4281
5150
|
{
|
|
4282
5151
|
src: playbackUrl,
|
|
5152
|
+
sourceId: slotSourceId,
|
|
4283
5153
|
paused: shouldPause,
|
|
4284
5154
|
videoProps: {
|
|
4285
5155
|
...mergedVideoProps,
|
|
@@ -4287,7 +5157,8 @@ function TrackSlotMediaContent(props) {
|
|
|
4287
5157
|
playsInline: videoProps?.playsInline ?? true,
|
|
4288
5158
|
controls: isActive ? videoProps?.controls ?? true : false
|
|
4289
5159
|
},
|
|
4290
|
-
style: videoStyle
|
|
5160
|
+
style: videoStyle,
|
|
5161
|
+
telemetry
|
|
4291
5162
|
}
|
|
4292
5163
|
);
|
|
4293
5164
|
}
|
|
@@ -4337,17 +5208,48 @@ function SlotVideo(props) {
|
|
|
4337
5208
|
video.play().catch(() => void 0);
|
|
4338
5209
|
}
|
|
4339
5210
|
}, [props.paused]);
|
|
5211
|
+
const onError = (event) => {
|
|
5212
|
+
recordPlainVideoTelemetry(
|
|
5213
|
+
"ivi.media.video.error",
|
|
5214
|
+
props.telemetry,
|
|
5215
|
+
props.sourceId,
|
|
5216
|
+
event.currentTarget.error
|
|
5217
|
+
);
|
|
5218
|
+
props.videoProps.onError?.(event);
|
|
5219
|
+
};
|
|
5220
|
+
const onStalled = (event) => {
|
|
5221
|
+
recordPlainVideoTelemetry(
|
|
5222
|
+
"ivi.media.video.stalled",
|
|
5223
|
+
props.telemetry,
|
|
5224
|
+
props.sourceId,
|
|
5225
|
+
new Error("<video> stalled")
|
|
5226
|
+
);
|
|
5227
|
+
props.videoProps.onStalled?.(event);
|
|
5228
|
+
};
|
|
4340
5229
|
return /* @__PURE__ */ jsx(
|
|
4341
5230
|
"video",
|
|
4342
5231
|
{
|
|
4343
5232
|
ref: videoRef,
|
|
4344
5233
|
src: props.src,
|
|
4345
5234
|
...props.videoProps,
|
|
5235
|
+
onError,
|
|
5236
|
+
onStalled,
|
|
4346
5237
|
autoPlay: props.paused ? false : props.videoProps.autoPlay ?? true,
|
|
4347
5238
|
style: props.style
|
|
4348
5239
|
}
|
|
4349
5240
|
);
|
|
4350
5241
|
}
|
|
5242
|
+
function recordPlainVideoTelemetry(name, telemetry, sourceId, error) {
|
|
5243
|
+
recordIviTelemetrySpan(
|
|
5244
|
+
name,
|
|
5245
|
+
telemetry,
|
|
5246
|
+
{
|
|
5247
|
+
"ivi.source.id": sourceId,
|
|
5248
|
+
"ivi.media.kind": "video"
|
|
5249
|
+
},
|
|
5250
|
+
error
|
|
5251
|
+
);
|
|
5252
|
+
}
|
|
4351
5253
|
|
|
4352
5254
|
// src/react/internal/use-multi-preload-sources.ts
|
|
4353
5255
|
function useMultiPreloadSources(sources, activeSourceId) {
|
|
@@ -4614,7 +5516,83 @@ function getClientLogTag(category) {
|
|
|
4614
5516
|
if (category === "reconnect") return "[IVI-RECONNECT]";
|
|
4615
5517
|
return "[IVI-CLIENT]";
|
|
4616
5518
|
}
|
|
5519
|
+
function useIviSessionRuntime(config) {
|
|
5520
|
+
const [status, setStatus] = useState("idle");
|
|
5521
|
+
const [session, setSession] = useState(null);
|
|
5522
|
+
const [runtime, setRuntime] = useState(null);
|
|
5523
|
+
const [client, setClient] = useState(null);
|
|
5524
|
+
const [error, setError] = useState(null);
|
|
5525
|
+
useEffect(() => {
|
|
5526
|
+
const {
|
|
5527
|
+
request,
|
|
5528
|
+
enabled = true,
|
|
5529
|
+
autoStart = true,
|
|
5530
|
+
onCreated,
|
|
5531
|
+
onRuntimeReady,
|
|
5532
|
+
onError,
|
|
5533
|
+
...options
|
|
5534
|
+
} = config;
|
|
5535
|
+
if (!enabled || !request) {
|
|
5536
|
+
setStatus("idle");
|
|
5537
|
+
setSession(null);
|
|
5538
|
+
setRuntime(null);
|
|
5539
|
+
setClient(null);
|
|
5540
|
+
setError(null);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
let disposed = false;
|
|
5544
|
+
let activeRuntime = null;
|
|
5545
|
+
setStatus("creating");
|
|
5546
|
+
setSession(null);
|
|
5547
|
+
setRuntime(null);
|
|
5548
|
+
setClient(null);
|
|
5549
|
+
setError(null);
|
|
5550
|
+
void createIVISessionRuntime(request, options).then(async (created) => {
|
|
5551
|
+
if (disposed) {
|
|
5552
|
+
created.runtime.stop();
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5555
|
+
activeRuntime = created.runtime;
|
|
5556
|
+
setSession(created.session);
|
|
5557
|
+
setRuntime(created.runtime);
|
|
5558
|
+
setClient(created.client);
|
|
5559
|
+
onCreated?.(created.session);
|
|
5560
|
+
onRuntimeReady?.(created.runtime, created.client);
|
|
5561
|
+
if (!autoStart) {
|
|
5562
|
+
setStatus("idle");
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
setStatus("connecting");
|
|
5566
|
+
await created.runtime.start();
|
|
5567
|
+
if (disposed) {
|
|
5568
|
+
created.runtime.stop();
|
|
5569
|
+
return;
|
|
5570
|
+
}
|
|
5571
|
+
setStatus("running");
|
|
5572
|
+
}).catch((caughtError) => {
|
|
5573
|
+
if (disposed) {
|
|
5574
|
+
return;
|
|
5575
|
+
}
|
|
5576
|
+
const normalizedError = caughtError instanceof Error ? caughtError : new Error(String(caughtError));
|
|
5577
|
+
setError(normalizedError);
|
|
5578
|
+
setStatus("error");
|
|
5579
|
+
onError?.(caughtError);
|
|
5580
|
+
});
|
|
5581
|
+
return () => {
|
|
5582
|
+
disposed = true;
|
|
5583
|
+
activeRuntime?.stop();
|
|
5584
|
+
setStatus("stopped");
|
|
5585
|
+
};
|
|
5586
|
+
}, [config]);
|
|
5587
|
+
return {
|
|
5588
|
+
status,
|
|
5589
|
+
session,
|
|
5590
|
+
runtime,
|
|
5591
|
+
client,
|
|
5592
|
+
error
|
|
5593
|
+
};
|
|
5594
|
+
}
|
|
4617
5595
|
|
|
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 };
|
|
5596
|
+
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
5597
|
//# sourceMappingURL=index.js.map
|
|
4620
5598
|
//# sourceMappingURL=index.js.map
|