@vivix-ai/ivi-frontend-sdk 0.3.0 → 0.3.2
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 +38 -7
- package/dist/index.cjs +184 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +185 -13
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -45,12 +45,6 @@ pnpm update @vivix-ai/ivi-frontend-sdk
|
|
|
45
45
|
yarn up @vivix-ai/ivi-frontend-sdk
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
### `dist` 与 npm 发布
|
|
49
|
-
|
|
50
|
-
`package.json` 的 `main` / `module` / `types` / `exports` 均指向 `dist/`,且 npm 包通过 `"files": ["dist"]` 发布到 npm 官方仓库。因此发布前必须先完成构建,确保 npm 包包含可运行、可解析类型的产物。
|
|
51
|
-
|
|
52
|
-
**协作约定**:修改 `src/` 后必须在本地执行 `npm run build`(或 `pnpm run build`),并将**与本次源码变更对应的一次完整 `dist/` 输出**一并提交:可与功能改动放在**同一提交**,或在必要时单独提交,提交信息示例:`chore: 重新构建 dist`。禁止只改源码而不更新 `dist/`,避免仓库内源码与产物不一致。
|
|
53
|
-
|
|
54
48
|
### Peer Dependencies
|
|
55
49
|
|
|
56
50
|
React 为可选 peer dependency,仅使用 React 组件时需要:
|
|
@@ -161,12 +155,29 @@ const unlistenEvent = runtime.onEvent((event, state) => {
|
|
|
161
155
|
console.log("event:", event.type, "runtime:", state.status);
|
|
162
156
|
});
|
|
163
157
|
|
|
158
|
+
const unlistenTrtcEvent = runtime.onTrtcEvent((event) => {
|
|
159
|
+
console.log("trtc event:", event.sourceId, event.type, event.payload);
|
|
160
|
+
});
|
|
161
|
+
|
|
164
162
|
// 业务结束时
|
|
165
163
|
unlistenState();
|
|
166
164
|
unlistenEvent();
|
|
165
|
+
unlistenTrtcEvent();
|
|
167
166
|
runtime.stop();
|
|
168
167
|
```
|
|
169
168
|
|
|
169
|
+
也可以在创建 runtime 时通过 `onTrtcEvent` 配置项统一接收 TRTC SDK 原始事件:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const runtime = new IviRuntimeCoordinator(client, {
|
|
173
|
+
onTrtcEvent: (event) => {
|
|
174
|
+
// event.type: "remote_video_available" | "remote_video_unavailable" | "remote_audio_available" | "remote_audio_unavailable"
|
|
175
|
+
// event.rawType: TRTC SDK 原始事件名
|
|
176
|
+
// event.payload: TRTC SDK 原始事件 payload
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
170
181
|
### 3)发送用户文本并触发模型回复
|
|
171
182
|
|
|
172
183
|
```ts
|
|
@@ -514,7 +525,27 @@ HLS 流播放器(基于 `hls.js`),由 `IVITrackSlot` 内部用于 `.m3u8`
|
|
|
514
525
|
>
|
|
515
526
|
> 若需“保留播放/音量,仅隐藏进度条”,请在上层自行渲染自定义控制栏。
|
|
516
527
|
|
|
517
|
-
### `
|
|
528
|
+
### `IVIFlvVideo`
|
|
529
|
+
|
|
530
|
+
FLV 流播放器(基于 `xgplayer` + `xgplayer-flv`),用于 `.flv` 地址播放。`IVITrackSlot` 会按 URL 后缀自动选用。辅助函数 `isFlvUrl(url)` 可判断 URL 是否为 flv。
|
|
531
|
+
|
|
532
|
+
使用前需安装 peer 依赖:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
npm install xgplayer xgplayer-flv
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
| Prop | 类型 | 说明 |
|
|
539
|
+
|------|------|------|
|
|
540
|
+
| `url` | `string` | FLV 播放地址 |
|
|
541
|
+
| `videoProps` | — | 透传 muted / controls / onEnded 等语义 |
|
|
542
|
+
| `style` | — | 容器样式 |
|
|
543
|
+
| `isLive` | `boolean` | 是否直播,默认 `true` |
|
|
544
|
+
| `paused` | `boolean` | 是否暂停(standby 预加载) |
|
|
545
|
+
|
|
546
|
+
> 直播 FLV 通常不会触发 `ended`,切换 source 时请使用定时模式而非「播放完毕」。
|
|
547
|
+
|
|
548
|
+
### `IVIVolumeControl`
|
|
518
549
|
|
|
519
550
|
音量控制浮层组件,由 `IVITrackSlot` 在 `showVolumeControl` 开启时内部使用;当前不从包根公开导出。
|
|
520
551
|
|
package/dist/index.cjs
CHANGED
|
@@ -1108,8 +1108,9 @@ var ConversationManager = class {
|
|
|
1108
1108
|
// src/runtime/managers/trtc-source-manager.ts
|
|
1109
1109
|
var TAG = "[IVI-TRTC]";
|
|
1110
1110
|
var TrtcSourceManager = class {
|
|
1111
|
-
constructor(onLog) {
|
|
1111
|
+
constructor(onLog, onTrtcEvent) {
|
|
1112
1112
|
this.onLog = onLog;
|
|
1113
|
+
this.onTrtcEvent = onTrtcEvent;
|
|
1113
1114
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1114
1115
|
this.listeners = /* @__PURE__ */ new Map();
|
|
1115
1116
|
}
|
|
@@ -1345,6 +1346,7 @@ var TrtcSourceManager = class {
|
|
|
1345
1346
|
session.views.forEach((binding) => {
|
|
1346
1347
|
void this.startRemoteVideoForBinding(client, event.userId, event.streamType, binding, remoteVideoKey);
|
|
1347
1348
|
});
|
|
1349
|
+
this.emitTrtcEvent(session.sourceId, "remote_video_available", TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, event);
|
|
1348
1350
|
};
|
|
1349
1351
|
const onRemoteVideoUnavailable = (event) => {
|
|
1350
1352
|
this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
|
|
@@ -1358,16 +1360,19 @@ var TrtcSourceManager = class {
|
|
|
1358
1360
|
userId: event.userId,
|
|
1359
1361
|
streamType: event.streamType
|
|
1360
1362
|
}).catch(() => void 0);
|
|
1363
|
+
this.emitTrtcEvent(session.sourceId, "remote_video_unavailable", TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, event);
|
|
1361
1364
|
};
|
|
1362
1365
|
const onRemoteAudioAvailable = (event) => {
|
|
1363
1366
|
this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
|
|
1364
1367
|
session.remoteAudioUsers.add(event.userId);
|
|
1365
1368
|
void this.applyAudioPolicy(session);
|
|
1369
|
+
this.emitTrtcEvent(session.sourceId, "remote_audio_available", TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event);
|
|
1366
1370
|
};
|
|
1367
1371
|
const onRemoteAudioUnavailable = (event) => {
|
|
1368
1372
|
this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
|
|
1369
1373
|
session.remoteAudioUsers.delete(event.userId);
|
|
1370
1374
|
void this.applyAudioPolicy(session);
|
|
1375
|
+
this.emitTrtcEvent(session.sourceId, "remote_audio_unavailable", TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, event);
|
|
1371
1376
|
};
|
|
1372
1377
|
session.onRemoteVideoAvailable = onRemoteVideoAvailable;
|
|
1373
1378
|
session.onRemoteVideoUnavailable = onRemoteVideoUnavailable;
|
|
@@ -1540,6 +1545,17 @@ var TrtcSourceManager = class {
|
|
|
1540
1545
|
data: extra.length > 0 ? { message, extra } : { message }
|
|
1541
1546
|
});
|
|
1542
1547
|
}
|
|
1548
|
+
emitTrtcEvent(sourceId, type, rawType, payload) {
|
|
1549
|
+
const event = {
|
|
1550
|
+
sourceId,
|
|
1551
|
+
type,
|
|
1552
|
+
rawType: String(rawType),
|
|
1553
|
+
payload,
|
|
1554
|
+
userId: payload.userId,
|
|
1555
|
+
streamType: payload.streamType
|
|
1556
|
+
};
|
|
1557
|
+
this.onTrtcEvent?.(event);
|
|
1558
|
+
}
|
|
1543
1559
|
};
|
|
1544
1560
|
function isRuntimeTrtcSource(source) {
|
|
1545
1561
|
return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
|
|
@@ -2038,6 +2054,7 @@ var IviRuntimeCoordinator = class {
|
|
|
2038
2054
|
this.conversationManager = new ConversationManager();
|
|
2039
2055
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
2040
2056
|
this.eventListeners = /* @__PURE__ */ new Set();
|
|
2057
|
+
this.trtcEventListeners = /* @__PURE__ */ new Set();
|
|
2041
2058
|
this.pendingUserTextToResponseFlows = /* @__PURE__ */ new Map();
|
|
2042
2059
|
this.userTextFlowCounter = 0;
|
|
2043
2060
|
this.waitingTracksListValidation = false;
|
|
@@ -2057,7 +2074,10 @@ var IviRuntimeCoordinator = class {
|
|
|
2057
2074
|
syncStageOnSessionCreated: true,
|
|
2058
2075
|
...config
|
|
2059
2076
|
};
|
|
2060
|
-
this.trtcSourceManager = new TrtcSourceManager(
|
|
2077
|
+
this.trtcSourceManager = new TrtcSourceManager(
|
|
2078
|
+
this.config.onLog,
|
|
2079
|
+
(event) => this.emitTrtcEvent(event)
|
|
2080
|
+
);
|
|
2061
2081
|
this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
|
|
2062
2082
|
this.sessionHandler = new SessionEventHandler(this.sessionManager, {
|
|
2063
2083
|
onSessionCreated: (event) => this.onSessionCreated(event),
|
|
@@ -2130,6 +2150,12 @@ var IviRuntimeCoordinator = class {
|
|
|
2130
2150
|
this.eventListeners.delete(listener);
|
|
2131
2151
|
};
|
|
2132
2152
|
}
|
|
2153
|
+
onTrtcEvent(listener) {
|
|
2154
|
+
this.trtcEventListeners.add(listener);
|
|
2155
|
+
return () => {
|
|
2156
|
+
this.trtcEventListeners.delete(listener);
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2133
2159
|
emitLog(entry) {
|
|
2134
2160
|
this.config.onLog?.(entry);
|
|
2135
2161
|
}
|
|
@@ -2384,6 +2410,10 @@ var IviRuntimeCoordinator = class {
|
|
|
2384
2410
|
this.progressUserTextToResponseFlows(event);
|
|
2385
2411
|
this.eventListeners.forEach((listener) => listener(event, this.state));
|
|
2386
2412
|
}
|
|
2413
|
+
emitTrtcEvent(event) {
|
|
2414
|
+
this.config.onTrtcEvent?.(event);
|
|
2415
|
+
this.trtcEventListeners.forEach((listener) => listener(event));
|
|
2416
|
+
}
|
|
2387
2417
|
resetStoppedState() {
|
|
2388
2418
|
this.rejectPendingUserTextToResponseFlows(
|
|
2389
2419
|
new Error("Runtime stopped before user text to response flow completed.")
|
|
@@ -3292,6 +3322,132 @@ function IVILivekitPlayer(props) {
|
|
|
3292
3322
|
}
|
|
3293
3323
|
);
|
|
3294
3324
|
}
|
|
3325
|
+
var loadModulesPromise = null;
|
|
3326
|
+
async function loadXgplayerModules() {
|
|
3327
|
+
if (!loadModulesPromise) {
|
|
3328
|
+
loadModulesPromise = (async () => {
|
|
3329
|
+
try {
|
|
3330
|
+
await import('xgplayer/dist/index.min.css');
|
|
3331
|
+
} catch (err) {
|
|
3332
|
+
console.warn("[IVIFlvVideo] \u52A0\u8F7D xgplayer \u6837\u5F0F\u5931\u8D25\uFF0C\u64AD\u653E\u5668\u63A7\u4EF6\u53EF\u80FD\u663E\u793A\u5F02\u5E38", err);
|
|
3333
|
+
}
|
|
3334
|
+
const [playerMod, flvMod] = await Promise.all([import('xgplayer'), import('xgplayer-flv')]);
|
|
3335
|
+
const playerModule = playerMod;
|
|
3336
|
+
const flvModule = flvMod;
|
|
3337
|
+
const Player = playerModule.default ?? playerModule.Player;
|
|
3338
|
+
const FlvPlugin = flvModule.default ?? flvModule.FlvPlugin;
|
|
3339
|
+
if (!Player || !FlvPlugin) {
|
|
3340
|
+
throw new Error("xgplayer \u6216 xgplayer-flv \u6A21\u5757\u5BFC\u51FA\u65E0\u6548");
|
|
3341
|
+
}
|
|
3342
|
+
return { Player, FlvPlugin };
|
|
3343
|
+
})().catch((err) => {
|
|
3344
|
+
loadModulesPromise = null;
|
|
3345
|
+
throw err;
|
|
3346
|
+
});
|
|
3347
|
+
}
|
|
3348
|
+
return loadModulesPromise;
|
|
3349
|
+
}
|
|
3350
|
+
function IVIFlvVideo(props) {
|
|
3351
|
+
const { url, videoProps, style, isLive = true, paused = false } = props;
|
|
3352
|
+
const containerRef = react.useRef(null);
|
|
3353
|
+
const playerRef = react.useRef(null);
|
|
3354
|
+
const pausedRef = react.useRef(paused);
|
|
3355
|
+
const videoPropsRef = react.useRef(videoProps);
|
|
3356
|
+
videoPropsRef.current = videoProps;
|
|
3357
|
+
const reactId = react.useId();
|
|
3358
|
+
const containerId = `ivi-flv-${reactId.replace(/:/g, "")}`;
|
|
3359
|
+
react.useEffect(() => {
|
|
3360
|
+
pausedRef.current = paused;
|
|
3361
|
+
const player = playerRef.current;
|
|
3362
|
+
if (!player) return;
|
|
3363
|
+
if (paused) {
|
|
3364
|
+
player.pause();
|
|
3365
|
+
} else {
|
|
3366
|
+
Promise.resolve(player.play()).catch((err) => {
|
|
3367
|
+
console.warn(
|
|
3368
|
+
"[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",
|
|
3369
|
+
err
|
|
3370
|
+
);
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
}, [paused]);
|
|
3374
|
+
react.useEffect(() => {
|
|
3375
|
+
const container = containerRef.current;
|
|
3376
|
+
if (!container) return;
|
|
3377
|
+
let disposed = false;
|
|
3378
|
+
let endedHandler = null;
|
|
3379
|
+
let errorHandler = null;
|
|
3380
|
+
const setup = async () => {
|
|
3381
|
+
try {
|
|
3382
|
+
const { Player, FlvPlugin } = await loadXgplayerModules();
|
|
3383
|
+
if (disposed || !containerRef.current) return;
|
|
3384
|
+
playerRef.current?.destroy();
|
|
3385
|
+
playerRef.current = null;
|
|
3386
|
+
container.innerHTML = "";
|
|
3387
|
+
const shouldAutoplay = !pausedRef.current;
|
|
3388
|
+
const currentVideoProps = videoPropsRef.current;
|
|
3389
|
+
const muted = Boolean(currentVideoProps?.muted);
|
|
3390
|
+
const player = new Player({
|
|
3391
|
+
id: containerId,
|
|
3392
|
+
el: containerRef.current,
|
|
3393
|
+
url,
|
|
3394
|
+
plugins: [FlvPlugin],
|
|
3395
|
+
isLive,
|
|
3396
|
+
autoplay: shouldAutoplay,
|
|
3397
|
+
autoplayMuted: muted,
|
|
3398
|
+
muted,
|
|
3399
|
+
controls: currentVideoProps?.controls ?? true,
|
|
3400
|
+
width: "100%",
|
|
3401
|
+
height: "100%",
|
|
3402
|
+
fluid: true,
|
|
3403
|
+
videoFillMode: "contain",
|
|
3404
|
+
flv: {
|
|
3405
|
+
retryCount: Number.MAX_SAFE_INTEGER,
|
|
3406
|
+
retryDelay: 500,
|
|
3407
|
+
loadTimeout: 1e4
|
|
3408
|
+
}
|
|
3409
|
+
});
|
|
3410
|
+
playerRef.current = player;
|
|
3411
|
+
errorHandler = (...args) => {
|
|
3412
|
+
console.warn("[IVIFlvVideo] \u64AD\u653E\u5668 error \u4E8B\u4EF6", { url, args });
|
|
3413
|
+
};
|
|
3414
|
+
player.on("error", errorHandler);
|
|
3415
|
+
const onEnded = currentVideoProps?.onEnded;
|
|
3416
|
+
if (onEnded) {
|
|
3417
|
+
endedHandler = () => {
|
|
3418
|
+
videoPropsRef.current?.onEnded?.({});
|
|
3419
|
+
};
|
|
3420
|
+
player.on("ended", endedHandler);
|
|
3421
|
+
}
|
|
3422
|
+
if (!pausedRef.current) {
|
|
3423
|
+
Promise.resolve(player.play()).catch((err) => {
|
|
3424
|
+
console.warn("[IVIFlvVideo] \u521D\u59CB play() \u88AB\u6D4F\u89C8\u5668\u62D2\u7EDD", err);
|
|
3425
|
+
});
|
|
3426
|
+
}
|
|
3427
|
+
} catch (err) {
|
|
3428
|
+
console.warn("[IVIFlvVideo] \u521D\u59CB\u5316 xgplayer-flv \u5931\u8D25", err);
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
3431
|
+
void setup();
|
|
3432
|
+
return () => {
|
|
3433
|
+
disposed = true;
|
|
3434
|
+
const player = playerRef.current;
|
|
3435
|
+
if (player) {
|
|
3436
|
+
if (endedHandler) player.off("ended", endedHandler);
|
|
3437
|
+
if (errorHandler) player.off("error", errorHandler);
|
|
3438
|
+
player.destroy();
|
|
3439
|
+
}
|
|
3440
|
+
playerRef.current = null;
|
|
3441
|
+
if (containerRef.current) {
|
|
3442
|
+
containerRef.current.innerHTML = "";
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
}, [url, isLive, containerId]);
|
|
3446
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { id: containerId, ref: containerRef, style: { width: "100%", height: "100%", ...style } });
|
|
3447
|
+
}
|
|
3448
|
+
function isFlvUrl(url) {
|
|
3449
|
+
return /\.flv(?:$|[?#])/i.test(url);
|
|
3450
|
+
}
|
|
3295
3451
|
var RETRY_DELAY_MS = 500;
|
|
3296
3452
|
var UNLIMITED_RETRIES = Number.MAX_SAFE_INTEGER;
|
|
3297
3453
|
var HLS_LOG_TAG = "[IVI-HLS]";
|
|
@@ -3648,16 +3804,32 @@ function TrackSlotMediaContent(props) {
|
|
|
3648
3804
|
) : void 0
|
|
3649
3805
|
};
|
|
3650
3806
|
const shouldPause = !isActive;
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3807
|
+
if (isM3u8Url(playbackUrl)) {
|
|
3808
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3809
|
+
IVIHlsVideo,
|
|
3810
|
+
{
|
|
3811
|
+
url: playbackUrl,
|
|
3812
|
+
videoProps: mergedVideoProps,
|
|
3813
|
+
style: videoStyle,
|
|
3814
|
+
paused: shouldPause
|
|
3815
|
+
}
|
|
3816
|
+
);
|
|
3817
|
+
}
|
|
3818
|
+
if (isFlvUrl(playbackUrl)) {
|
|
3819
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3820
|
+
IVIFlvVideo,
|
|
3821
|
+
{
|
|
3822
|
+
url: playbackUrl,
|
|
3823
|
+
videoProps: {
|
|
3824
|
+
...mergedVideoProps,
|
|
3825
|
+
controls: isActive ? videoProps?.controls ?? true : false
|
|
3826
|
+
},
|
|
3827
|
+
style: videoStyle,
|
|
3828
|
+
paused: shouldPause
|
|
3829
|
+
}
|
|
3830
|
+
);
|
|
3831
|
+
}
|
|
3832
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3661
3833
|
SlotVideo,
|
|
3662
3834
|
{
|
|
3663
3835
|
src: playbackUrl,
|