@vivix-ai/ivi-frontend-sdk 0.2.3 → 0.3.1
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 +183 -108
- package/dist/index.cjs +1027 -391
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -37
- package/dist/index.d.ts +196 -37
- package/dist/index.js +1020 -393
- package/dist/index.js.map +1 -1
- package/package.json +20 -4
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var iviSdkTs = require('@vivix/ivi-sdk-ts');
|
|
3
|
+
var iviSdkTs = require('@vivix-ai/ivi-sdk-ts');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
@@ -28,7 +28,6 @@ function logIviEventReceived(event) {
|
|
|
28
28
|
args: [message, event.raw],
|
|
29
29
|
data: event.raw
|
|
30
30
|
});
|
|
31
|
-
console.log(message, event.raw);
|
|
32
31
|
}
|
|
33
32
|
function logIviStateChange(entity, key, eventType, before, after) {
|
|
34
33
|
if (!didChange(before, after)) {
|
|
@@ -47,7 +46,6 @@ function logIviStateChange(entity, key, eventType, before, after) {
|
|
|
47
46
|
args: [message, data],
|
|
48
47
|
data
|
|
49
48
|
});
|
|
50
|
-
console.log(message, data);
|
|
51
49
|
}
|
|
52
50
|
function didChange(before, after) {
|
|
53
51
|
if (before === after) return false;
|
|
@@ -110,7 +108,7 @@ var SessionEventHandler = class {
|
|
|
110
108
|
this.callbacks = callbacks;
|
|
111
109
|
}
|
|
112
110
|
handle(event) {
|
|
113
|
-
if (event instanceof iviSdkTs.
|
|
111
|
+
if (event instanceof iviSdkTs.ReceiveSessionCreatedEvent) {
|
|
114
112
|
const before = this.sessionManager.getSession();
|
|
115
113
|
this.sessionManager.setSession(event.session);
|
|
116
114
|
logIviStateChange("session", null, event.type, before, this.sessionManager.getSession());
|
|
@@ -259,7 +257,7 @@ var SourceEventHandler = class {
|
|
|
259
257
|
const before = this.sourceManager.get(sourceId);
|
|
260
258
|
this.sourceManager.upsertCreated(source);
|
|
261
259
|
this.sourceManager.applyPreload(sourceId, {
|
|
262
|
-
autoclearAfterPlay: event.autoclearAfterPlay
|
|
260
|
+
autoclearAfterPlay: event.autoclearAfterPlay ?? true
|
|
263
261
|
});
|
|
264
262
|
logIviStateChange("source", sourceId, event.type, before, this.sourceManager.get(sourceId));
|
|
265
263
|
}
|
|
@@ -301,7 +299,7 @@ var StreamEventHandler = class {
|
|
|
301
299
|
this.callbacks = callbacks;
|
|
302
300
|
}
|
|
303
301
|
handle(event) {
|
|
304
|
-
if (event instanceof iviSdkTs.
|
|
302
|
+
if (event instanceof iviSdkTs.ReceiveSessionStreamCreatedEvent) {
|
|
305
303
|
const streamId = event.stream.stream_id;
|
|
306
304
|
const before = this.streamManager.getAll().get(streamId);
|
|
307
305
|
this.streamManager.upsertCreated(event.stream);
|
|
@@ -362,7 +360,7 @@ var ConversationEventHandler = class {
|
|
|
362
360
|
handle(event) {
|
|
363
361
|
if (event instanceof iviSdkTs.ReceiveConversationListResponseEvent) {
|
|
364
362
|
const before = snapshotMap4(this.conversationManager.getAllMap());
|
|
365
|
-
this.conversationManager.replaceAll(event.items);
|
|
363
|
+
this.conversationManager.replaceAll(event.items.filter(hasConversationItemId));
|
|
366
364
|
logIviStateChange(
|
|
367
365
|
"conversations(list)",
|
|
368
366
|
null,
|
|
@@ -374,6 +372,9 @@ var ConversationEventHandler = class {
|
|
|
374
372
|
return { handled: true };
|
|
375
373
|
}
|
|
376
374
|
if (event instanceof iviSdkTs.ReceiveConversationItemAddedEvent) {
|
|
375
|
+
if (!hasConversationItemId(event.item)) {
|
|
376
|
+
return { handled: true };
|
|
377
|
+
}
|
|
377
378
|
const before = this.conversationManager.getAllMap().get(event.item.id);
|
|
378
379
|
this.conversationManager.upsertAdded(event.item);
|
|
379
380
|
logIviStateChange(
|
|
@@ -387,6 +388,9 @@ var ConversationEventHandler = class {
|
|
|
387
388
|
return { handled: true };
|
|
388
389
|
}
|
|
389
390
|
if (event instanceof iviSdkTs.ReceiveConversationItemDoneEvent) {
|
|
391
|
+
if (!hasConversationItemId(event.item)) {
|
|
392
|
+
return { handled: true };
|
|
393
|
+
}
|
|
390
394
|
const before = this.conversationManager.getAllMap().get(event.item.id);
|
|
391
395
|
this.conversationManager.markDone(event.item);
|
|
392
396
|
logIviStateChange(
|
|
@@ -400,6 +404,9 @@ var ConversationEventHandler = class {
|
|
|
400
404
|
return { handled: true };
|
|
401
405
|
}
|
|
402
406
|
if (event instanceof iviSdkTs.ReceiveResponseOutputTextDeltaEvent) {
|
|
407
|
+
if (!event.itemId) {
|
|
408
|
+
return { handled: true };
|
|
409
|
+
}
|
|
403
410
|
const before = this.conversationManager.getAllMap().get(event.itemId);
|
|
404
411
|
this.conversationManager.applyTextDelta(event.itemId, event.delta);
|
|
405
412
|
logIviStateChange(
|
|
@@ -413,6 +420,9 @@ var ConversationEventHandler = class {
|
|
|
413
420
|
return { handled: true };
|
|
414
421
|
}
|
|
415
422
|
if (event instanceof iviSdkTs.ReceiveResponseOutputTextDoneEvent) {
|
|
423
|
+
if (!event.itemId) {
|
|
424
|
+
return { handled: true };
|
|
425
|
+
}
|
|
416
426
|
const before = this.conversationManager.getAllMap().get(event.itemId);
|
|
417
427
|
this.conversationManager.applyTextDone(event.itemId, event.text);
|
|
418
428
|
logIviStateChange(
|
|
@@ -426,6 +436,9 @@ var ConversationEventHandler = class {
|
|
|
426
436
|
return { handled: true };
|
|
427
437
|
}
|
|
428
438
|
if (event instanceof iviSdkTs.ReceiveResponseOutputAudioTranscriptDeltaEvent) {
|
|
439
|
+
if (!event.itemId) {
|
|
440
|
+
return { handled: true };
|
|
441
|
+
}
|
|
429
442
|
const before = this.conversationManager.getAllMap().get(event.itemId);
|
|
430
443
|
this.conversationManager.applyTranscriptDelta(event.itemId, event.delta);
|
|
431
444
|
logIviStateChange(
|
|
@@ -439,6 +452,9 @@ var ConversationEventHandler = class {
|
|
|
439
452
|
return { handled: true };
|
|
440
453
|
}
|
|
441
454
|
if (event instanceof iviSdkTs.ReceiveResponseOutputAudioTranscriptDoneEvent) {
|
|
455
|
+
if (!event.itemId) {
|
|
456
|
+
return { handled: true };
|
|
457
|
+
}
|
|
442
458
|
const before = this.conversationManager.getAllMap().get(event.itemId);
|
|
443
459
|
this.conversationManager.applyTranscriptDone(event.itemId, event.transcript);
|
|
444
460
|
logIviStateChange(
|
|
@@ -480,6 +496,9 @@ function snapshotMap4(map) {
|
|
|
480
496
|
});
|
|
481
497
|
return snap;
|
|
482
498
|
}
|
|
499
|
+
function hasConversationItemId(item) {
|
|
500
|
+
return typeof item.id === "string" && item.id.length > 0;
|
|
501
|
+
}
|
|
483
502
|
|
|
484
503
|
// src/runtime/managers/session-manager.ts
|
|
485
504
|
var SessionManager = class {
|
|
@@ -550,13 +569,13 @@ var TrackManager = class {
|
|
|
550
569
|
}
|
|
551
570
|
this.patchTrack(trackId, {
|
|
552
571
|
active_source_id: resolvedSourceId,
|
|
553
|
-
next_source_id:
|
|
572
|
+
next_source_id: void 0
|
|
554
573
|
});
|
|
555
574
|
}
|
|
556
575
|
applyTrackCued(trackId, sourceId) {
|
|
557
576
|
this.patchTrack(trackId, {
|
|
558
577
|
active_source_id: sourceId,
|
|
559
|
-
next_source_id:
|
|
578
|
+
next_source_id: void 0
|
|
560
579
|
});
|
|
561
580
|
}
|
|
562
581
|
applyTrackNextSet(trackId, sourceId) {
|
|
@@ -1120,7 +1139,7 @@ var TrtcSourceManager = class {
|
|
|
1120
1139
|
upsertSource(sourceId, trtc) {
|
|
1121
1140
|
const existing = this.sessions.get(sourceId);
|
|
1122
1141
|
if (!existing) {
|
|
1123
|
-
this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${trtc
|
|
1142
|
+
this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")} user=${getTrtcString(trtc, "user_id")}`);
|
|
1124
1143
|
const session2 = this.createSession(sourceId, trtc);
|
|
1125
1144
|
this.sessions.set(sourceId, session2);
|
|
1126
1145
|
void this.ensureConnected(session2);
|
|
@@ -1129,7 +1148,7 @@ var TrtcSourceManager = class {
|
|
|
1129
1148
|
if (isSameTrtcConfig(existing.trtc, trtc)) {
|
|
1130
1149
|
return;
|
|
1131
1150
|
}
|
|
1132
|
-
this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${trtc
|
|
1151
|
+
this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")}`);
|
|
1133
1152
|
void this.disposeSession(existing);
|
|
1134
1153
|
const session = this.createSession(sourceId, trtc);
|
|
1135
1154
|
this.sessions.set(sourceId, session);
|
|
@@ -1303,11 +1322,14 @@ var TrtcSourceManager = class {
|
|
|
1303
1322
|
session.connectPromise = (async () => {
|
|
1304
1323
|
const m = await import('trtc-sdk-v5');
|
|
1305
1324
|
const TRTC = m.default ?? m;
|
|
1306
|
-
const
|
|
1325
|
+
const roomId = getTrtcString(session.trtc, "room_id");
|
|
1326
|
+
const userId = getTrtcString(session.trtc, "user_id");
|
|
1327
|
+
const userSig = getTrtcString(session.trtc, "user_sig");
|
|
1328
|
+
const sdkAppId = Number(getTrtcString(session.trtc, "app_id"));
|
|
1307
1329
|
if (!Number.isFinite(sdkAppId)) {
|
|
1308
1330
|
throw new Error("TRTC app_id \u5FC5\u987B\u662F\u6570\u5B57\u5B57\u7B26\u4E32\u3002");
|
|
1309
1331
|
}
|
|
1310
|
-
const isStringRoomId = shouldUseStringRoomId(
|
|
1332
|
+
const isStringRoomId = shouldUseStringRoomId(roomId);
|
|
1311
1333
|
const client = TRTC.create();
|
|
1312
1334
|
const onRemoteVideoAvailable = (event) => {
|
|
1313
1335
|
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
|
|
@@ -1355,21 +1377,21 @@ var TrtcSourceManager = class {
|
|
|
1355
1377
|
client.on(TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, onRemoteVideoUnavailable);
|
|
1356
1378
|
client.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, onRemoteAudioAvailable);
|
|
1357
1379
|
client.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, onRemoteAudioUnavailable);
|
|
1358
|
-
this.log("info", `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${
|
|
1380
|
+
this.log("info", `\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${roomId} sdkAppId=${sdkAppId} userId=${userId}`);
|
|
1359
1381
|
await client.enterRoom({
|
|
1360
1382
|
sdkAppId,
|
|
1361
|
-
userId
|
|
1362
|
-
userSig
|
|
1383
|
+
userId,
|
|
1384
|
+
userSig,
|
|
1363
1385
|
scene: TRTC.TYPE.SCENE_LIVE,
|
|
1364
1386
|
role: TRTC.TYPE.ROLE_AUDIENCE,
|
|
1365
1387
|
autoReceiveAudio: true,
|
|
1366
|
-
...isStringRoomId ? { strRoomId:
|
|
1388
|
+
...isStringRoomId ? { strRoomId: roomId } : { roomId: Number(roomId) }
|
|
1367
1389
|
});
|
|
1368
1390
|
session.TRTC = TRTC;
|
|
1369
1391
|
session.client = client;
|
|
1370
1392
|
session.status = "connected";
|
|
1371
1393
|
session.error = void 0;
|
|
1372
|
-
this.log("info", `\u8FDB\u623F\u6210\u529F source=${session.sourceId} room=${
|
|
1394
|
+
this.log("info", `\u8FDB\u623F\u6210\u529F source=${session.sourceId} room=${roomId}`);
|
|
1373
1395
|
this.emitSnapshot(session.sourceId, {
|
|
1374
1396
|
status: session.status
|
|
1375
1397
|
});
|
|
@@ -1517,19 +1539,10 @@ var TrtcSourceManager = class {
|
|
|
1517
1539
|
args,
|
|
1518
1540
|
data: extra.length > 0 ? { message, extra } : { message }
|
|
1519
1541
|
});
|
|
1520
|
-
if (level === "error") {
|
|
1521
|
-
console.error(...args);
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
if (level === "warn") {
|
|
1525
|
-
console.warn(...args);
|
|
1526
|
-
return;
|
|
1527
|
-
}
|
|
1528
|
-
console.log(...args);
|
|
1529
1542
|
}
|
|
1530
1543
|
};
|
|
1531
1544
|
function isRuntimeTrtcSource(source) {
|
|
1532
|
-
return source.status === "ready" && source.playback?.type === "trtc" &&
|
|
1545
|
+
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1533
1546
|
}
|
|
1534
1547
|
function isSameTrtcConfig(a, b) {
|
|
1535
1548
|
return a.app_id === b.app_id && a.user_id === b.user_id && a.user_sig === b.user_sig && a.room_id === b.room_id;
|
|
@@ -1541,6 +1554,10 @@ function shouldUseStringRoomId(roomId) {
|
|
|
1541
1554
|
const roomNumber = Number(roomId);
|
|
1542
1555
|
return !Number.isInteger(roomNumber) || roomNumber < 1 || roomNumber > 4294967294;
|
|
1543
1556
|
}
|
|
1557
|
+
function getTrtcString(trtc, key) {
|
|
1558
|
+
const value = trtc[key];
|
|
1559
|
+
return typeof value === "string" ? value : "";
|
|
1560
|
+
}
|
|
1544
1561
|
function buildRemoteVideoKey(userId, streamType) {
|
|
1545
1562
|
return `${userId}::${streamType}`;
|
|
1546
1563
|
}
|
|
@@ -1579,6 +1596,433 @@ function enforceContainMedia(container) {
|
|
|
1579
1596
|
});
|
|
1580
1597
|
}
|
|
1581
1598
|
|
|
1599
|
+
// src/runtime/livekit-types.ts
|
|
1600
|
+
function isLivekitSourcePlayback(playback) {
|
|
1601
|
+
if (!playback) return false;
|
|
1602
|
+
const candidate = playback;
|
|
1603
|
+
if (candidate.type !== "livekit" || typeof candidate.livekit !== "object" || candidate.livekit === null) {
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
const livekit = candidate.livekit;
|
|
1607
|
+
return typeof livekit.ws_url === "string" && typeof livekit.token === "string";
|
|
1608
|
+
}
|
|
1609
|
+
function isReadyLivekitRuntimeSource(source) {
|
|
1610
|
+
if (!source || source.status !== "ready") return false;
|
|
1611
|
+
return isLivekitSourcePlayback(source.playback);
|
|
1612
|
+
}
|
|
1613
|
+
function isSameLivekitConfig(a, b) {
|
|
1614
|
+
return a.ws_url === b.ws_url && a.token === b.token && a.room === b.room && a.identity === b.identity;
|
|
1615
|
+
}
|
|
1616
|
+
function describeLivekitRoom(livekit) {
|
|
1617
|
+
return livekit.room ?? livekit.identity ?? "unknown";
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
// src/runtime/managers/livekit-source-manager.ts
|
|
1621
|
+
var TAG2 = "[IVI-LIVEKIT]";
|
|
1622
|
+
var LivekitSourceManager = class {
|
|
1623
|
+
constructor(onLog) {
|
|
1624
|
+
this.onLog = onLog;
|
|
1625
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1626
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* 与 runtime 当前 sources 对齐:
|
|
1630
|
+
* - 对 ready + livekit 的 source 进行 upsert(必要时建立连接);
|
|
1631
|
+
* - 清理已不在 runtime 中的会话(释放资源)。
|
|
1632
|
+
*/
|
|
1633
|
+
syncRuntimeSources(sources) {
|
|
1634
|
+
const existingIds = new Set(sources.keys());
|
|
1635
|
+
sources.forEach((source, sourceId) => {
|
|
1636
|
+
if (!isReadyLivekitRuntimeSource(source)) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
this.upsertSource(sourceId, source.playback.livekit);
|
|
1640
|
+
});
|
|
1641
|
+
this.sessions.forEach((_session, sourceId) => {
|
|
1642
|
+
if (!existingIds.has(sourceId)) {
|
|
1643
|
+
this.removeSource(sourceId);
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* 按 sourceId 注册/更新 LiveKit 配置。
|
|
1649
|
+
* 若 ws_url / token / room / identity 任一发生变化,会先销毁旧会话再按新参数重建连接。
|
|
1650
|
+
*/
|
|
1651
|
+
upsertSource(sourceId, livekit) {
|
|
1652
|
+
const existing = this.sessions.get(sourceId);
|
|
1653
|
+
if (!existing) {
|
|
1654
|
+
this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${describeLivekitRoom(livekit)}`);
|
|
1655
|
+
const session2 = this.createSession(sourceId, livekit);
|
|
1656
|
+
this.sessions.set(sourceId, session2);
|
|
1657
|
+
void this.ensureConnected(session2);
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (isSameLivekitConfig(existing.livekit, livekit)) {
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${describeLivekitRoom(livekit)}`);
|
|
1664
|
+
void this.disposeSession(existing);
|
|
1665
|
+
const session = this.createSession(sourceId, livekit);
|
|
1666
|
+
this.sessions.set(sourceId, session);
|
|
1667
|
+
void this.ensureConnected(session);
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* 删除指定 source 的 LiveKit 会话并释放连接资源。
|
|
1671
|
+
*/
|
|
1672
|
+
removeSource(sourceId) {
|
|
1673
|
+
const session = this.sessions.get(sourceId);
|
|
1674
|
+
if (!session) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
this.log("info", `\u79FB\u9664\u4F1A\u8BDD source=${sourceId}`);
|
|
1678
|
+
this.sessions.delete(sourceId);
|
|
1679
|
+
void this.disposeSession(session);
|
|
1680
|
+
this.emitSnapshot(sourceId, { status: "idle" });
|
|
1681
|
+
this.listeners.delete(sourceId);
|
|
1682
|
+
}
|
|
1683
|
+
reset() {
|
|
1684
|
+
this.log("info", `\u91CD\u7F6E\u5168\u90E8\u4F1A\u8BDD count=${this.sessions.size}`);
|
|
1685
|
+
const sessions = Array.from(this.sessions.values());
|
|
1686
|
+
this.sessions.clear();
|
|
1687
|
+
sessions.forEach((session) => {
|
|
1688
|
+
void this.disposeSession(session);
|
|
1689
|
+
});
|
|
1690
|
+
this.listeners.clear();
|
|
1691
|
+
}
|
|
1692
|
+
subscribe(sourceId, listener) {
|
|
1693
|
+
const set = this.listeners.get(sourceId) ?? /* @__PURE__ */ new Set();
|
|
1694
|
+
set.add(listener);
|
|
1695
|
+
this.listeners.set(sourceId, set);
|
|
1696
|
+
listener(this.getSnapshot(sourceId));
|
|
1697
|
+
return () => {
|
|
1698
|
+
const target = this.listeners.get(sourceId);
|
|
1699
|
+
if (!target) {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
target.delete(listener);
|
|
1703
|
+
if (target.size === 0) {
|
|
1704
|
+
this.listeners.delete(sourceId);
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
getSnapshot(sourceId) {
|
|
1709
|
+
const session = this.sessions.get(sourceId);
|
|
1710
|
+
if (!session) {
|
|
1711
|
+
return { status: "idle" };
|
|
1712
|
+
}
|
|
1713
|
+
return { status: session.status, error: session.error };
|
|
1714
|
+
}
|
|
1715
|
+
hasRemoteVideoAvailable(sourceId) {
|
|
1716
|
+
const session = this.sessions.get(sourceId);
|
|
1717
|
+
return session?.hasEverReceivedRemoteVideo ?? false;
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* 等待指定 source 的 LiveKit 会话首次拿到远端视频 track。
|
|
1721
|
+
* - 若已收到过,立即返回 true;
|
|
1722
|
+
* - 若会话被销毁或超时,返回 false。
|
|
1723
|
+
*/
|
|
1724
|
+
waitForRemoteVideoAvailable(sourceId, timeoutMs = 3e4) {
|
|
1725
|
+
const session = this.sessions.get(sourceId);
|
|
1726
|
+
if (!session) return Promise.resolve(false);
|
|
1727
|
+
if (session.hasEverReceivedRemoteVideo) return Promise.resolve(true);
|
|
1728
|
+
return new Promise((resolve) => {
|
|
1729
|
+
let settled = false;
|
|
1730
|
+
const timer = setTimeout(() => {
|
|
1731
|
+
if (settled) return;
|
|
1732
|
+
settled = true;
|
|
1733
|
+
const idx = session.remoteVideoWaiters.indexOf(waiter);
|
|
1734
|
+
if (idx >= 0) session.remoteVideoWaiters.splice(idx, 1);
|
|
1735
|
+
resolve(false);
|
|
1736
|
+
}, timeoutMs);
|
|
1737
|
+
const waiter = (available) => {
|
|
1738
|
+
if (settled) return;
|
|
1739
|
+
settled = true;
|
|
1740
|
+
clearTimeout(timer);
|
|
1741
|
+
resolve(available);
|
|
1742
|
+
};
|
|
1743
|
+
session.remoteVideoWaiters.push(waiter);
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* 把一个渲染容器绑定到 source 会话:
|
|
1748
|
+
* - 确保已连接;
|
|
1749
|
+
* - 把已订阅的远端 video/audio 回放到该容器;
|
|
1750
|
+
* - 应用初始 muted 策略。
|
|
1751
|
+
*/
|
|
1752
|
+
async attachView(sourceId, viewId, container, muted) {
|
|
1753
|
+
const session = this.sessions.get(sourceId);
|
|
1754
|
+
if (!session) {
|
|
1755
|
+
throw new Error(`LiveKit source session not found: ${sourceId}`);
|
|
1756
|
+
}
|
|
1757
|
+
session.views.set(viewId, {
|
|
1758
|
+
container,
|
|
1759
|
+
muted,
|
|
1760
|
+
attachedElements: /* @__PURE__ */ new Map()
|
|
1761
|
+
});
|
|
1762
|
+
await this.ensureConnected(session);
|
|
1763
|
+
const binding = session.views.get(viewId);
|
|
1764
|
+
if (!binding) {
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
this.attachKnownTracksToView(session, binding);
|
|
1768
|
+
this.applyMutedToBinding(binding);
|
|
1769
|
+
}
|
|
1770
|
+
detachView(sourceId, viewId) {
|
|
1771
|
+
const session = this.sessions.get(sourceId);
|
|
1772
|
+
if (!session) {
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const binding = session.views.get(viewId);
|
|
1776
|
+
if (!binding) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
this.detachAllElementsFromView(session, binding);
|
|
1780
|
+
session.views.delete(viewId);
|
|
1781
|
+
}
|
|
1782
|
+
updateViewMuted(sourceId, viewId, muted) {
|
|
1783
|
+
const session = this.sessions.get(sourceId);
|
|
1784
|
+
if (!session) {
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const binding = session.views.get(viewId);
|
|
1788
|
+
if (!binding) {
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
binding.muted = muted;
|
|
1792
|
+
this.applyMutedToBinding(binding);
|
|
1793
|
+
}
|
|
1794
|
+
createSession(sourceId, livekit) {
|
|
1795
|
+
return {
|
|
1796
|
+
sourceId,
|
|
1797
|
+
livekit,
|
|
1798
|
+
livekitModule: null,
|
|
1799
|
+
room: null,
|
|
1800
|
+
connectPromise: null,
|
|
1801
|
+
views: /* @__PURE__ */ new Map(),
|
|
1802
|
+
remoteTracks: /* @__PURE__ */ new Map(),
|
|
1803
|
+
status: "idle",
|
|
1804
|
+
hasEverReceivedRemoteVideo: false,
|
|
1805
|
+
remoteVideoWaiters: [],
|
|
1806
|
+
detachRoomListeners: () => void 0
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* 懒连接:单飞模式下复用同一个 connectPromise;建连后注册远端事件并把已订阅 track 推给 view。
|
|
1811
|
+
*/
|
|
1812
|
+
async ensureConnected(session) {
|
|
1813
|
+
if (session.room && session.livekitModule) {
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
if (session.connectPromise) {
|
|
1817
|
+
await session.connectPromise;
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
session.status = "connecting";
|
|
1821
|
+
session.error = void 0;
|
|
1822
|
+
this.emitSnapshot(session.sourceId, { status: session.status });
|
|
1823
|
+
session.connectPromise = (async () => {
|
|
1824
|
+
const livekitModule = await import('livekit-client');
|
|
1825
|
+
const RoomCtor = livekitModule.Room;
|
|
1826
|
+
const RoomEvent = livekitModule.RoomEvent;
|
|
1827
|
+
const TrackKind = livekitModule.Track.Kind;
|
|
1828
|
+
const room = new RoomCtor({
|
|
1829
|
+
adaptiveStream: true,
|
|
1830
|
+
dynacast: true
|
|
1831
|
+
});
|
|
1832
|
+
const onTrackSubscribed = (track, _publication, participant) => {
|
|
1833
|
+
const kind = track.kind === TrackKind.Video ? "video" : track.kind === TrackKind.Audio ? "audio" : null;
|
|
1834
|
+
if (!kind) return;
|
|
1835
|
+
const trackKey = buildRemoteTrackKey(participant.identity, track.sid ?? track.kind);
|
|
1836
|
+
this.log(
|
|
1837
|
+
"info",
|
|
1838
|
+
`\u8BA2\u9605\u8FDC\u7AEF track source=${session.sourceId} participant=${participant.identity} kind=${kind} sid=${track.sid ?? "?"}`
|
|
1839
|
+
);
|
|
1840
|
+
session.remoteTracks.set(trackKey, {
|
|
1841
|
+
track,
|
|
1842
|
+
participantIdentity: participant.identity,
|
|
1843
|
+
kind
|
|
1844
|
+
});
|
|
1845
|
+
if (kind === "video" && !session.hasEverReceivedRemoteVideo) {
|
|
1846
|
+
session.hasEverReceivedRemoteVideo = true;
|
|
1847
|
+
for (const waiter of session.remoteVideoWaiters) {
|
|
1848
|
+
waiter(true);
|
|
1849
|
+
}
|
|
1850
|
+
session.remoteVideoWaiters.length = 0;
|
|
1851
|
+
}
|
|
1852
|
+
session.views.forEach((binding) => {
|
|
1853
|
+
this.attachTrackToView(track, kind, trackKey, binding);
|
|
1854
|
+
});
|
|
1855
|
+
};
|
|
1856
|
+
const onTrackUnsubscribed = (track, _publication, participant) => {
|
|
1857
|
+
const trackKey = buildRemoteTrackKey(participant.identity, track.sid ?? track.kind);
|
|
1858
|
+
this.log(
|
|
1859
|
+
"info",
|
|
1860
|
+
`\u8FDC\u7AEF track \u53D6\u6D88\u8BA2\u9605 source=${session.sourceId} participant=${participant.identity} sid=${track.sid ?? "?"}`
|
|
1861
|
+
);
|
|
1862
|
+
session.remoteTracks.delete(trackKey);
|
|
1863
|
+
session.views.forEach((binding) => {
|
|
1864
|
+
this.detachTrackFromBinding(track, trackKey, binding);
|
|
1865
|
+
});
|
|
1866
|
+
};
|
|
1867
|
+
const onDisconnected = () => {
|
|
1868
|
+
this.log("warn", `Room \u65AD\u5F00 source=${session.sourceId}`);
|
|
1869
|
+
};
|
|
1870
|
+
room.on(RoomEvent.TrackSubscribed, onTrackSubscribed);
|
|
1871
|
+
room.on(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed);
|
|
1872
|
+
room.on(RoomEvent.Disconnected, onDisconnected);
|
|
1873
|
+
session.detachRoomListeners = () => {
|
|
1874
|
+
room.off(RoomEvent.TrackSubscribed, onTrackSubscribed);
|
|
1875
|
+
room.off(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed);
|
|
1876
|
+
room.off(RoomEvent.Disconnected, onDisconnected);
|
|
1877
|
+
};
|
|
1878
|
+
this.log(
|
|
1879
|
+
"info",
|
|
1880
|
+
`\u6B63\u5728\u8FDB\u623F source=${session.sourceId} room=${describeLivekitRoom(session.livekit)} ws=${session.livekit.ws_url}`
|
|
1881
|
+
);
|
|
1882
|
+
await room.connect(session.livekit.ws_url, session.livekit.token);
|
|
1883
|
+
session.room = room;
|
|
1884
|
+
session.livekitModule = livekitModule;
|
|
1885
|
+
session.status = "connected";
|
|
1886
|
+
session.error = void 0;
|
|
1887
|
+
this.log("info", `\u8FDB\u623F\u6210\u529F source=${session.sourceId} room=${describeLivekitRoom(session.livekit)}`);
|
|
1888
|
+
this.emitSnapshot(session.sourceId, { status: session.status });
|
|
1889
|
+
})().catch((error) => {
|
|
1890
|
+
session.status = "error";
|
|
1891
|
+
session.error = error instanceof Error ? error.message : String(error);
|
|
1892
|
+
this.log("error", `\u8FDE\u63A5\u5931\u8D25 source=${session.sourceId} error=${session.error}`);
|
|
1893
|
+
this.emitSnapshot(session.sourceId, {
|
|
1894
|
+
status: session.status,
|
|
1895
|
+
error: session.error
|
|
1896
|
+
});
|
|
1897
|
+
throw error;
|
|
1898
|
+
}).finally(() => {
|
|
1899
|
+
session.connectPromise = null;
|
|
1900
|
+
});
|
|
1901
|
+
await session.connectPromise;
|
|
1902
|
+
}
|
|
1903
|
+
attachKnownTracksToView(session, binding) {
|
|
1904
|
+
session.remoteTracks.forEach((entry, trackKey) => {
|
|
1905
|
+
this.attachTrackToView(entry.track, entry.kind, trackKey, binding);
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
attachTrackToView(track, kind, trackKey, binding) {
|
|
1909
|
+
if (binding.attachedElements.has(trackKey)) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
let element;
|
|
1913
|
+
try {
|
|
1914
|
+
element = track.attach();
|
|
1915
|
+
} catch (error) {
|
|
1916
|
+
this.log("warn", `attach track \u5931\u8D25 trackKey=${trackKey}`, error);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
binding.attachedElements.set(trackKey, element);
|
|
1920
|
+
if (kind === "video") {
|
|
1921
|
+
enforceContainMedia2(element);
|
|
1922
|
+
} else {
|
|
1923
|
+
element.style.display = "none";
|
|
1924
|
+
}
|
|
1925
|
+
binding.container.appendChild(element);
|
|
1926
|
+
this.applyMutedToElement(element, binding.muted);
|
|
1927
|
+
}
|
|
1928
|
+
detachTrackFromBinding(track, trackKey, binding) {
|
|
1929
|
+
const element = binding.attachedElements.get(trackKey);
|
|
1930
|
+
if (!element) return;
|
|
1931
|
+
try {
|
|
1932
|
+
track.detach(element);
|
|
1933
|
+
} catch {
|
|
1934
|
+
}
|
|
1935
|
+
if (element.parentElement === binding.container) {
|
|
1936
|
+
binding.container.removeChild(element);
|
|
1937
|
+
}
|
|
1938
|
+
binding.attachedElements.delete(trackKey);
|
|
1939
|
+
}
|
|
1940
|
+
detachAllElementsFromView(session, binding) {
|
|
1941
|
+
binding.attachedElements.forEach((element, trackKey) => {
|
|
1942
|
+
const entry = session.remoteTracks.get(trackKey);
|
|
1943
|
+
if (entry) {
|
|
1944
|
+
try {
|
|
1945
|
+
entry.track.detach(element);
|
|
1946
|
+
} catch {
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
if (element.parentElement === binding.container) {
|
|
1950
|
+
binding.container.removeChild(element);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
binding.attachedElements.clear();
|
|
1954
|
+
}
|
|
1955
|
+
applyMutedToBinding(binding) {
|
|
1956
|
+
binding.attachedElements.forEach((element) => {
|
|
1957
|
+
this.applyMutedToElement(element, binding.muted);
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
applyMutedToElement(element, muted) {
|
|
1961
|
+
element.muted = muted;
|
|
1962
|
+
if (muted) {
|
|
1963
|
+
element.volume = 0;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
async disposeSession(session) {
|
|
1967
|
+
session.connectPromise = null;
|
|
1968
|
+
session.views.forEach((binding) => {
|
|
1969
|
+
this.detachAllElementsFromView(session, binding);
|
|
1970
|
+
});
|
|
1971
|
+
session.views.clear();
|
|
1972
|
+
session.remoteTracks.clear();
|
|
1973
|
+
for (const waiter of session.remoteVideoWaiters) {
|
|
1974
|
+
waiter(false);
|
|
1975
|
+
}
|
|
1976
|
+
session.remoteVideoWaiters.length = 0;
|
|
1977
|
+
session.hasEverReceivedRemoteVideo = false;
|
|
1978
|
+
session.status = "idle";
|
|
1979
|
+
session.error = void 0;
|
|
1980
|
+
const room = session.room;
|
|
1981
|
+
session.detachRoomListeners();
|
|
1982
|
+
session.detachRoomListeners = () => void 0;
|
|
1983
|
+
session.room = null;
|
|
1984
|
+
session.livekitModule = null;
|
|
1985
|
+
if (!room) {
|
|
1986
|
+
return;
|
|
1987
|
+
}
|
|
1988
|
+
this.log("info", `\u9500\u6BC1\u4F1A\u8BDD source=${session.sourceId}`);
|
|
1989
|
+
try {
|
|
1990
|
+
await room.disconnect();
|
|
1991
|
+
this.log("info", `\u79BB\u5F00\u623F\u95F4\u5B8C\u6210 source=${session.sourceId}`);
|
|
1992
|
+
} catch (err) {
|
|
1993
|
+
this.log("warn", `\u79BB\u5F00\u623F\u95F4\u5F02\u5E38 source=${session.sourceId}`, err);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
emitSnapshot(sourceId, snapshot) {
|
|
1997
|
+
const targetListeners = this.listeners.get(sourceId);
|
|
1998
|
+
if (!targetListeners) {
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
targetListeners.forEach((listener) => listener(snapshot));
|
|
2002
|
+
}
|
|
2003
|
+
log(level, message, ...extra) {
|
|
2004
|
+
const args = [TAG2, message, ...extra];
|
|
2005
|
+
this.onLog?.({
|
|
2006
|
+
level,
|
|
2007
|
+
tag: TAG2,
|
|
2008
|
+
message: `${TAG2} ${message}`,
|
|
2009
|
+
args,
|
|
2010
|
+
data: extra.length > 0 ? { message, extra } : { message }
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
function buildRemoteTrackKey(identity, trackId) {
|
|
2015
|
+
return `${identity}::${trackId}`;
|
|
2016
|
+
}
|
|
2017
|
+
function enforceContainMedia2(element) {
|
|
2018
|
+
element.style.setProperty("width", "100%", "important");
|
|
2019
|
+
element.style.setProperty("height", "100%", "important");
|
|
2020
|
+
element.style.setProperty("max-width", "100%", "important");
|
|
2021
|
+
element.style.setProperty("max-height", "100%", "important");
|
|
2022
|
+
element.style.setProperty("object-fit", "contain", "important");
|
|
2023
|
+
element.style.setProperty("display", "block", "important");
|
|
2024
|
+
}
|
|
2025
|
+
|
|
1582
2026
|
// src/runtime/runtime-coordinator.ts
|
|
1583
2027
|
var IviRuntimeCoordinator = class {
|
|
1584
2028
|
/**
|
|
@@ -1614,6 +2058,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1614
2058
|
...config
|
|
1615
2059
|
};
|
|
1616
2060
|
this.trtcSourceManager = new TrtcSourceManager(this.config.onLog);
|
|
2061
|
+
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
|
|
1617
2062
|
this.sessionHandler = new SessionEventHandler(this.sessionManager, {
|
|
1618
2063
|
onSessionCreated: (event) => this.onSessionCreated(event),
|
|
1619
2064
|
onSessionEnded: (event) => this.onSessionEnded(event)
|
|
@@ -1685,13 +2130,16 @@ var IviRuntimeCoordinator = class {
|
|
|
1685
2130
|
this.eventListeners.delete(listener);
|
|
1686
2131
|
};
|
|
1687
2132
|
}
|
|
2133
|
+
emitLog(entry) {
|
|
2134
|
+
this.config.onLog?.(entry);
|
|
2135
|
+
}
|
|
1688
2136
|
/**
|
|
1689
2137
|
* 编排一次"用户文本输入 -> 触发模型回复"链路。
|
|
1690
2138
|
*
|
|
1691
2139
|
* 流程:
|
|
1692
2140
|
* 1) 发送 conversation.item.create(可指定 itemId);
|
|
1693
2141
|
* 2) 等待 conversation.item.added + conversation.item.done;
|
|
1694
|
-
* 3) 发送 response.create
|
|
2142
|
+
* 3) 发送 response.create(默认不携带 input,使用 IVI 内部上下文;可选显式绑定 item_reference);
|
|
1695
2143
|
* 4) 等待 response.created 并结束。
|
|
1696
2144
|
*/
|
|
1697
2145
|
sendUserTextAndTriggerResponse(options) {
|
|
@@ -1713,12 +2161,13 @@ var IviRuntimeCoordinator = class {
|
|
|
1713
2161
|
itemId,
|
|
1714
2162
|
streamId: normalizedStreamId,
|
|
1715
2163
|
response: options.response,
|
|
2164
|
+
responseInput: options.responseInput ?? "context",
|
|
1716
2165
|
callbacks: options.callbacks,
|
|
1717
2166
|
responseRequested: false,
|
|
1718
2167
|
resolve,
|
|
1719
2168
|
reject
|
|
1720
2169
|
});
|
|
1721
|
-
this.client.
|
|
2170
|
+
this.client.sendUserText(normalizedText, itemId).catch((error) => {
|
|
1722
2171
|
this.pendingUserTextToResponseFlows.delete(itemId);
|
|
1723
2172
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
1724
2173
|
});
|
|
@@ -1749,6 +2198,10 @@ var IviRuntimeCoordinator = class {
|
|
|
1749
2198
|
void this.deferredTrtcTakeCompleteAndTake(resolvedTrackId, track.next_source_id, sourceId, trackId);
|
|
1750
2199
|
return;
|
|
1751
2200
|
}
|
|
2201
|
+
if (isVideoPlaybackSource(currentSource) && isLivekitPlaybackSource(nextSource)) {
|
|
2202
|
+
void this.deferredLivekitTakeCompleteAndTake(resolvedTrackId, track.next_source_id, sourceId, trackId);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
1752
2205
|
this.applyLocalTrackTake(resolvedTrackId);
|
|
1753
2206
|
this.client.sendSessionSourcePlaybackCompleted(sourceId, trackId);
|
|
1754
2207
|
this.sendSessionTrackTake(resolvedTrackId);
|
|
@@ -1759,6 +2212,9 @@ var IviRuntimeCoordinator = class {
|
|
|
1759
2212
|
getTrtcSourceManager() {
|
|
1760
2213
|
return this.trtcSourceManager;
|
|
1761
2214
|
}
|
|
2215
|
+
getLivekitSourceManager() {
|
|
2216
|
+
return this.livekitSourceManager;
|
|
2217
|
+
}
|
|
1762
2218
|
onConnectionChange(connected) {
|
|
1763
2219
|
if (connected || this.state.status === "idle") {
|
|
1764
2220
|
return;
|
|
@@ -1798,6 +2254,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1798
2254
|
this.sourceManager.reset();
|
|
1799
2255
|
this.streamManager.reset();
|
|
1800
2256
|
this.trtcSourceManager.reset();
|
|
2257
|
+
this.livekitSourceManager.reset();
|
|
1801
2258
|
this.conversationManager.reset();
|
|
1802
2259
|
this.reset();
|
|
1803
2260
|
const nextStatus = this.config.syncStageOnSessionCreated !== false ? "syncing" : "running";
|
|
@@ -1838,6 +2295,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1838
2295
|
const nextStage = this.stageManager.getStage();
|
|
1839
2296
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
1840
2297
|
this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
2298
|
+
this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
1841
2299
|
this.setState({
|
|
1842
2300
|
...this.state,
|
|
1843
2301
|
tracks: this.trackManager.getAll(),
|
|
@@ -1852,14 +2310,27 @@ var IviRuntimeCoordinator = class {
|
|
|
1852
2310
|
this.trackManager.applyTrackTook(trackId);
|
|
1853
2311
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
1854
2312
|
this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
2313
|
+
this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
1855
2314
|
this.setState({
|
|
1856
2315
|
...this.state,
|
|
1857
2316
|
tracks: this.trackManager.getAll(),
|
|
1858
2317
|
sources: this.sourceManager.getAll()
|
|
1859
2318
|
});
|
|
1860
2319
|
}
|
|
1861
|
-
async deferredTrtcTakeCompleteAndTake(trackId, nextSourceId, completedSourceId, completedTrackIdArg) {
|
|
1862
|
-
const remoteVideoAvailable = await this.trtcSourceManager.waitForRemoteVideoAvailable(nextSourceId);
|
|
2320
|
+
async deferredTrtcTakeCompleteAndTake(trackId, nextSourceId, completedSourceId, completedTrackIdArg) {
|
|
2321
|
+
const remoteVideoAvailable = await this.trtcSourceManager.waitForRemoteVideoAvailable(nextSourceId);
|
|
2322
|
+
if (!remoteVideoAvailable) return;
|
|
2323
|
+
if (this.state.status !== "running") return;
|
|
2324
|
+
const currentTrack = this.trackManager.getAll().get(trackId);
|
|
2325
|
+
if (!currentTrack || currentTrack.next_source_id !== nextSourceId) {
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
this.applyLocalTrackTake(trackId);
|
|
2329
|
+
this.client.sendSessionSourcePlaybackCompleted(completedSourceId, completedTrackIdArg);
|
|
2330
|
+
this.sendSessionTrackTake(trackId);
|
|
2331
|
+
}
|
|
2332
|
+
async deferredLivekitTakeCompleteAndTake(trackId, nextSourceId, completedSourceId, completedTrackIdArg) {
|
|
2333
|
+
const remoteVideoAvailable = await this.livekitSourceManager.waitForRemoteVideoAvailable(nextSourceId);
|
|
1863
2334
|
if (!remoteVideoAvailable) return;
|
|
1864
2335
|
if (this.state.status !== "running") return;
|
|
1865
2336
|
const currentTrack = this.trackManager.getAll().get(trackId);
|
|
@@ -1880,6 +2351,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1880
2351
|
onSourcesChanged(listRefreshed) {
|
|
1881
2352
|
this.sourceManager.syncWithTracks(this.trackManager.getAll());
|
|
1882
2353
|
this.trtcSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
2354
|
+
this.livekitSourceManager.syncRuntimeSources(this.sourceManager.getAll());
|
|
1883
2355
|
this.setState({
|
|
1884
2356
|
...this.state,
|
|
1885
2357
|
sources: this.sourceManager.getAll()
|
|
@@ -1922,6 +2394,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1922
2394
|
this.sourceManager.reset();
|
|
1923
2395
|
this.streamManager.reset();
|
|
1924
2396
|
this.trtcSourceManager.reset();
|
|
2397
|
+
this.livekitSourceManager.reset();
|
|
1925
2398
|
this.conversationManager.reset();
|
|
1926
2399
|
this.reset();
|
|
1927
2400
|
this.setState({
|
|
@@ -1960,7 +2433,7 @@ var IviRuntimeCoordinator = class {
|
|
|
1960
2433
|
return [];
|
|
1961
2434
|
}
|
|
1962
2435
|
const missing = /* @__PURE__ */ new Set();
|
|
1963
|
-
stage.composition.forEach((item) => {
|
|
2436
|
+
(stage.composition ?? []).forEach((item) => {
|
|
1964
2437
|
if (!hasTrack(item.track_id)) {
|
|
1965
2438
|
missing.add(item.track_id);
|
|
1966
2439
|
}
|
|
@@ -1995,6 +2468,9 @@ var IviRuntimeCoordinator = class {
|
|
|
1995
2468
|
}
|
|
1996
2469
|
progressUserTextToResponseFlows(event) {
|
|
1997
2470
|
if (event instanceof iviSdkTs.ReceiveConversationItemAddedEvent) {
|
|
2471
|
+
if (!event.item.id) {
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
1998
2474
|
const flow = this.pendingUserTextToResponseFlows.get(event.item.id);
|
|
1999
2475
|
if (!flow) {
|
|
2000
2476
|
return;
|
|
@@ -2005,6 +2481,9 @@ var IviRuntimeCoordinator = class {
|
|
|
2005
2481
|
return;
|
|
2006
2482
|
}
|
|
2007
2483
|
if (event instanceof iviSdkTs.ReceiveConversationItemDoneEvent) {
|
|
2484
|
+
if (!event.item.id) {
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2008
2487
|
const flow = this.pendingUserTextToResponseFlows.get(event.item.id);
|
|
2009
2488
|
if (!flow) {
|
|
2010
2489
|
return;
|
|
@@ -2040,7 +2519,11 @@ var IviRuntimeCoordinator = class {
|
|
|
2040
2519
|
addedEvent: flow.addedEvent,
|
|
2041
2520
|
doneEvent: flow.doneEvent
|
|
2042
2521
|
});
|
|
2043
|
-
|
|
2522
|
+
if (flow.responseInput === "item_reference") {
|
|
2523
|
+
this.client.sendResponseCreateByItemId(flow.itemId, flow.streamId, flow.response);
|
|
2524
|
+
} else {
|
|
2525
|
+
this.client.sendResponseCreate(flow.streamId, flow.response);
|
|
2526
|
+
}
|
|
2044
2527
|
flow.responseRequested = true;
|
|
2045
2528
|
}
|
|
2046
2529
|
buildUserTextItemId() {
|
|
@@ -2053,13 +2536,18 @@ var IviRuntimeCoordinator = class {
|
|
|
2053
2536
|
function isVideoPlaybackSource(source) {
|
|
2054
2537
|
if (!source || source.status !== "ready" || !source.playback) return false;
|
|
2055
2538
|
if (source.playback.type === "trtc") return false;
|
|
2539
|
+
if (isLivekitSourcePlayback(source.playback)) return false;
|
|
2056
2540
|
if (source.source.asset_type === "image") return false;
|
|
2057
|
-
return Boolean(source.playback.url);
|
|
2541
|
+
return source.playback.type === "url" && Boolean(source.playback.url);
|
|
2058
2542
|
}
|
|
2059
2543
|
function isTrtcPlaybackSource(source) {
|
|
2060
2544
|
if (!source || !source.playback) return false;
|
|
2061
2545
|
return source.playback.type === "trtc" && Boolean(source.playback.trtc);
|
|
2062
2546
|
}
|
|
2547
|
+
function isLivekitPlaybackSource(source) {
|
|
2548
|
+
if (!source || !source.playback) return false;
|
|
2549
|
+
return isLivekitSourcePlayback(source.playback);
|
|
2550
|
+
}
|
|
2063
2551
|
var IviFrontendSdk = class {
|
|
2064
2552
|
createClient(config) {
|
|
2065
2553
|
return new iviSdkTs.IviClient(config);
|
|
@@ -2146,14 +2634,14 @@ function IVIStageView(props) {
|
|
|
2146
2634
|
}
|
|
2147
2635
|
function buildSlotTrackMapFromState(state) {
|
|
2148
2636
|
const map = /* @__PURE__ */ new Map();
|
|
2149
|
-
state.stage?.composition.forEach((item) => {
|
|
2637
|
+
(state.stage?.composition ?? []).forEach((item) => {
|
|
2150
2638
|
map.set(item.slot, item.track_id);
|
|
2151
2639
|
});
|
|
2152
2640
|
return map;
|
|
2153
2641
|
}
|
|
2154
2642
|
function buildSlotBindingsFromState(state) {
|
|
2155
2643
|
const bindings = [];
|
|
2156
|
-
state.stage?.composition.forEach((item) => {
|
|
2644
|
+
(state.stage?.composition ?? []).forEach((item) => {
|
|
2157
2645
|
const track = state.tracks.get(item.track_id);
|
|
2158
2646
|
const sourceId = track?.active_source_id ?? null;
|
|
2159
2647
|
const source = sourceId ? state.sources.get(sourceId) : void 0;
|
|
@@ -2180,12 +2668,18 @@ function loadVolumePreferences() {
|
|
|
2180
2668
|
return {
|
|
2181
2669
|
trtc: clampVolume(parsed.trtc ?? DEFAULT_VOLUME),
|
|
2182
2670
|
video: clampVolume(parsed.video ?? DEFAULT_VOLUME),
|
|
2183
|
-
hls: clampVolume(parsed.hls ?? DEFAULT_VOLUME)
|
|
2671
|
+
hls: clampVolume(parsed.hls ?? DEFAULT_VOLUME),
|
|
2672
|
+
livekit: clampVolume(parsed.livekit ?? DEFAULT_VOLUME)
|
|
2184
2673
|
};
|
|
2185
2674
|
}
|
|
2186
2675
|
} catch {
|
|
2187
2676
|
}
|
|
2188
|
-
return {
|
|
2677
|
+
return {
|
|
2678
|
+
trtc: DEFAULT_VOLUME,
|
|
2679
|
+
video: DEFAULT_VOLUME,
|
|
2680
|
+
hls: DEFAULT_VOLUME,
|
|
2681
|
+
livekit: DEFAULT_VOLUME
|
|
2682
|
+
};
|
|
2189
2683
|
}
|
|
2190
2684
|
function saveVolumePreferences(prefs) {
|
|
2191
2685
|
try {
|
|
@@ -2443,101 +2937,133 @@ function useApplyVolumeToSlot(containerRef, volume, enabled, activeSourceId) {
|
|
|
2443
2937
|
return () => observer.disconnect();
|
|
2444
2938
|
}, [containerRef, volume, enabled, activeSourceId]);
|
|
2445
2939
|
}
|
|
2446
|
-
function
|
|
2447
|
-
const
|
|
2448
|
-
const
|
|
2449
|
-
const
|
|
2450
|
-
const
|
|
2940
|
+
function useIviSubtitles(runtime, options = {}) {
|
|
2941
|
+
const roles = options.roles ?? "user";
|
|
2942
|
+
const maxItems = normalizeMaxItems(options.maxItems);
|
|
2943
|
+
const roleKey = Array.isArray(roles) ? roles.join("\0") : roles;
|
|
2944
|
+
const roleSet = react.useMemo(
|
|
2945
|
+
() => new Set(roleKey.split("\0")),
|
|
2946
|
+
[roleKey]
|
|
2947
|
+
);
|
|
2948
|
+
const [subtitles, setSubtitles] = react.useState([]);
|
|
2949
|
+
const seenIdsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2451
2950
|
const initializedRef = react.useRef(false);
|
|
2452
2951
|
react.useEffect(() => {
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
if (!
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2952
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
2953
|
+
initializedRef.current = false;
|
|
2954
|
+
setSubtitles([]);
|
|
2955
|
+
if (!runtime) {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
const syncConversations = (conversations) => {
|
|
2959
|
+
const now = Date.now();
|
|
2960
|
+
const seenIds = seenIdsRef.current;
|
|
2961
|
+
if (!initializedRef.current) {
|
|
2962
|
+
initializedRef.current = true;
|
|
2963
|
+
for (const item of conversations) {
|
|
2964
|
+
if (item.lifecycle === "done" || !getDisplayText(item) || !roleSet.has(item.role)) {
|
|
2965
|
+
seenIds.add(item.id);
|
|
2966
|
+
}
|
|
2462
2967
|
}
|
|
2463
2968
|
}
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
newIds.push(item.id);
|
|
2472
|
-
}
|
|
2473
|
-
if (item.lifecycle === "done" && !dismissed.has(item.id)) {
|
|
2474
|
-
dismissed.add(item.id);
|
|
2475
|
-
const timer = setTimeout(() => {
|
|
2476
|
-
timers.delete(item.id);
|
|
2477
|
-
setVisibleIds((prev) => prev.filter((id) => id !== item.id));
|
|
2478
|
-
}, dismissAfterMs);
|
|
2479
|
-
timers.set(item.id, timer);
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
if (newIds.length > 0) {
|
|
2483
|
-
setVisibleIds((prev) => {
|
|
2484
|
-
const next = [...prev, ...newIds];
|
|
2485
|
-
while (next.length > maxVisible) {
|
|
2486
|
-
const removedId = next.shift();
|
|
2487
|
-
if (timers.has(removedId)) {
|
|
2488
|
-
clearTimeout(timers.get(removedId));
|
|
2489
|
-
timers.delete(removedId);
|
|
2969
|
+
setSubtitles((previous) => {
|
|
2970
|
+
const conversationMap = new Map(conversations.map((item) => [item.id, item]));
|
|
2971
|
+
const nextById = /* @__PURE__ */ new Map();
|
|
2972
|
+
for (const previousItem of previous) {
|
|
2973
|
+
const conversation = conversationMap.get(previousItem.id);
|
|
2974
|
+
if (!conversation || !roleSet.has(conversation.role) || !getDisplayText(conversation)) {
|
|
2975
|
+
continue;
|
|
2490
2976
|
}
|
|
2491
|
-
|
|
2977
|
+
nextById.set(
|
|
2978
|
+
previousItem.id,
|
|
2979
|
+
buildSubtitleItem(
|
|
2980
|
+
conversation,
|
|
2981
|
+
previousItem.timestamp,
|
|
2982
|
+
hasSubtitleChanged(previousItem, conversation) ? now : previousItem.updatedAt
|
|
2983
|
+
)
|
|
2984
|
+
);
|
|
2492
2985
|
}
|
|
2493
|
-
|
|
2986
|
+
for (const conversation of conversations) {
|
|
2987
|
+
if (!roleSet.has(conversation.role) || !getDisplayText(conversation)) {
|
|
2988
|
+
continue;
|
|
2989
|
+
}
|
|
2990
|
+
if (seenIds.has(conversation.id)) {
|
|
2991
|
+
continue;
|
|
2992
|
+
}
|
|
2993
|
+
seenIds.add(conversation.id);
|
|
2994
|
+
nextById.set(conversation.id, buildSubtitleItem(conversation, now, now));
|
|
2995
|
+
}
|
|
2996
|
+
const next = Array.from(nextById.values());
|
|
2997
|
+
if (maxItems === 0) {
|
|
2998
|
+
return [];
|
|
2999
|
+
}
|
|
3000
|
+
return next.length > maxItems ? next.slice(next.length - maxItems) : next;
|
|
2494
3001
|
});
|
|
2495
|
-
}
|
|
2496
|
-
}, [conversations, maxVisible, dismissAfterMs]);
|
|
2497
|
-
react.useEffect(() => {
|
|
2498
|
-
const timers = timersRef.current;
|
|
2499
|
-
return () => {
|
|
2500
|
-
timers.forEach((t) => clearTimeout(t));
|
|
2501
|
-
timers.clear();
|
|
2502
3002
|
};
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
3003
|
+
syncConversations(runtime.getState().conversations);
|
|
3004
|
+
return runtime.onEvent((event, state) => {
|
|
3005
|
+
if (event.type === "session.ended") {
|
|
3006
|
+
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
3007
|
+
initializedRef.current = false;
|
|
3008
|
+
setSubtitles([]);
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
if (!isSubtitleRelatedEvent(event.type)) {
|
|
3012
|
+
return;
|
|
3013
|
+
}
|
|
3014
|
+
syncConversations(state.conversations);
|
|
3015
|
+
});
|
|
3016
|
+
}, [runtime, roleSet, maxItems]);
|
|
3017
|
+
return subtitles;
|
|
3018
|
+
}
|
|
3019
|
+
function normalizeMaxItems(maxItems) {
|
|
3020
|
+
if (maxItems === void 0) {
|
|
3021
|
+
return 2;
|
|
3022
|
+
}
|
|
3023
|
+
if (!Number.isFinite(maxItems)) {
|
|
3024
|
+
return 2;
|
|
3025
|
+
}
|
|
3026
|
+
return Math.max(0, Math.floor(maxItems));
|
|
3027
|
+
}
|
|
3028
|
+
function getDisplayText(item) {
|
|
3029
|
+
return item.text || item.transcript;
|
|
3030
|
+
}
|
|
3031
|
+
function buildSubtitleItem(item, timestamp, updatedAt) {
|
|
3032
|
+
return {
|
|
3033
|
+
id: item.id,
|
|
3034
|
+
role: item.role,
|
|
3035
|
+
lifecycle: item.lifecycle,
|
|
3036
|
+
status: item.status,
|
|
3037
|
+
text: item.text,
|
|
3038
|
+
transcript: item.transcript,
|
|
3039
|
+
displayText: getDisplayText(item),
|
|
3040
|
+
content: item.content,
|
|
3041
|
+
item: item.item,
|
|
3042
|
+
timestamp,
|
|
3043
|
+
updatedAt
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
function hasSubtitleChanged(previous, next) {
|
|
3047
|
+
return previous.text !== next.text || previous.transcript !== next.transcript || previous.lifecycle !== next.lifecycle || previous.status !== next.status || previous.role !== next.role;
|
|
3048
|
+
}
|
|
3049
|
+
function isSubtitleRelatedEvent(type) {
|
|
3050
|
+
return type.startsWith("conversation.") || type.startsWith("response.");
|
|
2520
3051
|
}
|
|
2521
3052
|
var BREATHE_KEYFRAMES = `@keyframes ivi-subtitle-breathe{0%,100%{opacity:1}50%{opacity:.55}}`;
|
|
2522
3053
|
function IVISubtitleOverlay(props) {
|
|
2523
3054
|
const {
|
|
2524
|
-
|
|
3055
|
+
runtime,
|
|
2525
3056
|
roles = "user",
|
|
2526
|
-
|
|
2527
|
-
|
|
3057
|
+
maxItems,
|
|
3058
|
+
maxVisible,
|
|
2528
3059
|
subtitleStyle,
|
|
2529
3060
|
className,
|
|
2530
3061
|
style
|
|
2531
3062
|
} = props;
|
|
2532
|
-
const
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
);
|
|
2536
|
-
const filtered = react.useMemo(
|
|
2537
|
-
() => conversations.filter((c) => roleSet.has(c.role)),
|
|
2538
|
-
[conversations, roleSet]
|
|
2539
|
-
);
|
|
2540
|
-
const entries = useSubtitleEntries(filtered, maxVisible, dismissAfterMs);
|
|
3063
|
+
const entries = useIviSubtitles(runtime, {
|
|
3064
|
+
roles,
|
|
3065
|
+
maxItems: maxItems ?? maxVisible
|
|
3066
|
+
});
|
|
2541
3067
|
if (entries.length === 0) return null;
|
|
2542
3068
|
const fontFamily = subtitleStyle?.fontFamily ?? "system-ui, -apple-system, sans-serif";
|
|
2543
3069
|
const fontSize = subtitleStyle?.fontSize ?? 14;
|
|
@@ -2575,7 +3101,7 @@ function IVISubtitleOverlay(props) {
|
|
|
2575
3101
|
whiteSpace: "nowrap",
|
|
2576
3102
|
animation: entry.lifecycle === "added" ? "ivi-subtitle-breathe 1.5s ease-in-out infinite" : void 0
|
|
2577
3103
|
},
|
|
2578
|
-
children: entry.
|
|
3104
|
+
children: entry.displayText
|
|
2579
3105
|
},
|
|
2580
3106
|
entry.id
|
|
2581
3107
|
))
|
|
@@ -2678,9 +3204,224 @@ function IVITrtcPlayer(props) {
|
|
|
2678
3204
|
}
|
|
2679
3205
|
);
|
|
2680
3206
|
}
|
|
3207
|
+
var standaloneLivekitSourceManager = new LivekitSourceManager();
|
|
3208
|
+
function IVILivekitPlayer(props) {
|
|
3209
|
+
const {
|
|
3210
|
+
livekit,
|
|
3211
|
+
sourceId,
|
|
3212
|
+
runtime,
|
|
3213
|
+
className,
|
|
3214
|
+
style,
|
|
3215
|
+
loadingFallback = null,
|
|
3216
|
+
errorFallback = null,
|
|
3217
|
+
muted = false
|
|
3218
|
+
} = props;
|
|
3219
|
+
const containerRef = react.useRef(null);
|
|
3220
|
+
const viewIdRef = react.useRef(`livekit-view-${Math.random().toString(36).slice(2, 10)}`);
|
|
3221
|
+
const manager = runtime?.getLivekitSourceManager() ?? standaloneLivekitSourceManager;
|
|
3222
|
+
const resolvedSourceId = react.useMemo(
|
|
3223
|
+
() => sourceId ?? `adhoc:${livekit.ws_url}:${describeLivekitRoom(livekit)}:${livekit.token}`,
|
|
3224
|
+
[sourceId, livekit]
|
|
3225
|
+
);
|
|
3226
|
+
const shouldManageSourceLifecycle = !runtime || !sourceId;
|
|
3227
|
+
const [loading, setLoading] = react.useState(true);
|
|
3228
|
+
const [error, setError] = react.useState(null);
|
|
3229
|
+
const mutedRef = react.useRef(muted);
|
|
3230
|
+
mutedRef.current = muted;
|
|
3231
|
+
react.useEffect(() => {
|
|
3232
|
+
const container = containerRef.current;
|
|
3233
|
+
if (!container) {
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
let disposed = false;
|
|
3237
|
+
if (shouldManageSourceLifecycle) {
|
|
3238
|
+
manager.upsertSource(resolvedSourceId, livekit);
|
|
3239
|
+
}
|
|
3240
|
+
const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
|
|
3241
|
+
if (disposed) {
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
setLoading(snapshot.status === "idle" || snapshot.status === "connecting");
|
|
3245
|
+
setError(snapshot.status === "error" ? snapshot.error ?? "LiveKit \u62C9\u6D41\u5931\u8D25" : null);
|
|
3246
|
+
});
|
|
3247
|
+
void manager.attachView(resolvedSourceId, viewIdRef.current, container, mutedRef.current).catch((caughtError) => {
|
|
3248
|
+
if (disposed) {
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
setError(caughtError instanceof Error ? caughtError.message : "LiveKit \u62C9\u6D41\u5931\u8D25");
|
|
3252
|
+
setLoading(false);
|
|
3253
|
+
});
|
|
3254
|
+
return () => {
|
|
3255
|
+
disposed = true;
|
|
3256
|
+
unsubscribe();
|
|
3257
|
+
manager.detachView(resolvedSourceId, viewIdRef.current);
|
|
3258
|
+
if (shouldManageSourceLifecycle) {
|
|
3259
|
+
manager.removeSource(resolvedSourceId);
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
}, [
|
|
3263
|
+
manager,
|
|
3264
|
+
resolvedSourceId,
|
|
3265
|
+
shouldManageSourceLifecycle,
|
|
3266
|
+
livekit.ws_url,
|
|
3267
|
+
livekit.token,
|
|
3268
|
+
livekit.room,
|
|
3269
|
+
livekit.identity
|
|
3270
|
+
]);
|
|
3271
|
+
react.useEffect(() => {
|
|
3272
|
+
manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);
|
|
3273
|
+
}, [manager, muted, resolvedSourceId]);
|
|
3274
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3275
|
+
"div",
|
|
3276
|
+
{
|
|
3277
|
+
className,
|
|
3278
|
+
style: {
|
|
3279
|
+
width: "100%",
|
|
3280
|
+
height: "100%",
|
|
3281
|
+
minWidth: 0,
|
|
3282
|
+
minHeight: 0,
|
|
3283
|
+
backgroundColor: "#000",
|
|
3284
|
+
position: "relative",
|
|
3285
|
+
...style
|
|
3286
|
+
},
|
|
3287
|
+
children: [
|
|
3288
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } }),
|
|
3289
|
+
loading ? loadingFallback : null,
|
|
3290
|
+
error ? errorFallback ?? /* @__PURE__ */ jsxRuntime.jsx("div", { children: error }) : null
|
|
3291
|
+
]
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
var loadModulesPromise = null;
|
|
3296
|
+
async function loadXgplayerModules() {
|
|
3297
|
+
if (!loadModulesPromise) {
|
|
3298
|
+
loadModulesPromise = (async () => {
|
|
3299
|
+
try {
|
|
3300
|
+
await import('xgplayer/dist/index.min.css');
|
|
3301
|
+
} catch (err) {
|
|
3302
|
+
console.warn("[IVIFlvVideo] \u52A0\u8F7D xgplayer \u6837\u5F0F\u5931\u8D25\uFF0C\u64AD\u653E\u5668\u63A7\u4EF6\u53EF\u80FD\u663E\u793A\u5F02\u5E38", err);
|
|
3303
|
+
}
|
|
3304
|
+
const [playerMod, flvMod] = await Promise.all([import('xgplayer'), import('xgplayer-flv')]);
|
|
3305
|
+
const playerModule = playerMod;
|
|
3306
|
+
const flvModule = flvMod;
|
|
3307
|
+
const Player = playerModule.default ?? playerModule.Player;
|
|
3308
|
+
const FlvPlugin = flvModule.default ?? flvModule.FlvPlugin;
|
|
3309
|
+
if (!Player || !FlvPlugin) {
|
|
3310
|
+
throw new Error("xgplayer \u6216 xgplayer-flv \u6A21\u5757\u5BFC\u51FA\u65E0\u6548");
|
|
3311
|
+
}
|
|
3312
|
+
return { Player, FlvPlugin };
|
|
3313
|
+
})().catch((err) => {
|
|
3314
|
+
loadModulesPromise = null;
|
|
3315
|
+
throw err;
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
return loadModulesPromise;
|
|
3319
|
+
}
|
|
3320
|
+
function IVIFlvVideo(props) {
|
|
3321
|
+
const { url, videoProps, style, isLive = true, paused = false } = props;
|
|
3322
|
+
const containerRef = react.useRef(null);
|
|
3323
|
+
const playerRef = react.useRef(null);
|
|
3324
|
+
const pausedRef = react.useRef(paused);
|
|
3325
|
+
const videoPropsRef = react.useRef(videoProps);
|
|
3326
|
+
videoPropsRef.current = videoProps;
|
|
3327
|
+
const reactId = react.useId();
|
|
3328
|
+
const containerId = `ivi-flv-${reactId.replace(/:/g, "")}`;
|
|
3329
|
+
react.useEffect(() => {
|
|
3330
|
+
pausedRef.current = paused;
|
|
3331
|
+
const player = playerRef.current;
|
|
3332
|
+
if (!player) return;
|
|
3333
|
+
if (paused) {
|
|
3334
|
+
player.pause();
|
|
3335
|
+
} else {
|
|
3336
|
+
Promise.resolve(player.play()).catch((err) => {
|
|
3337
|
+
console.warn(
|
|
3338
|
+
"[IVIFlvVideo] paused\u2192active \u5207\u6362\u65F6 play() \u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD\uFF08\u591A\u534A\u53D7\u81EA\u52A8\u64AD\u653E\u7B56\u7565\u9650\u5236\uFF09",
|
|
3339
|
+
err
|
|
3340
|
+
);
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
}, [paused]);
|
|
3344
|
+
react.useEffect(() => {
|
|
3345
|
+
const container = containerRef.current;
|
|
3346
|
+
if (!container) return;
|
|
3347
|
+
let disposed = false;
|
|
3348
|
+
let endedHandler = null;
|
|
3349
|
+
let errorHandler = null;
|
|
3350
|
+
const setup = async () => {
|
|
3351
|
+
try {
|
|
3352
|
+
const { Player, FlvPlugin } = await loadXgplayerModules();
|
|
3353
|
+
if (disposed || !containerRef.current) return;
|
|
3354
|
+
playerRef.current?.destroy();
|
|
3355
|
+
playerRef.current = null;
|
|
3356
|
+
container.innerHTML = "";
|
|
3357
|
+
const shouldAutoplay = !pausedRef.current;
|
|
3358
|
+
const currentVideoProps = videoPropsRef.current;
|
|
3359
|
+
const muted = Boolean(currentVideoProps?.muted);
|
|
3360
|
+
const player = new Player({
|
|
3361
|
+
id: containerId,
|
|
3362
|
+
el: containerRef.current,
|
|
3363
|
+
url,
|
|
3364
|
+
plugins: [FlvPlugin],
|
|
3365
|
+
isLive,
|
|
3366
|
+
autoplay: shouldAutoplay,
|
|
3367
|
+
autoplayMuted: muted,
|
|
3368
|
+
muted,
|
|
3369
|
+
controls: currentVideoProps?.controls ?? true,
|
|
3370
|
+
width: "100%",
|
|
3371
|
+
height: "100%",
|
|
3372
|
+
fluid: true,
|
|
3373
|
+
videoFillMode: "contain",
|
|
3374
|
+
flv: {
|
|
3375
|
+
retryCount: Number.MAX_SAFE_INTEGER,
|
|
3376
|
+
retryDelay: 500,
|
|
3377
|
+
loadTimeout: 1e4
|
|
3378
|
+
}
|
|
3379
|
+
});
|
|
3380
|
+
playerRef.current = player;
|
|
3381
|
+
errorHandler = (...args) => {
|
|
3382
|
+
console.warn("[IVIFlvVideo] \u64AD\u653E\u5668 error \u4E8B\u4EF6", { url, args });
|
|
3383
|
+
};
|
|
3384
|
+
player.on("error", errorHandler);
|
|
3385
|
+
const onEnded = currentVideoProps?.onEnded;
|
|
3386
|
+
if (onEnded) {
|
|
3387
|
+
endedHandler = () => {
|
|
3388
|
+
videoPropsRef.current?.onEnded?.({});
|
|
3389
|
+
};
|
|
3390
|
+
player.on("ended", endedHandler);
|
|
3391
|
+
}
|
|
3392
|
+
if (!pausedRef.current) {
|
|
3393
|
+
Promise.resolve(player.play()).catch((err) => {
|
|
3394
|
+
console.warn("[IVIFlvVideo] \u521D\u59CB play() \u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD", err);
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
} catch (err) {
|
|
3398
|
+
console.warn("[IVIFlvVideo] \u521D\u59CB\u5316 xgplayer-flv \u5931\u8D25", err);
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
void setup();
|
|
3402
|
+
return () => {
|
|
3403
|
+
disposed = true;
|
|
3404
|
+
const player = playerRef.current;
|
|
3405
|
+
if (player) {
|
|
3406
|
+
if (endedHandler) player.off("ended", endedHandler);
|
|
3407
|
+
if (errorHandler) player.off("error", errorHandler);
|
|
3408
|
+
player.destroy();
|
|
3409
|
+
}
|
|
3410
|
+
playerRef.current = null;
|
|
3411
|
+
if (containerRef.current) {
|
|
3412
|
+
containerRef.current.innerHTML = "";
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
}, [url, isLive, containerId]);
|
|
3416
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { id: containerId, ref: containerRef, style: { width: "100%", height: "100%", ...style } });
|
|
3417
|
+
}
|
|
3418
|
+
function isFlvUrl(url) {
|
|
3419
|
+
return /\.flv(?:$|[?#])/i.test(url);
|
|
3420
|
+
}
|
|
2681
3421
|
var RETRY_DELAY_MS = 500;
|
|
2682
3422
|
var UNLIMITED_RETRIES = Number.MAX_SAFE_INTEGER;
|
|
2683
|
-
|
|
3423
|
+
var HLS_LOG_TAG = "[IVI-HLS]";
|
|
3424
|
+
function makeRetryConfig(label, kind, onLog) {
|
|
2684
3425
|
return {
|
|
2685
3426
|
maxNumRetry: UNLIMITED_RETRIES,
|
|
2686
3427
|
retryDelayMs: RETRY_DELAY_MS,
|
|
@@ -2688,25 +3429,27 @@ function makeRetryConfig(label, kind) {
|
|
|
2688
3429
|
backoff: "linear",
|
|
2689
3430
|
shouldRetry: (_config, retryCount, isTimeout) => {
|
|
2690
3431
|
const reason = kind === "timeout" || isTimeout ? "\u52A0\u8F7D\u8D85\u65F6" : "\u52A0\u8F7D\u5931\u8D25";
|
|
2691
|
-
|
|
2692
|
-
|
|
3432
|
+
emitHlsLog(
|
|
3433
|
+
onLog,
|
|
3434
|
+
"warn",
|
|
3435
|
+
`${label} ${reason}\uFF0C${RETRY_DELAY_MS}ms \u540E\u8FDB\u884C\u7B2C ${retryCount + 1} \u6B21\u91CD\u8BD5\uFF08\u65E0\u4E0A\u9650\uFF09`
|
|
2693
3436
|
);
|
|
2694
3437
|
return true;
|
|
2695
3438
|
}
|
|
2696
3439
|
};
|
|
2697
3440
|
}
|
|
2698
|
-
function makeLoadPolicy(label) {
|
|
3441
|
+
function makeLoadPolicy(label, onLog) {
|
|
2699
3442
|
return {
|
|
2700
3443
|
default: {
|
|
2701
3444
|
maxTimeToFirstByteMs: 1e4,
|
|
2702
3445
|
maxLoadTimeMs: 2e4,
|
|
2703
|
-
timeoutRetry: makeRetryConfig(label, "timeout"),
|
|
2704
|
-
errorRetry: makeRetryConfig(label, "error")
|
|
3446
|
+
timeoutRetry: makeRetryConfig(label, "timeout", onLog),
|
|
3447
|
+
errorRetry: makeRetryConfig(label, "error", onLog)
|
|
2705
3448
|
}
|
|
2706
3449
|
};
|
|
2707
3450
|
}
|
|
2708
3451
|
function IVIHlsVideo(props) {
|
|
2709
|
-
const { url, videoProps, style, aggressivePreload = false, paused = false } = props;
|
|
3452
|
+
const { url, videoProps, style, aggressivePreload = false, paused = false, onLog } = props;
|
|
2710
3453
|
const videoRef = react.useRef(null);
|
|
2711
3454
|
const pausedRef = react.useRef(paused);
|
|
2712
3455
|
react.useEffect(() => {
|
|
@@ -2717,13 +3460,15 @@ function IVIHlsVideo(props) {
|
|
|
2717
3460
|
video.pause();
|
|
2718
3461
|
} else {
|
|
2719
3462
|
video.play().catch((err) => {
|
|
2720
|
-
|
|
2721
|
-
|
|
3463
|
+
emitHlsLog(
|
|
3464
|
+
onLog,
|
|
3465
|
+
"warn",
|
|
3466
|
+
"paused\u2192active \u5207\u6362\u65F6 play() \u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD\uFF08\u591A\u534A\u53D7\u81EA\u52A8\u64AD\u653E\u7B56\u7565\u9650\u5236\uFF09",
|
|
2722
3467
|
err
|
|
2723
3468
|
);
|
|
2724
3469
|
});
|
|
2725
3470
|
}
|
|
2726
|
-
}, [paused]);
|
|
3471
|
+
}, [onLog, paused]);
|
|
2727
3472
|
react.useEffect(() => {
|
|
2728
3473
|
const video = videoRef.current;
|
|
2729
3474
|
if (!video) {
|
|
@@ -2741,7 +3486,7 @@ function IVIHlsVideo(props) {
|
|
|
2741
3486
|
const onVideoError = () => {
|
|
2742
3487
|
const el = videoRef.current;
|
|
2743
3488
|
const mediaErr = el?.error;
|
|
2744
|
-
|
|
3489
|
+
emitHlsLog(onLog, "warn", "<video> \u5143\u7D20\u62A5\u9519", {
|
|
2745
3490
|
code: mediaErr?.code,
|
|
2746
3491
|
message: mediaErr?.message,
|
|
2747
3492
|
currentSrc: el?.currentSrc,
|
|
@@ -2750,7 +3495,7 @@ function IVIHlsVideo(props) {
|
|
|
2750
3495
|
});
|
|
2751
3496
|
};
|
|
2752
3497
|
const onVideoStalled = () => {
|
|
2753
|
-
|
|
3498
|
+
emitHlsLog(onLog, "warn", "<video> stalled\uFF08\u7F13\u51B2\u505C\u6EDE\uFF09", {
|
|
2754
3499
|
currentTime: videoRef.current?.currentTime,
|
|
2755
3500
|
readyState: videoRef.current?.readyState
|
|
2756
3501
|
});
|
|
@@ -2763,17 +3508,17 @@ function IVIHlsVideo(props) {
|
|
|
2763
3508
|
const el = videoRef.current;
|
|
2764
3509
|
if (!el) return;
|
|
2765
3510
|
el.play().catch((err) => {
|
|
2766
|
-
|
|
2767
|
-
|
|
3511
|
+
emitHlsLog(
|
|
3512
|
+
onLog,
|
|
3513
|
+
"warn",
|
|
3514
|
+
"\u6062\u590D\u540E play() \u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD\uFF08\u53EF\u80FD\u53D7\u81EA\u52A8\u64AD\u653E\u7B56\u7565\u9650\u5236\uFF09",
|
|
2768
3515
|
err
|
|
2769
3516
|
);
|
|
2770
3517
|
});
|
|
2771
3518
|
};
|
|
2772
3519
|
const scheduleFullReload = (reason) => {
|
|
2773
3520
|
if (disposed || fullReloadTimer) return;
|
|
2774
|
-
|
|
2775
|
-
`[IVIHlsVideo] ${reason}\uFF0C${RETRY_DELAY_MS}ms \u540E\u91CD\u65B0\u62C9\u53D6\u6E90\uFF08\u65E0\u9650\u91CD\u8BD5\uFF09`
|
|
2776
|
-
);
|
|
3521
|
+
emitHlsLog(onLog, "warn", `${reason}\uFF0C${RETRY_DELAY_MS}ms \u540E\u91CD\u65B0\u62C9\u53D6\u6E90\uFF08\u65E0\u9650\u91CD\u8BD5\uFF09`);
|
|
2777
3522
|
fullReloadTimer = setTimeout(() => {
|
|
2778
3523
|
fullReloadTimer = null;
|
|
2779
3524
|
if (disposed || !hlsInstance || !videoRef.current) return;
|
|
@@ -2783,7 +3528,7 @@ function IVIHlsVideo(props) {
|
|
|
2783
3528
|
hlsInstance.startLoad();
|
|
2784
3529
|
resumePlayback();
|
|
2785
3530
|
} catch (err) {
|
|
2786
|
-
|
|
3531
|
+
emitHlsLog(onLog, "warn", "\u91CD\u65B0\u62C9\u53D6\u6E90\u629B\u51FA\u5F02\u5E38\uFF0C\u7EE7\u7EED\u91CD\u8BD5", err);
|
|
2787
3532
|
scheduleFullReload("\u91CD\u65B0\u62C9\u53D6\u6E90\u5F02\u5E38");
|
|
2788
3533
|
}
|
|
2789
3534
|
}, RETRY_DELAY_MS);
|
|
@@ -2799,8 +3544,10 @@ function IVIHlsVideo(props) {
|
|
|
2799
3544
|
const events = hlsModule.Events ?? Hls.Events ?? {};
|
|
2800
3545
|
const errorTypes = hlsModule.ErrorTypes ?? Hls.ErrorTypes ?? {};
|
|
2801
3546
|
if (!Hls.isSupported()) {
|
|
2802
|
-
|
|
2803
|
-
|
|
3547
|
+
emitHlsLog(
|
|
3548
|
+
onLog,
|
|
3549
|
+
"warn",
|
|
3550
|
+
"\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301 hls.js\uFF0C\u964D\u7EA7\u5230\u539F\u751F HLS\uFF08\u5931\u8D25\u5C06\u7531 <video> error \u4E8B\u4EF6\u4E0A\u62A5\uFF09",
|
|
2804
3551
|
{ url }
|
|
2805
3552
|
);
|
|
2806
3553
|
videoRef.current.src = url;
|
|
@@ -2822,9 +3569,9 @@ function IVIHlsVideo(props) {
|
|
|
2822
3569
|
capLevelToPlayerSize: true,
|
|
2823
3570
|
// 以下三项是核心:列表与分片的拉取全部使用 hls.js 内置重试,
|
|
2824
3571
|
// 无上限次数、间隔上限 0.5s、每次重试通过 shouldRetry 打印警告。
|
|
2825
|
-
manifestLoadPolicy: makeLoadPolicy("manifest \u4E3B\u5217\u8868"),
|
|
2826
|
-
playlistLoadPolicy: makeLoadPolicy("level \u5B50\u7801\u7387\u5217\u8868"),
|
|
2827
|
-
fragLoadPolicy: makeLoadPolicy("fragment \u5A92\u4F53\u5206\u7247")
|
|
3572
|
+
manifestLoadPolicy: makeLoadPolicy("manifest \u4E3B\u5217\u8868", onLog),
|
|
3573
|
+
playlistLoadPolicy: makeLoadPolicy("level \u5B50\u7801\u7387\u5217\u8868", onLog),
|
|
3574
|
+
fragLoadPolicy: makeLoadPolicy("fragment \u5A92\u4F53\u5206\u7247", onLog)
|
|
2828
3575
|
});
|
|
2829
3576
|
hlsInstance = instance;
|
|
2830
3577
|
const errorEvent = events.ERROR ?? "hlsError";
|
|
@@ -2832,52 +3579,38 @@ function IVIHlsVideo(props) {
|
|
|
2832
3579
|
const mediaErrorType = errorTypes.MEDIA_ERROR ?? "mediaError";
|
|
2833
3580
|
instance.on(errorEvent, (_eventName, data) => {
|
|
2834
3581
|
if (!data) {
|
|
2835
|
-
|
|
3582
|
+
emitHlsLog(onLog, "warn", "HLS ERROR \u4E8B\u4EF6 data \u4E3A\u7A7A");
|
|
2836
3583
|
return;
|
|
2837
3584
|
}
|
|
2838
3585
|
if (!data.fatal) {
|
|
2839
3586
|
if (data.type !== networkErrorType) {
|
|
2840
|
-
|
|
2841
|
-
"[IVIHlsVideo] HLS \u975E\u81F4\u547D\u9519\u8BEF\uFF08\u4E0D\u8D70\u5185\u7F6E\u91CD\u8BD5\uFF09",
|
|
2842
|
-
data.type,
|
|
2843
|
-
data.details
|
|
2844
|
-
);
|
|
3587
|
+
emitHlsLog(onLog, "warn", "HLS \u975E\u81F4\u547D\u9519\u8BEF\uFF08\u4E0D\u8D70\u5185\u7F6E\u91CD\u8BD5\uFF09", data.type, data.details);
|
|
2845
3588
|
}
|
|
2846
3589
|
return;
|
|
2847
3590
|
}
|
|
2848
3591
|
switch (data.type) {
|
|
2849
3592
|
case networkErrorType:
|
|
2850
|
-
|
|
2851
|
-
"[IVIHlsVideo] HLS \u81F4\u547D\u7F51\u7EDC\u9519\u8BEF\uFF0C\u8C03\u7528 startLoad() \u91CD\u542F\u62C9\u6D41",
|
|
2852
|
-
data.details
|
|
2853
|
-
);
|
|
3593
|
+
emitHlsLog(onLog, "warn", "HLS \u81F4\u547D\u7F51\u7EDC\u9519\u8BEF\uFF0C\u8C03\u7528 startLoad() \u91CD\u542F\u62C9\u6D41", data.details);
|
|
2854
3594
|
try {
|
|
2855
3595
|
hlsInstance?.startLoad();
|
|
2856
3596
|
resumePlayback();
|
|
2857
3597
|
} catch (err) {
|
|
2858
|
-
|
|
3598
|
+
emitHlsLog(onLog, "warn", "startLoad() \u5F02\u5E38\uFF0C\u8D70\u515C\u5E95\u91CD\u8F7D", err);
|
|
2859
3599
|
scheduleFullReload("startLoad \u5F02\u5E38");
|
|
2860
3600
|
}
|
|
2861
3601
|
break;
|
|
2862
3602
|
case mediaErrorType:
|
|
2863
|
-
|
|
2864
|
-
"[IVIHlsVideo] HLS \u81F4\u547D\u5A92\u4F53\u9519\u8BEF\uFF0C\u8C03\u7528 recoverMediaError() \u6062\u590D",
|
|
2865
|
-
data.details
|
|
2866
|
-
);
|
|
3603
|
+
emitHlsLog(onLog, "warn", "HLS \u81F4\u547D\u5A92\u4F53\u9519\u8BEF\uFF0C\u8C03\u7528 recoverMediaError() \u6062\u590D", data.details);
|
|
2867
3604
|
try {
|
|
2868
3605
|
hlsInstance?.recoverMediaError();
|
|
2869
3606
|
resumePlayback();
|
|
2870
3607
|
} catch (err) {
|
|
2871
|
-
|
|
3608
|
+
emitHlsLog(onLog, "warn", "recoverMediaError() \u5F02\u5E38\uFF0C\u8D70\u515C\u5E95\u91CD\u8F7D", err);
|
|
2872
3609
|
scheduleFullReload("recoverMediaError \u5F02\u5E38");
|
|
2873
3610
|
}
|
|
2874
3611
|
break;
|
|
2875
3612
|
default:
|
|
2876
|
-
|
|
2877
|
-
"[IVIHlsVideo] HLS \u5176\u4ED6\u81F4\u547D\u9519\u8BEF\uFF0C\u51C6\u5907\u91CD\u65B0\u62C9\u53D6\u6E90",
|
|
2878
|
-
data.type,
|
|
2879
|
-
data.details
|
|
2880
|
-
);
|
|
3613
|
+
emitHlsLog(onLog, "warn", "HLS \u5176\u4ED6\u81F4\u547D\u9519\u8BEF\uFF0C\u51C6\u5907\u91CD\u65B0\u62C9\u53D6\u6E90", data.type, data.details);
|
|
2881
3614
|
scheduleFullReload("\u5176\u4ED6\u81F4\u547D\u9519\u8BEF");
|
|
2882
3615
|
break;
|
|
2883
3616
|
}
|
|
@@ -2885,8 +3618,10 @@ function IVIHlsVideo(props) {
|
|
|
2885
3618
|
instance.loadSource(url);
|
|
2886
3619
|
instance.attachMedia(videoRef.current);
|
|
2887
3620
|
} catch (err) {
|
|
2888
|
-
|
|
2889
|
-
|
|
3621
|
+
emitHlsLog(
|
|
3622
|
+
onLog,
|
|
3623
|
+
"warn",
|
|
3624
|
+
"\u52A8\u6001\u52A0\u8F7D hls.js \u5931\u8D25\uFF0C\u964D\u7EA7\u5230\u539F\u751F HLS\uFF08\u5931\u8D25\u5C06\u7531 <video> error \u4E8B\u4EF6\u4E0A\u62A5\uFF09",
|
|
2890
3625
|
err
|
|
2891
3626
|
);
|
|
2892
3627
|
if (disposed || !videoRef.current) return;
|
|
@@ -2902,7 +3637,7 @@ function IVIHlsVideo(props) {
|
|
|
2902
3637
|
hlsInstance?.destroy();
|
|
2903
3638
|
hlsInstance = null;
|
|
2904
3639
|
};
|
|
2905
|
-
}, [url, aggressivePreload]);
|
|
3640
|
+
}, [url, aggressivePreload, onLog]);
|
|
2906
3641
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2907
3642
|
"video",
|
|
2908
3643
|
{
|
|
@@ -2918,159 +3653,16 @@ function IVIHlsVideo(props) {
|
|
|
2918
3653
|
function isM3u8Url(url) {
|
|
2919
3654
|
return /\.m3u8(?:$|[?#])/i.test(url);
|
|
2920
3655
|
}
|
|
2921
|
-
function
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: MAIN_LAYER_STYLE, children })
|
|
2931
|
-
] });
|
|
2932
|
-
}
|
|
2933
|
-
function BlurBackgroundLayer({
|
|
2934
|
-
source,
|
|
2935
|
-
mode
|
|
2936
|
-
}) {
|
|
2937
|
-
const { playback } = source;
|
|
2938
|
-
if (source.source.asset_type === "image" && playback.url) {
|
|
2939
|
-
return /* @__PURE__ */ jsxRuntime.jsx("img", { src: playback.url, alt: "", style: BLUR_MEDIA_STYLE });
|
|
2940
|
-
}
|
|
2941
|
-
if (playback.type === "trtc") {
|
|
2942
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SlotVideoBlurCanvas, { staticOnly: mode === "static" });
|
|
2943
|
-
}
|
|
2944
|
-
const url = playback.url;
|
|
2945
|
-
if (!url) return null;
|
|
2946
|
-
if (mode === "static") {
|
|
2947
|
-
return isM3u8Url(url) ? /* @__PURE__ */ jsxRuntime.jsx(HlsStaticBlurFrame, { url }) : /* @__PURE__ */ jsxRuntime.jsx(StaticBlurFrame, { url });
|
|
2948
|
-
}
|
|
2949
|
-
if (isM3u8Url(url)) {
|
|
2950
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2951
|
-
IVIHlsVideo,
|
|
2952
|
-
{
|
|
2953
|
-
url,
|
|
2954
|
-
videoProps: { muted: true, autoPlay: true, playsInline: true },
|
|
2955
|
-
style: BLUR_MEDIA_STYLE,
|
|
2956
|
-
paused: false
|
|
2957
|
-
}
|
|
2958
|
-
);
|
|
2959
|
-
}
|
|
2960
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2961
|
-
"video",
|
|
2962
|
-
{
|
|
2963
|
-
src: url,
|
|
2964
|
-
muted: true,
|
|
2965
|
-
autoPlay: true,
|
|
2966
|
-
playsInline: true,
|
|
2967
|
-
style: BLUR_MEDIA_STYLE
|
|
2968
|
-
}
|
|
2969
|
-
);
|
|
2970
|
-
}
|
|
2971
|
-
function SlotVideoBlurCanvas({ staticOnly }) {
|
|
2972
|
-
const canvasRef = react.useRef(null);
|
|
2973
|
-
react.useEffect(() => {
|
|
2974
|
-
const canvas = canvasRef.current;
|
|
2975
|
-
if (!canvas) return;
|
|
2976
|
-
const container = canvas.closest("[data-ivi-source-id]");
|
|
2977
|
-
if (!container) return;
|
|
2978
|
-
let animId;
|
|
2979
|
-
let lastDrawTime = 0;
|
|
2980
|
-
let captured = false;
|
|
2981
|
-
const intervalMs = 1e3 / 5;
|
|
2982
|
-
const draw = (time) => {
|
|
2983
|
-
if (staticOnly && captured) return;
|
|
2984
|
-
animId = requestAnimationFrame(draw);
|
|
2985
|
-
if (time - lastDrawTime < intervalMs) return;
|
|
2986
|
-
lastDrawTime = time;
|
|
2987
|
-
const video = container.querySelector("video");
|
|
2988
|
-
if (!video || video.readyState < 2) return;
|
|
2989
|
-
const ctx = canvas.getContext("2d");
|
|
2990
|
-
if (!ctx) return;
|
|
2991
|
-
if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
|
|
2992
|
-
canvas.width = video.videoWidth || 640;
|
|
2993
|
-
canvas.height = video.videoHeight || 360;
|
|
2994
|
-
}
|
|
2995
|
-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
2996
|
-
captured = true;
|
|
2997
|
-
};
|
|
2998
|
-
animId = requestAnimationFrame(draw);
|
|
2999
|
-
return () => cancelAnimationFrame(animId);
|
|
3000
|
-
}, [staticOnly]);
|
|
3001
|
-
return /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, style: BLUR_MEDIA_STYLE });
|
|
3002
|
-
}
|
|
3003
|
-
function StaticBlurFrame({ url }) {
|
|
3004
|
-
const canvasRef = react.useRef(null);
|
|
3005
|
-
react.useEffect(() => {
|
|
3006
|
-
const canvas = canvasRef.current;
|
|
3007
|
-
if (!canvas) return;
|
|
3008
|
-
const video = document.createElement("video");
|
|
3009
|
-
video.muted = true;
|
|
3010
|
-
video.playsInline = true;
|
|
3011
|
-
video.preload = "auto";
|
|
3012
|
-
video.crossOrigin = "anonymous";
|
|
3013
|
-
video.src = url;
|
|
3014
|
-
const onReady = () => {
|
|
3015
|
-
const ctx = canvas.getContext("2d");
|
|
3016
|
-
if (!ctx) return;
|
|
3017
|
-
canvas.width = video.videoWidth || 640;
|
|
3018
|
-
canvas.height = video.videoHeight || 360;
|
|
3019
|
-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
3020
|
-
cleanup();
|
|
3021
|
-
};
|
|
3022
|
-
const cleanup = () => {
|
|
3023
|
-
video.removeEventListener("loadeddata", onReady);
|
|
3024
|
-
video.pause();
|
|
3025
|
-
video.removeAttribute("src");
|
|
3026
|
-
video.load();
|
|
3027
|
-
};
|
|
3028
|
-
video.addEventListener("loadeddata", onReady);
|
|
3029
|
-
video.load();
|
|
3030
|
-
return cleanup;
|
|
3031
|
-
}, [url]);
|
|
3032
|
-
return /* @__PURE__ */ jsxRuntime.jsx("canvas", { ref: canvasRef, style: BLUR_MEDIA_STYLE });
|
|
3033
|
-
}
|
|
3034
|
-
function HlsStaticBlurFrame({ url }) {
|
|
3035
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3036
|
-
IVIHlsVideo,
|
|
3037
|
-
{
|
|
3038
|
-
url,
|
|
3039
|
-
videoProps: { muted: true, autoPlay: true, playsInline: true },
|
|
3040
|
-
style: BLUR_MEDIA_STYLE,
|
|
3041
|
-
paused: true
|
|
3042
|
-
}
|
|
3043
|
-
);
|
|
3656
|
+
function emitHlsLog(onLog, level, message, ...extra) {
|
|
3657
|
+
const args = [HLS_LOG_TAG, message, ...extra];
|
|
3658
|
+
onLog?.({
|
|
3659
|
+
level,
|
|
3660
|
+
tag: HLS_LOG_TAG,
|
|
3661
|
+
message: `${HLS_LOG_TAG} ${message}`,
|
|
3662
|
+
args,
|
|
3663
|
+
data: extra.length > 0 ? { message, extra } : { message }
|
|
3664
|
+
});
|
|
3044
3665
|
}
|
|
3045
|
-
var CONTAINER_STYLE = {
|
|
3046
|
-
width: "100%",
|
|
3047
|
-
height: "100%",
|
|
3048
|
-
position: "relative",
|
|
3049
|
-
overflow: "hidden"
|
|
3050
|
-
};
|
|
3051
|
-
var BG_LAYER_STYLE = {
|
|
3052
|
-
position: "absolute",
|
|
3053
|
-
top: 0,
|
|
3054
|
-
left: 0,
|
|
3055
|
-
width: "100%",
|
|
3056
|
-
height: "100%",
|
|
3057
|
-
zIndex: 0,
|
|
3058
|
-
overflow: "hidden"
|
|
3059
|
-
};
|
|
3060
|
-
var MAIN_LAYER_STYLE = {
|
|
3061
|
-
position: "relative",
|
|
3062
|
-
zIndex: 1,
|
|
3063
|
-
width: "100%",
|
|
3064
|
-
height: "100%"
|
|
3065
|
-
};
|
|
3066
|
-
var BLUR_MEDIA_STYLE = {
|
|
3067
|
-
width: "100%",
|
|
3068
|
-
height: "100%",
|
|
3069
|
-
objectFit: "cover",
|
|
3070
|
-
filter: "blur(20px)",
|
|
3071
|
-
transform: "scale(1.15)",
|
|
3072
|
-
display: "block"
|
|
3073
|
-
};
|
|
3074
3666
|
function toReadyRuntimeSource(source) {
|
|
3075
3667
|
if (!source || source.status !== "ready" || !source.playback) {
|
|
3076
3668
|
return null;
|
|
@@ -3084,9 +3676,10 @@ function supportsSubtitleOverlay(source) {
|
|
|
3084
3676
|
}
|
|
3085
3677
|
function detectMediaVolumeType(source) {
|
|
3086
3678
|
if (!source) return null;
|
|
3087
|
-
if (source.playback
|
|
3679
|
+
if (getPlaybackType(source.playback) === "trtc") return "trtc";
|
|
3680
|
+
if (isLivekitSourcePlayback(source.playback)) return "livekit";
|
|
3088
3681
|
if (source.source.asset_type === "image") return null;
|
|
3089
|
-
const url = source.playback
|
|
3682
|
+
const url = getPlaybackUrl(source.playback);
|
|
3090
3683
|
if (!url) return null;
|
|
3091
3684
|
return isM3u8Url(url) ? "hls" : "video";
|
|
3092
3685
|
}
|
|
@@ -3099,13 +3692,15 @@ function TrackSlotMediaContent(props) {
|
|
|
3099
3692
|
isActive,
|
|
3100
3693
|
runtime,
|
|
3101
3694
|
renderTrtc,
|
|
3695
|
+
renderLivekit,
|
|
3102
3696
|
renderMedia,
|
|
3103
3697
|
imageProps,
|
|
3104
3698
|
videoProps,
|
|
3105
3699
|
trtcPlayerProps,
|
|
3700
|
+
livekitPlayerProps,
|
|
3106
3701
|
adaptToSourceSize,
|
|
3107
3702
|
fitStrategy,
|
|
3108
|
-
|
|
3703
|
+
onLog
|
|
3109
3704
|
} = props;
|
|
3110
3705
|
const renderContext = {
|
|
3111
3706
|
slot: slot ?? "",
|
|
@@ -3113,17 +3708,18 @@ function TrackSlotMediaContent(props) {
|
|
|
3113
3708
|
source,
|
|
3114
3709
|
isPreloading: !isActive
|
|
3115
3710
|
};
|
|
3116
|
-
const mediaStyle = buildAdaptiveMediaStyle(
|
|
3711
|
+
const mediaStyle = buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy);
|
|
3117
3712
|
const shouldMute = !isActive;
|
|
3118
3713
|
if (renderMedia) return renderMedia(renderContext);
|
|
3119
|
-
if (source.playback
|
|
3120
|
-
|
|
3714
|
+
if (getPlaybackType(source.playback) === "trtc") {
|
|
3715
|
+
const trtc = getTrtcPlayback(source.playback);
|
|
3716
|
+
if (!trtc) return null;
|
|
3121
3717
|
if (renderTrtc) return renderTrtc(renderContext);
|
|
3122
3718
|
const trtcMuted = shouldMute || Boolean(trtcPlayerProps?.muted);
|
|
3123
3719
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3124
3720
|
IVITrtcPlayer,
|
|
3125
3721
|
{
|
|
3126
|
-
trtc
|
|
3722
|
+
trtc,
|
|
3127
3723
|
sourceId: source.source.source_id,
|
|
3128
3724
|
runtime,
|
|
3129
3725
|
...trtcPlayerProps,
|
|
@@ -3134,18 +3730,37 @@ function TrackSlotMediaContent(props) {
|
|
|
3134
3730
|
}
|
|
3135
3731
|
);
|
|
3136
3732
|
}
|
|
3733
|
+
if (isLivekitSourcePlayback(source.playback)) {
|
|
3734
|
+
if (renderLivekit) return renderLivekit(renderContext);
|
|
3735
|
+
const livekitMuted = shouldMute || Boolean(livekitPlayerProps?.muted);
|
|
3736
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3737
|
+
IVILivekitPlayer,
|
|
3738
|
+
{
|
|
3739
|
+
livekit: source.playback.livekit,
|
|
3740
|
+
sourceId: source.source.source_id,
|
|
3741
|
+
runtime,
|
|
3742
|
+
...livekitPlayerProps,
|
|
3743
|
+
muted: livekitMuted,
|
|
3744
|
+
loadingFallback: isActive ? livekitPlayerProps?.loadingFallback : null,
|
|
3745
|
+
errorFallback: isActive ? livekitPlayerProps?.errorFallback : null,
|
|
3746
|
+
style: { ...mediaStyle, ...livekitPlayerProps?.style ?? {} }
|
|
3747
|
+
}
|
|
3748
|
+
);
|
|
3749
|
+
}
|
|
3137
3750
|
if (source.source.asset_type === "image") {
|
|
3751
|
+
const imageUrl = getPlaybackUrl(source.playback);
|
|
3752
|
+
if (!imageUrl) return null;
|
|
3138
3753
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3139
3754
|
"img",
|
|
3140
3755
|
{
|
|
3141
|
-
src:
|
|
3756
|
+
src: imageUrl,
|
|
3142
3757
|
alt: "",
|
|
3143
3758
|
...imageProps,
|
|
3144
3759
|
style: { ...mediaStyle, ...imageProps?.style ?? {} }
|
|
3145
3760
|
}
|
|
3146
3761
|
);
|
|
3147
3762
|
}
|
|
3148
|
-
const playbackUrl = source.playback
|
|
3763
|
+
const playbackUrl = getPlaybackUrl(source.playback);
|
|
3149
3764
|
if (!playbackUrl) return null;
|
|
3150
3765
|
const videoStyle = { ...mediaStyle, ...videoProps?.style ?? {} };
|
|
3151
3766
|
const mergedVideoProps = {
|
|
@@ -3159,15 +3774,32 @@ function TrackSlotMediaContent(props) {
|
|
|
3159
3774
|
) : void 0
|
|
3160
3775
|
};
|
|
3161
3776
|
const shouldPause = !isActive;
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3777
|
+
if (isM3u8Url(playbackUrl)) {
|
|
3778
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3779
|
+
IVIHlsVideo,
|
|
3780
|
+
{
|
|
3781
|
+
url: playbackUrl,
|
|
3782
|
+
videoProps: mergedVideoProps,
|
|
3783
|
+
style: videoStyle,
|
|
3784
|
+
paused: shouldPause
|
|
3785
|
+
}
|
|
3786
|
+
);
|
|
3787
|
+
}
|
|
3788
|
+
if (isFlvUrl(playbackUrl)) {
|
|
3789
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3790
|
+
IVIFlvVideo,
|
|
3791
|
+
{
|
|
3792
|
+
url: playbackUrl,
|
|
3793
|
+
videoProps: {
|
|
3794
|
+
...mergedVideoProps,
|
|
3795
|
+
controls: isActive ? videoProps?.controls ?? true : false
|
|
3796
|
+
},
|
|
3797
|
+
style: videoStyle,
|
|
3798
|
+
paused: shouldPause
|
|
3799
|
+
}
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3802
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3171
3803
|
SlotVideo,
|
|
3172
3804
|
{
|
|
3173
3805
|
src: playbackUrl,
|
|
@@ -3182,7 +3814,7 @@ function TrackSlotMediaContent(props) {
|
|
|
3182
3814
|
}
|
|
3183
3815
|
);
|
|
3184
3816
|
}
|
|
3185
|
-
function buildAdaptiveMediaStyle(
|
|
3817
|
+
function buildAdaptiveMediaStyle(adaptToSourceSize, fitStrategy) {
|
|
3186
3818
|
const objectFitStyle = fitStrategy === "auto" ? {} : {
|
|
3187
3819
|
objectFit: fitStrategy ?? "contain"
|
|
3188
3820
|
};
|
|
@@ -3193,20 +3825,23 @@ function buildAdaptiveMediaStyle(source, adaptToSourceSize, fitStrategy, backgro
|
|
|
3193
3825
|
width: "100%",
|
|
3194
3826
|
height: "100%",
|
|
3195
3827
|
display: "block",
|
|
3196
|
-
...objectFitStyle
|
|
3197
|
-
backgroundColor: resolveBackgroundColor(background)
|
|
3828
|
+
...objectFitStyle
|
|
3198
3829
|
};
|
|
3199
3830
|
}
|
|
3200
|
-
function
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3831
|
+
function getPlaybackType(playback) {
|
|
3832
|
+
const type = playback.type;
|
|
3833
|
+
return typeof type === "string" ? type : void 0;
|
|
3834
|
+
}
|
|
3835
|
+
function getPlaybackUrl(playback) {
|
|
3836
|
+
const url = playback.url;
|
|
3837
|
+
return typeof url === "string" ? url : void 0;
|
|
3838
|
+
}
|
|
3839
|
+
function getTrtcPlayback(playback) {
|
|
3840
|
+
const trtc = playback.trtc;
|
|
3841
|
+
if (typeof trtc !== "object" || trtc === null) {
|
|
3842
|
+
return null;
|
|
3208
3843
|
}
|
|
3209
|
-
return
|
|
3844
|
+
return trtc;
|
|
3210
3845
|
}
|
|
3211
3846
|
function createAutoTakeOnEndedHandler(runtime, sourceId, trackId, userOnEnded) {
|
|
3212
3847
|
return (event) => {
|
|
@@ -3268,19 +3903,26 @@ function IVITrackSlot(props) {
|
|
|
3268
3903
|
style,
|
|
3269
3904
|
emptyFallback = null,
|
|
3270
3905
|
renderTrtc,
|
|
3906
|
+
renderLivekit,
|
|
3271
3907
|
renderMedia,
|
|
3272
3908
|
videoProps,
|
|
3273
3909
|
imageProps,
|
|
3274
3910
|
adaptToSourceSize = true,
|
|
3275
3911
|
fitStrategy = "contain",
|
|
3276
3912
|
trtcPlayerProps,
|
|
3913
|
+
livekitPlayerProps,
|
|
3277
3914
|
showVolumeControl,
|
|
3278
3915
|
volumeControlProps,
|
|
3279
3916
|
showSubtitle,
|
|
3280
3917
|
subtitleProps,
|
|
3281
|
-
|
|
3918
|
+
onLog
|
|
3282
3919
|
} = props;
|
|
3283
3920
|
const context = useIviStageView();
|
|
3921
|
+
const fallbackLogCallback = react.useCallback(
|
|
3922
|
+
(entry) => context.runtime?.emitLog(entry),
|
|
3923
|
+
[context.runtime]
|
|
3924
|
+
);
|
|
3925
|
+
const mediaLogCallback = onLog ?? fallbackLogCallback;
|
|
3284
3926
|
const containerRef = react.useRef(null);
|
|
3285
3927
|
const resolvedTrackId = trackId ?? (slot ? context.slotTrackMap.get(slot) : void 0);
|
|
3286
3928
|
const track = resolvedTrackId ? context.state.tracks.get(resolvedTrackId) : void 0;
|
|
@@ -3302,7 +3944,6 @@ function IVITrackSlot(props) {
|
|
|
3302
3944
|
minHeight: 0,
|
|
3303
3945
|
...style ?? {}
|
|
3304
3946
|
};
|
|
3305
|
-
const blurMode = resolveBlurMode(background);
|
|
3306
3947
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3307
3948
|
"div",
|
|
3308
3949
|
{
|
|
@@ -3315,33 +3956,33 @@ function IVITrackSlot(props) {
|
|
|
3315
3956
|
!activeSource && emptyFallback,
|
|
3316
3957
|
preloadEntries.map((entry) => {
|
|
3317
3958
|
const isActive = entry.isActive;
|
|
3318
|
-
const showBlur = blurMode !== false && isActive;
|
|
3319
|
-
const content = /* @__PURE__ */ jsxRuntime.jsx(
|
|
3320
|
-
TrackSlotMediaContent,
|
|
3321
|
-
{
|
|
3322
|
-
slot,
|
|
3323
|
-
track,
|
|
3324
|
-
source: entry.source,
|
|
3325
|
-
slotSourceId: entry.sourceId,
|
|
3326
|
-
isActive,
|
|
3327
|
-
runtime: context.runtime,
|
|
3328
|
-
renderTrtc,
|
|
3329
|
-
renderMedia,
|
|
3330
|
-
imageProps,
|
|
3331
|
-
videoProps,
|
|
3332
|
-
trtcPlayerProps,
|
|
3333
|
-
adaptToSourceSize,
|
|
3334
|
-
fitStrategy,
|
|
3335
|
-
background
|
|
3336
|
-
}
|
|
3337
|
-
);
|
|
3338
3959
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3339
3960
|
"div",
|
|
3340
3961
|
{
|
|
3341
3962
|
style: isActive ? ACTIVE_SLOT_STYLE : STANDBY_SLOT_STYLE,
|
|
3342
3963
|
"data-ivi-source-id": entry.sourceId,
|
|
3343
3964
|
"data-ivi-slot-role": isActive ? "active" : "standby",
|
|
3344
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: SLOT_CONTENT_STYLE, children:
|
|
3965
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: SLOT_CONTENT_STYLE, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3966
|
+
TrackSlotMediaContent,
|
|
3967
|
+
{
|
|
3968
|
+
slot,
|
|
3969
|
+
track,
|
|
3970
|
+
source: entry.source,
|
|
3971
|
+
slotSourceId: entry.sourceId,
|
|
3972
|
+
isActive,
|
|
3973
|
+
runtime: context.runtime,
|
|
3974
|
+
renderTrtc,
|
|
3975
|
+
renderLivekit,
|
|
3976
|
+
renderMedia,
|
|
3977
|
+
imageProps,
|
|
3978
|
+
videoProps,
|
|
3979
|
+
trtcPlayerProps,
|
|
3980
|
+
livekitPlayerProps,
|
|
3981
|
+
adaptToSourceSize,
|
|
3982
|
+
fitStrategy,
|
|
3983
|
+
onLog: mediaLogCallback
|
|
3984
|
+
}
|
|
3985
|
+
) })
|
|
3345
3986
|
},
|
|
3346
3987
|
entry.sourceId
|
|
3347
3988
|
);
|
|
@@ -3349,7 +3990,7 @@ function IVITrackSlot(props) {
|
|
|
3349
3990
|
showSubtitle && activeSource && supportsSubtitleOverlay(activeSource) && /* @__PURE__ */ jsxRuntime.jsx("div", { style: SUBTITLE_OVERLAY_STYLE, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3350
3991
|
IVISubtitleOverlay,
|
|
3351
3992
|
{
|
|
3352
|
-
|
|
3993
|
+
runtime: context.runtime,
|
|
3353
3994
|
...subtitleProps
|
|
3354
3995
|
}
|
|
3355
3996
|
) }),
|
|
@@ -3411,17 +4052,16 @@ function useManagedIviRuntime(config) {
|
|
|
3411
4052
|
onRuntimeInitError,
|
|
3412
4053
|
onLog
|
|
3413
4054
|
} = config;
|
|
3414
|
-
const { url, sessionId } = clientConfig;
|
|
3415
4055
|
const runtime = react.useMemo(() => {
|
|
3416
|
-
if (typeof window === "undefined"
|
|
4056
|
+
if (typeof window === "undefined") {
|
|
3417
4057
|
return null;
|
|
3418
4058
|
}
|
|
3419
|
-
if (!
|
|
3420
|
-
|
|
4059
|
+
if (!clientConfig.transport) {
|
|
4060
|
+
return null;
|
|
3421
4061
|
}
|
|
3422
4062
|
const mergedClientConfig = {
|
|
3423
4063
|
...clientConfig,
|
|
3424
|
-
|
|
4064
|
+
transport: clientConfig.transport,
|
|
3425
4065
|
onLog: (entry) => {
|
|
3426
4066
|
onLog?.(normalizeClientLogEntry(entry));
|
|
3427
4067
|
clientConfig.onLog?.(entry);
|
|
@@ -3436,7 +4076,7 @@ function useManagedIviRuntime(config) {
|
|
|
3436
4076
|
}
|
|
3437
4077
|
};
|
|
3438
4078
|
return new IviRuntimeCoordinator(client, mergedRuntimeConfig);
|
|
3439
|
-
}, [
|
|
4079
|
+
}, [clientConfig, runtimeConfig, onLog]);
|
|
3440
4080
|
react.useEffect(() => {
|
|
3441
4081
|
if (!autoStart || !runtime) {
|
|
3442
4082
|
return;
|
|
@@ -3459,19 +4099,6 @@ function useManagedIviRuntime(config) {
|
|
|
3459
4099
|
}, [autoStart, runtime, onRuntimeInitError]);
|
|
3460
4100
|
return runtime;
|
|
3461
4101
|
}
|
|
3462
|
-
function normalizeUrlToWsUrl(url) {
|
|
3463
|
-
if (/^wss?:\/\//.test(url)) {
|
|
3464
|
-
return url;
|
|
3465
|
-
}
|
|
3466
|
-
if (/^https?:\/\//.test(url)) {
|
|
3467
|
-
const normalized = new URL(url);
|
|
3468
|
-
normalized.protocol = normalized.protocol === "https:" ? "wss:" : "ws:";
|
|
3469
|
-
return normalized.toString();
|
|
3470
|
-
}
|
|
3471
|
-
throw new Error(
|
|
3472
|
-
`useManagedIviRuntime: invalid url "${url}". Expected a ws://, wss://, http://, or https:// URL.`
|
|
3473
|
-
);
|
|
3474
|
-
}
|
|
3475
4102
|
function normalizeClientLogEntry(entry) {
|
|
3476
4103
|
const tag = getClientLogTag(entry.category);
|
|
3477
4104
|
return {
|
|
@@ -3497,18 +4124,27 @@ function normalizeRuntimeLogEntry(entry) {
|
|
|
3497
4124
|
}
|
|
3498
4125
|
function getClientLogTag(category) {
|
|
3499
4126
|
if (category === "send") return "[IVI-SEND]";
|
|
3500
|
-
if (category === "
|
|
4127
|
+
if (category === "transport") return "[IVI-TRANSPORT]";
|
|
3501
4128
|
if (category === "reconnect") return "[IVI-RECONNECT]";
|
|
3502
4129
|
return "[IVI-CLIENT]";
|
|
3503
4130
|
}
|
|
3504
4131
|
|
|
3505
4132
|
exports.EMPTY_RUNTIME_STATE = EMPTY_RUNTIME_STATE;
|
|
4133
|
+
exports.IVILivekitPlayer = IVILivekitPlayer;
|
|
3506
4134
|
exports.IVIStageView = IVIStageView;
|
|
4135
|
+
exports.IVISubtitleOverlay = IVISubtitleOverlay;
|
|
3507
4136
|
exports.IVITrackSlot = IVITrackSlot;
|
|
4137
|
+
exports.IVITrtcPlayer = IVITrtcPlayer;
|
|
3508
4138
|
exports.IviFrontendSdk = IviFrontendSdk;
|
|
3509
4139
|
exports.IviRuntimeCoordinator = IviRuntimeCoordinator;
|
|
3510
4140
|
exports.IviRuntimeDispatcher = IviRuntimeDispatcher;
|
|
4141
|
+
exports.LivekitSourceManager = LivekitSourceManager;
|
|
4142
|
+
exports.TrtcSourceManager = TrtcSourceManager;
|
|
4143
|
+
exports.isLivekitSourcePlayback = isLivekitSourcePlayback;
|
|
4144
|
+
exports.isReadyLivekitRuntimeSource = isReadyLivekitRuntimeSource;
|
|
4145
|
+
exports.isSameLivekitConfig = isSameLivekitConfig;
|
|
3511
4146
|
exports.useIviStageView = useIviStageView;
|
|
4147
|
+
exports.useIviSubtitles = useIviSubtitles;
|
|
3512
4148
|
exports.useManagedIviRuntime = useManagedIviRuntime;
|
|
3513
4149
|
exports.useRuntimeState = useRuntimeState;
|
|
3514
4150
|
//# sourceMappingURL=index.cjs.map
|