@vivix-ai/ivi-frontend-sdk 0.3.6 → 0.3.8
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 +10 -3
- package/dist/index.cjs +206 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -1
- package/dist/index.d.ts +30 -1
- package/dist/index.js +206 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -380,13 +380,15 @@ function useIviSubtitles(
|
|
|
380
380
|
): IviSubtitleItem[];
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
-
基于 runtime 的 conversation / response
|
|
383
|
+
基于 runtime 的 conversation / response 事件维护字幕队列,不负责渲染。字幕进入完成态后默认 3000ms 自动移除,可通过 `hideCompletedAfterMs={false}` 关闭,或通过 `onSubtitleCompleted` 自定义保留/删除策略。默认从 conversation 条目聚合字幕;当 `roles` 包含 `"model"` 且开启 `useModelStreamingTranscript` 时,model 字幕改为使用 `response.output_audio_transcript.*` 事件,`response.output_audio_transcript.done` 只更新文本,只有 `response.done` 才表示这一轮 model 字幕结束。
|
|
384
384
|
|
|
385
385
|
| Option | 类型 | 默认值 | 说明 |
|
|
386
386
|
|--------|------|--------|------|
|
|
387
387
|
| `roles` | `IviSubtitleRole \| IviSubtitleRole[]` | `"user"` | 要收集的发言人角色 |
|
|
388
388
|
| `maxItems` | `number` | `2` | 最多保留的字幕条数,超过后清理最旧条目 |
|
|
389
389
|
| `useModelStreamingTranscript` | `boolean` | `false` | 当 `roles` 包含 `"model"` 时,model 字幕是否改用 `response.output_audio_transcript.*` 事件 |
|
|
390
|
+
| `hideCompletedAfterMs` | `number \| false` | `3000` | 字幕进入完成态后多久自动移除;传 `false` 保留到被 `maxItems` 裁剪 |
|
|
391
|
+
| `onSubtitleCompleted` | `(item, context) => "keep" \| "remove" \| { removeAfterMs } \| void \| Promise<...>` | — | 自定义完成态清理策略,优先于 `hideCompletedAfterMs` |
|
|
390
392
|
|
|
391
393
|
`IviSubtitleItem` 包含 `id`、`role`、`lifecycle`、`status`、`text`、`transcript`、`displayText`、`timestamp`、`updatedAt`、`content`、`item`、`source`、`responseId`、`itemId`。
|
|
392
394
|
|
|
@@ -395,7 +397,10 @@ function CustomSubtitles({ runtime }: { runtime: IviRuntimeCoordinator | null })
|
|
|
395
397
|
const subtitles = useIviSubtitles(runtime, {
|
|
396
398
|
roles: ["user", "model"],
|
|
397
399
|
maxItems: 5,
|
|
398
|
-
useModelStreamingTranscript: true
|
|
400
|
+
useModelStreamingTranscript: true,
|
|
401
|
+
onSubtitleCompleted: (item) => (
|
|
402
|
+
item.role === "model" ? { removeAfterMs: 4000 } : { removeAfterMs: 2500 }
|
|
403
|
+
)
|
|
399
404
|
});
|
|
400
405
|
|
|
401
406
|
return (
|
|
@@ -466,7 +471,7 @@ function useApplyVolumeToSlot(
|
|
|
466
471
|
| `showVolumeControl` | `boolean` | — | 是否显示音量控制浮层 |
|
|
467
472
|
| `volumeControlProps` | — | — | 音量控制自定义配置 |
|
|
468
473
|
| `showSubtitle` | `boolean` | — | 是否显示字幕浮层 |
|
|
469
|
-
| `subtitleProps` | `Omit<IVISubtitleOverlayProps, "runtime">` | — | 字幕自定义配置,可传 `roles`、`maxItems`、`useModelStreamingTranscript` 等 |
|
|
474
|
+
| `subtitleProps` | `Omit<IVISubtitleOverlayProps, "runtime">` | — | 字幕自定义配置,可传 `roles`、`maxItems`、`useModelStreamingTranscript`、`hideCompletedAfterMs`、`onSubtitleCompleted` 等 |
|
|
470
475
|
| `trtcPlayerProps` | — | — | TRTC 播放器自定义配置 |
|
|
471
476
|
| `livekitPlayerProps` | — | — | LiveKit 播放器自定义配置 |
|
|
472
477
|
| `videoProps` / `imageProps` | — | — | 透传给原生 `<video>` / `<img>` |
|
|
@@ -577,6 +582,8 @@ npm install xgplayer xgplayer-flv
|
|
|
577
582
|
| `maxItems` | `number` | `2` | 最多保留的字幕条数,超过后清理最旧条目 |
|
|
578
583
|
| `maxVisible` | `number` | — | 已废弃,兼容旧字段;请使用 `maxItems` |
|
|
579
584
|
| `useModelStreamingTranscript` | `boolean` | `false` | 当 `roles` 包含 `"model"` 时,model 字幕是否改用 `response.output_audio_transcript.*` 事件 |
|
|
585
|
+
| `hideCompletedAfterMs` | `number \| false` | `3000` | 字幕进入完成态后多久自动移除;传 `false` 关闭自动移除 |
|
|
586
|
+
| `onSubtitleCompleted` | `(item, context) => "keep" \| "remove" \| { removeAfterMs } \| void \| Promise<...>` | — | 自定义完成态清理策略,优先于 `hideCompletedAfterMs` |
|
|
580
587
|
| `subtitleStyle` | `IVISubtitleOverlayStyle` | — | 样式配置 |
|
|
581
588
|
| `className` / `style` | — | — | 样式 |
|
|
582
589
|
|
package/dist/index.cjs
CHANGED
|
@@ -1107,6 +1107,8 @@ var ConversationManager = class {
|
|
|
1107
1107
|
|
|
1108
1108
|
// src/runtime/managers/trtc-source-manager.ts
|
|
1109
1109
|
var TAG = "[IVI-TRTC]";
|
|
1110
|
+
var TRTC_VIEW_ATTR = "data-ivi-trtc-view";
|
|
1111
|
+
var TRTC_MEDIA_STYLE_ID = "ivi-trtc-media-style";
|
|
1110
1112
|
var DEFAULT_DENOISER_OPTIONS = {
|
|
1111
1113
|
enabled: true,
|
|
1112
1114
|
mode: "normal"
|
|
@@ -1268,6 +1270,8 @@ var TrtcSourceManager = class {
|
|
|
1268
1270
|
if (!session) {
|
|
1269
1271
|
throw new Error(`TRTC source session not found: ${sourceId}`);
|
|
1270
1272
|
}
|
|
1273
|
+
ensureTrtcMediaStyleSheet();
|
|
1274
|
+
container.setAttribute(TRTC_VIEW_ATTR, "");
|
|
1271
1275
|
session.views.set(viewId, {
|
|
1272
1276
|
sourceId,
|
|
1273
1277
|
container,
|
|
@@ -1290,6 +1294,7 @@ var TrtcSourceManager = class {
|
|
|
1290
1294
|
}
|
|
1291
1295
|
const binding = session.views.get(viewId);
|
|
1292
1296
|
if (binding?.sourceId === sourceId) {
|
|
1297
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1293
1298
|
session.views.delete(viewId);
|
|
1294
1299
|
}
|
|
1295
1300
|
void this.applyAudioPolicy(session);
|
|
@@ -1311,6 +1316,7 @@ var TrtcSourceManager = class {
|
|
|
1311
1316
|
this.sourceSessionKeys.delete(sourceId);
|
|
1312
1317
|
session.views.forEach((binding, viewId) => {
|
|
1313
1318
|
if (binding.sourceId === sourceId) {
|
|
1319
|
+
binding.container.removeAttribute(TRTC_VIEW_ATTR);
|
|
1314
1320
|
session.views.delete(viewId);
|
|
1315
1321
|
}
|
|
1316
1322
|
});
|
|
@@ -1630,6 +1636,38 @@ var TrtcSourceManager = class {
|
|
|
1630
1636
|
this.onTrtcEvent?.(event);
|
|
1631
1637
|
}
|
|
1632
1638
|
};
|
|
1639
|
+
function ensureTrtcMediaStyleSheet() {
|
|
1640
|
+
if (typeof document === "undefined") {
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
if (document.getElementById(TRTC_MEDIA_STYLE_ID)) {
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
const style = document.createElement("style");
|
|
1647
|
+
style.id = TRTC_MEDIA_STYLE_ID;
|
|
1648
|
+
style.textContent = `
|
|
1649
|
+
[${TRTC_VIEW_ATTR}] [id^="player_"],
|
|
1650
|
+
[${TRTC_VIEW_ATTR}] [id^="video_"],
|
|
1651
|
+
[${TRTC_VIEW_ATTR}] [id^="audio_"],
|
|
1652
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1653
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1654
|
+
width: 100% !important;
|
|
1655
|
+
height: 100% !important;
|
|
1656
|
+
max-width: 100% !important;
|
|
1657
|
+
max-height: 100% !important;
|
|
1658
|
+
background: transparent !important;
|
|
1659
|
+
background-color: transparent !important;
|
|
1660
|
+
background-image: none !important;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
[${TRTC_VIEW_ATTR}] video,
|
|
1664
|
+
[${TRTC_VIEW_ATTR}] canvas {
|
|
1665
|
+
object-fit: contain !important;
|
|
1666
|
+
display: block !important;
|
|
1667
|
+
}
|
|
1668
|
+
`;
|
|
1669
|
+
document.head.appendChild(style);
|
|
1670
|
+
}
|
|
1633
1671
|
function isRuntimeTrtcSource(source) {
|
|
1634
1672
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
1635
1673
|
}
|
|
@@ -1721,6 +1759,9 @@ function enforceContainMedia(container) {
|
|
|
1721
1759
|
element.style.setProperty("max-height", "100%", "important");
|
|
1722
1760
|
element.style.setProperty("object-fit", "contain", "important");
|
|
1723
1761
|
element.style.setProperty("display", "block", "important");
|
|
1762
|
+
element.style.setProperty("background", "transparent", "important");
|
|
1763
|
+
element.style.setProperty("background-color", "transparent", "important");
|
|
1764
|
+
element.style.setProperty("background-image", "none", "important");
|
|
1724
1765
|
});
|
|
1725
1766
|
}
|
|
1726
1767
|
|
|
@@ -3080,9 +3121,12 @@ function useApplyVolumeToSlot(containerRef, volume, enabled, activeSourceId) {
|
|
|
3080
3121
|
return () => observer.disconnect();
|
|
3081
3122
|
}, [containerRef, volume, enabled, activeSourceId]);
|
|
3082
3123
|
}
|
|
3124
|
+
var DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = 3e3;
|
|
3083
3125
|
function useIviSubtitles(runtime, options = {}) {
|
|
3084
3126
|
const roles = options.roles ?? "user";
|
|
3085
3127
|
const maxItems = normalizeMaxItems(options.maxItems);
|
|
3128
|
+
const hideCompletedAfterMs = normalizeHideCompletedAfterMs(options.hideCompletedAfterMs);
|
|
3129
|
+
const onSubtitleCompleted = options.onSubtitleCompleted;
|
|
3086
3130
|
const roleKey = Array.isArray(roles) ? roles.join("\0") : roles;
|
|
3087
3131
|
const roleSet = react.useMemo(
|
|
3088
3132
|
() => new Set(roleKey.split("\0")),
|
|
@@ -3092,9 +3136,14 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3092
3136
|
const [subtitles, setSubtitles] = react.useState([]);
|
|
3093
3137
|
const seenIdsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
3094
3138
|
const initializedRef = react.useRef(false);
|
|
3139
|
+
const completedSubtitleIdsRef = react.useRef(/* @__PURE__ */ new Set());
|
|
3140
|
+
const completionControllersRef = react.useRef(/* @__PURE__ */ new Map());
|
|
3141
|
+
const completionPolicyRef = react.useRef(null);
|
|
3095
3142
|
react.useEffect(() => {
|
|
3096
3143
|
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
3097
3144
|
initializedRef.current = false;
|
|
3145
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3146
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3098
3147
|
setSubtitles([]);
|
|
3099
3148
|
if (!runtime) {
|
|
3100
3149
|
return;
|
|
@@ -3150,6 +3199,8 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3150
3199
|
if (event.type === "session.ended") {
|
|
3151
3200
|
seenIdsRef.current = /* @__PURE__ */ new Set();
|
|
3152
3201
|
initializedRef.current = false;
|
|
3202
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3203
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3153
3204
|
setSubtitles([]);
|
|
3154
3205
|
return;
|
|
3155
3206
|
}
|
|
@@ -3167,6 +3218,78 @@ function useIviSubtitles(runtime, options = {}) {
|
|
|
3167
3218
|
syncConversations(state.conversations);
|
|
3168
3219
|
});
|
|
3169
3220
|
}, [runtime, roleSet, maxItems, useModelStreamingTranscript]);
|
|
3221
|
+
react.useEffect(() => {
|
|
3222
|
+
return () => {
|
|
3223
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3224
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3225
|
+
};
|
|
3226
|
+
}, []);
|
|
3227
|
+
react.useEffect(() => {
|
|
3228
|
+
const previousPolicy = completionPolicyRef.current;
|
|
3229
|
+
const policyChanged = Boolean(
|
|
3230
|
+
previousPolicy && (previousPolicy.hideCompletedAfterMs !== hideCompletedAfterMs || previousPolicy.onSubtitleCompleted !== onSubtitleCompleted)
|
|
3231
|
+
);
|
|
3232
|
+
completionPolicyRef.current = { hideCompletedAfterMs, onSubtitleCompleted };
|
|
3233
|
+
if (policyChanged) {
|
|
3234
|
+
completedSubtitleIdsRef.current = /* @__PURE__ */ new Set();
|
|
3235
|
+
abortCompletedSubtitleTasks(completionControllersRef.current);
|
|
3236
|
+
}
|
|
3237
|
+
const subtitleById = new Map(subtitles.map((item) => [item.id, item]));
|
|
3238
|
+
const completedIds = completedSubtitleIdsRef.current;
|
|
3239
|
+
const controllers = completionControllersRef.current;
|
|
3240
|
+
for (const [id, controller] of Array.from(controllers)) {
|
|
3241
|
+
const item = subtitleById.get(id);
|
|
3242
|
+
if (!item || !isCompletedSubtitle(item)) {
|
|
3243
|
+
controller.abort();
|
|
3244
|
+
controllers.delete(id);
|
|
3245
|
+
completedIds.delete(id);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
for (const id of Array.from(completedIds)) {
|
|
3249
|
+
const item = subtitleById.get(id);
|
|
3250
|
+
if (!item || !isCompletedSubtitle(item)) {
|
|
3251
|
+
completedIds.delete(id);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
for (const item of subtitles) {
|
|
3255
|
+
if (!isCompletedSubtitle(item) || completedIds.has(item.id)) {
|
|
3256
|
+
continue;
|
|
3257
|
+
}
|
|
3258
|
+
completedIds.add(item.id);
|
|
3259
|
+
const controller = new AbortController();
|
|
3260
|
+
controllers.set(item.id, controller);
|
|
3261
|
+
const context = {
|
|
3262
|
+
entries: subtitles,
|
|
3263
|
+
reason: getSubtitleCompletedReason(item),
|
|
3264
|
+
signal: controller.signal
|
|
3265
|
+
};
|
|
3266
|
+
let decisionResult;
|
|
3267
|
+
try {
|
|
3268
|
+
decisionResult = onSubtitleCompleted ? onSubtitleCompleted(item, context) : getDefaultCompletedSubtitleDecision(hideCompletedAfterMs);
|
|
3269
|
+
} catch {
|
|
3270
|
+
decisionResult = "keep";
|
|
3271
|
+
}
|
|
3272
|
+
void Promise.resolve(decisionResult).then((decision) => {
|
|
3273
|
+
applyCompletedSubtitleDecision(
|
|
3274
|
+
item.id,
|
|
3275
|
+
decision,
|
|
3276
|
+
controller,
|
|
3277
|
+
controllers,
|
|
3278
|
+
completedIds,
|
|
3279
|
+
(id) => setSubtitles((previous) => removeSubtitleById(previous, id))
|
|
3280
|
+
);
|
|
3281
|
+
}).catch(() => {
|
|
3282
|
+
applyCompletedSubtitleDecision(
|
|
3283
|
+
item.id,
|
|
3284
|
+
"keep",
|
|
3285
|
+
controller,
|
|
3286
|
+
controllers,
|
|
3287
|
+
completedIds,
|
|
3288
|
+
(id) => setSubtitles((previous) => removeSubtitleById(previous, id))
|
|
3289
|
+
);
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
}, [subtitles, hideCompletedAfterMs, onSubtitleCompleted]);
|
|
3170
3293
|
return subtitles;
|
|
3171
3294
|
}
|
|
3172
3295
|
function normalizeMaxItems(maxItems) {
|
|
@@ -3178,6 +3301,35 @@ function normalizeMaxItems(maxItems) {
|
|
|
3178
3301
|
}
|
|
3179
3302
|
return Math.max(0, Math.floor(maxItems));
|
|
3180
3303
|
}
|
|
3304
|
+
function normalizeHideCompletedAfterMs(hideCompletedAfterMs) {
|
|
3305
|
+
if (hideCompletedAfterMs === false) {
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
if (hideCompletedAfterMs === void 0 || !Number.isFinite(hideCompletedAfterMs)) {
|
|
3309
|
+
return DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
|
|
3310
|
+
}
|
|
3311
|
+
return Math.max(0, Math.floor(hideCompletedAfterMs));
|
|
3312
|
+
}
|
|
3313
|
+
function getDefaultCompletedSubtitleDecision(hideCompletedAfterMs) {
|
|
3314
|
+
if (hideCompletedAfterMs === false) {
|
|
3315
|
+
return "keep";
|
|
3316
|
+
}
|
|
3317
|
+
return hideCompletedAfterMs === 0 ? "remove" : { removeAfterMs: hideCompletedAfterMs };
|
|
3318
|
+
}
|
|
3319
|
+
function normalizeCompletedSubtitleDecision(decision) {
|
|
3320
|
+
if (decision === "keep" || decision === "remove") {
|
|
3321
|
+
return decision;
|
|
3322
|
+
}
|
|
3323
|
+
if (decision && typeof decision === "object" && "removeAfterMs" in decision) {
|
|
3324
|
+
const removeAfterMs = Number(decision.removeAfterMs);
|
|
3325
|
+
if (!Number.isFinite(removeAfterMs)) {
|
|
3326
|
+
return "keep";
|
|
3327
|
+
}
|
|
3328
|
+
const normalized = Math.max(0, Math.floor(removeAfterMs));
|
|
3329
|
+
return normalized === 0 ? "remove" : { removeAfterMs: normalized };
|
|
3330
|
+
}
|
|
3331
|
+
return "keep";
|
|
3332
|
+
}
|
|
3181
3333
|
function shouldUseConversationRole(role, roleSet, useModelStreamingTranscript) {
|
|
3182
3334
|
if (!roleSet.has(role)) {
|
|
3183
3335
|
return false;
|
|
@@ -3191,6 +3343,54 @@ function trimSubtitleItems(items, maxItems) {
|
|
|
3191
3343
|
const sorted = [...items].sort((a, b) => a.timestamp - b.timestamp);
|
|
3192
3344
|
return sorted.length > maxItems ? sorted.slice(sorted.length - maxItems) : sorted;
|
|
3193
3345
|
}
|
|
3346
|
+
function isCompletedSubtitle(item) {
|
|
3347
|
+
return item.lifecycle === "done" || item.status === "completed" || item.status === "incomplete";
|
|
3348
|
+
}
|
|
3349
|
+
function getSubtitleCompletedReason(item) {
|
|
3350
|
+
return item.source === "response_audio_transcript" || item.role === "model" ? "response_done" : "conversation_done";
|
|
3351
|
+
}
|
|
3352
|
+
function abortCompletedSubtitleTasks(controllers) {
|
|
3353
|
+
for (const controller of controllers.values()) {
|
|
3354
|
+
controller.abort();
|
|
3355
|
+
}
|
|
3356
|
+
controllers.clear();
|
|
3357
|
+
}
|
|
3358
|
+
function applyCompletedSubtitleDecision(itemId, decision, controller, controllers, completedIds, removeSubtitle) {
|
|
3359
|
+
if (controller.signal.aborted) {
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
const normalizedDecision = normalizeCompletedSubtitleDecision(decision);
|
|
3363
|
+
if (normalizedDecision === "keep") {
|
|
3364
|
+
controllers.delete(itemId);
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
const remove = () => {
|
|
3368
|
+
if (controller.signal.aborted) {
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
controllers.delete(itemId);
|
|
3372
|
+
completedIds.delete(itemId);
|
|
3373
|
+
removeSubtitle(itemId);
|
|
3374
|
+
};
|
|
3375
|
+
if (normalizedDecision === "remove") {
|
|
3376
|
+
remove();
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const timeout = setTimeout(remove, normalizedDecision.removeAfterMs);
|
|
3380
|
+
controller.signal.addEventListener(
|
|
3381
|
+
"abort",
|
|
3382
|
+
() => {
|
|
3383
|
+
clearTimeout(timeout);
|
|
3384
|
+
},
|
|
3385
|
+
{ once: true }
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
function removeSubtitleById(items, itemId) {
|
|
3389
|
+
if (!items.some((item) => item.id === itemId)) {
|
|
3390
|
+
return items;
|
|
3391
|
+
}
|
|
3392
|
+
return items.filter((item) => item.id !== itemId);
|
|
3393
|
+
}
|
|
3194
3394
|
function getDisplayText(item) {
|
|
3195
3395
|
return item.text || item.transcript;
|
|
3196
3396
|
}
|
|
@@ -3316,6 +3516,8 @@ function IVISubtitleOverlay(props) {
|
|
|
3316
3516
|
maxItems,
|
|
3317
3517
|
maxVisible,
|
|
3318
3518
|
useModelStreamingTranscript,
|
|
3519
|
+
hideCompletedAfterMs,
|
|
3520
|
+
onSubtitleCompleted,
|
|
3319
3521
|
subtitleStyle,
|
|
3320
3522
|
className,
|
|
3321
3523
|
style
|
|
@@ -3323,7 +3525,9 @@ function IVISubtitleOverlay(props) {
|
|
|
3323
3525
|
const entries = useIviSubtitles(runtime, {
|
|
3324
3526
|
roles,
|
|
3325
3527
|
maxItems: maxItems ?? maxVisible,
|
|
3326
|
-
useModelStreamingTranscript
|
|
3528
|
+
useModelStreamingTranscript,
|
|
3529
|
+
hideCompletedAfterMs,
|
|
3530
|
+
onSubtitleCompleted
|
|
3327
3531
|
});
|
|
3328
3532
|
if (entries.length === 0) return null;
|
|
3329
3533
|
const fontFamily = subtitleStyle?.fontFamily ?? "system-ui, -apple-system, sans-serif";
|
|
@@ -3455,7 +3659,6 @@ function IVITrtcPlayer(props) {
|
|
|
3455
3659
|
height: "100%",
|
|
3456
3660
|
minWidth: 0,
|
|
3457
3661
|
minHeight: 0,
|
|
3458
|
-
backgroundColor: "#000",
|
|
3459
3662
|
position: "relative",
|
|
3460
3663
|
...style
|
|
3461
3664
|
},
|
|
@@ -4414,6 +4617,7 @@ function getClientLogTag(category) {
|
|
|
4414
4617
|
return "[IVI-CLIENT]";
|
|
4415
4618
|
}
|
|
4416
4619
|
|
|
4620
|
+
exports.DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS = DEFAULT_HIDE_COMPLETED_SUBTITLE_AFTER_MS;
|
|
4417
4621
|
exports.EMPTY_RUNTIME_STATE = EMPTY_RUNTIME_STATE;
|
|
4418
4622
|
exports.IVILivekitPlayer = IVILivekitPlayer;
|
|
4419
4623
|
exports.IVIStageView = IVIStageView;
|