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