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