@vivix-ai/ivi-frontend-sdk 0.3.1 → 0.3.3

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 CHANGED
@@ -155,12 +155,35 @@ const unlistenEvent = runtime.onEvent((event, state) => {
155
155
  console.log("event:", event.type, "runtime:", state.status);
156
156
  });
157
157
 
158
+ const unlistenTrtcEvent = runtime.onTrtcEvent((event) => {
159
+ console.log("trtc event:", event.sourceId, event.type, event.payload);
160
+ });
161
+
158
162
  // 业务结束时
159
163
  unlistenState();
160
164
  unlistenEvent();
165
+ unlistenTrtcEvent();
161
166
  runtime.stop();
162
167
  ```
163
168
 
169
+ 也可以在创建 runtime 时通过 `onTrtcEvent` 配置项统一接收 TRTC SDK 原始事件:
170
+
171
+ ```ts
172
+ const runtime = new IviRuntimeCoordinator(client, {
173
+ trtcAIDenoiser: {
174
+ enabled: true,
175
+ mode: "normal" // 也可使用 "far_field_reduction"
176
+ },
177
+ onTrtcEvent: (event) => {
178
+ // event.type: "remote_video_available" | "remote_video_unavailable" | "remote_audio_available" | "remote_audio_unavailable"
179
+ // event.rawType: TRTC SDK 原始事件名
180
+ // event.payload: TRTC SDK 原始事件 payload
181
+ }
182
+ });
183
+ ```
184
+
185
+ `trtcAIDenoiser` 默认开启,传 `false` 可关闭;也可以传 `{ enabled, mode, assetsPath }` 调整 TRTC SDK 的 AIDenoiser 插件参数。
186
+
164
187
  ### 3)发送用户文本并触发模型回复
165
188
 
166
189
  ```ts
@@ -472,6 +495,7 @@ TRTC 远端流播放器,自动以 audience 身份入会并订阅远端流。
472
495
  | `loadingFallback` | `ReactNode` | 加载中兜底 |
473
496
  | `errorFallback` | `ReactNode` | 错误兜底 |
474
497
  | `muted` | `boolean` | 是否静音 |
498
+ | `trtcAIDenoiser` | `boolean \| IviRuntimeTrtcAIDenoiserOptions` | 独立使用 `IVITrtcPlayer` 时的 TRTC AIDenoiser 配置,默认开启 |
475
499
  | `className` / `style` | — | 容器样式 |
476
500
 
477
501
  ### `IVILivekitPlayer`
package/dist/index.cjs CHANGED
@@ -1107,9 +1107,15 @@ var ConversationManager = class {
1107
1107
 
1108
1108
  // src/runtime/managers/trtc-source-manager.ts
1109
1109
  var TAG = "[IVI-TRTC]";
1110
+ var DEFAULT_DENOISER_OPTIONS = {
1111
+ enabled: true,
1112
+ mode: "normal"
1113
+ };
1110
1114
  var TrtcSourceManager = class {
1111
- constructor(onLog) {
1115
+ constructor(onLog, onTrtcEvent, aiDenoiser) {
1112
1116
  this.onLog = onLog;
1117
+ this.onTrtcEvent = onTrtcEvent;
1118
+ this.aiDenoiser = aiDenoiser;
1113
1119
  this.sessions = /* @__PURE__ */ new Map();
1114
1120
  this.listeners = /* @__PURE__ */ new Map();
1115
1121
  }
@@ -1136,21 +1142,21 @@ var TrtcSourceManager = class {
1136
1142
  * 按 sourceId 注册/更新 TRTC 配置。
1137
1143
  * 若配置发生变化,会先销毁旧会话再按新参数重建连接。
1138
1144
  */
1139
- upsertSource(sourceId, trtc) {
1145
+ upsertSource(sourceId, trtc, aiDenoiser) {
1140
1146
  const existing = this.sessions.get(sourceId);
1141
1147
  if (!existing) {
1142
1148
  this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")} user=${getTrtcString(trtc, "user_id")}`);
1143
- const session2 = this.createSession(sourceId, trtc);
1149
+ const session2 = this.createSession(sourceId, trtc, aiDenoiser);
1144
1150
  this.sessions.set(sourceId, session2);
1145
1151
  void this.ensureConnected(session2);
1146
1152
  return;
1147
1153
  }
1148
- if (isSameTrtcConfig(existing.trtc, trtc)) {
1154
+ if (isSameTrtcConfig(existing.trtc, trtc) && isSameAIDenoiserOptions(existing.aiDenoiser, aiDenoiser)) {
1149
1155
  return;
1150
1156
  }
1151
1157
  this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")}`);
1152
1158
  void this.disposeSession(existing);
1153
- const session = this.createSession(sourceId, trtc);
1159
+ const session = this.createSession(sourceId, trtc, aiDenoiser);
1154
1160
  this.sessions.set(sourceId, session);
1155
1161
  void this.ensureConnected(session);
1156
1162
  }
@@ -1287,10 +1293,11 @@ var TrtcSourceManager = class {
1287
1293
  binding.muted = muted;
1288
1294
  void this.applyAudioPolicy(session);
1289
1295
  }
1290
- createSession(sourceId, trtc) {
1296
+ createSession(sourceId, trtc, aiDenoiser) {
1291
1297
  return {
1292
1298
  sourceId,
1293
1299
  trtc,
1300
+ aiDenoiser,
1294
1301
  TRTC: null,
1295
1302
  client: null,
1296
1303
  connectPromise: null,
@@ -1345,6 +1352,7 @@ var TrtcSourceManager = class {
1345
1352
  session.views.forEach((binding) => {
1346
1353
  void this.startRemoteVideoForBinding(client, event.userId, event.streamType, binding, remoteVideoKey);
1347
1354
  });
1355
+ this.emitTrtcEvent(session.sourceId, "remote_video_available", TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, event);
1348
1356
  };
1349
1357
  const onRemoteVideoUnavailable = (event) => {
1350
1358
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
@@ -1358,16 +1366,19 @@ var TrtcSourceManager = class {
1358
1366
  userId: event.userId,
1359
1367
  streamType: event.streamType
1360
1368
  }).catch(() => void 0);
1369
+ this.emitTrtcEvent(session.sourceId, "remote_video_unavailable", TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, event);
1361
1370
  };
1362
1371
  const onRemoteAudioAvailable = (event) => {
1363
1372
  this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
1364
1373
  session.remoteAudioUsers.add(event.userId);
1365
1374
  void this.applyAudioPolicy(session);
1375
+ this.emitTrtcEvent(session.sourceId, "remote_audio_available", TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event);
1366
1376
  };
1367
1377
  const onRemoteAudioUnavailable = (event) => {
1368
1378
  this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
1369
1379
  session.remoteAudioUsers.delete(event.userId);
1370
1380
  void this.applyAudioPolicy(session);
1381
+ this.emitTrtcEvent(session.sourceId, "remote_audio_unavailable", TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, event);
1371
1382
  };
1372
1383
  session.onRemoteVideoAvailable = onRemoteVideoAvailable;
1373
1384
  session.onRemoteVideoUnavailable = onRemoteVideoUnavailable;
@@ -1392,6 +1403,7 @@ var TrtcSourceManager = class {
1392
1403
  session.status = "connected";
1393
1404
  session.error = void 0;
1394
1405
  this.log("info", `\u8FDB\u623F\u6210\u529F source=${session.sourceId} room=${roomId}`);
1406
+ await this.applyAIDenoiserPolicy(session, client, sdkAppId, userId, userSig);
1395
1407
  this.emitSnapshot(session.sourceId, {
1396
1408
  status: session.status
1397
1409
  });
@@ -1477,6 +1489,25 @@ var TrtcSourceManager = class {
1477
1489
  await client.muteRemoteAudio(userId, false);
1478
1490
  }
1479
1491
  }
1492
+ async applyAIDenoiserPolicy(session, client, sdkAppId, userId, userSig) {
1493
+ const options = resolveAIDenoiserOptions(session.aiDenoiser ?? this.aiDenoiser);
1494
+ if (!options.enabled) {
1495
+ this.log("info", `TRTC AIDenoiser \u5DF2\u5173\u95ED source=${session.sourceId}`);
1496
+ return;
1497
+ }
1498
+ try {
1499
+ await client.startPlugin("AIDenoiser", {
1500
+ sdkAppId,
1501
+ userId,
1502
+ userSig,
1503
+ mode: getAIDenoiserModeValue(options.mode),
1504
+ ...options.assetsPath ? { assetsPath: options.assetsPath } : {}
1505
+ });
1506
+ this.log("info", `TRTC AIDenoiser \u5DF2\u5F00\u542F source=${session.sourceId} mode=${options.mode}`);
1507
+ } catch (error) {
1508
+ this.log("warn", `TRTC AIDenoiser \u5F00\u542F\u5931\u8D25 source=${session.sourceId}`, error);
1509
+ }
1510
+ }
1480
1511
  /**
1481
1512
  * 销毁单个 source 会话:
1482
1513
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -1540,6 +1571,17 @@ var TrtcSourceManager = class {
1540
1571
  data: extra.length > 0 ? { message, extra } : { message }
1541
1572
  });
1542
1573
  }
1574
+ emitTrtcEvent(sourceId, type, rawType, payload) {
1575
+ const event = {
1576
+ sourceId,
1577
+ type,
1578
+ rawType: String(rawType),
1579
+ payload,
1580
+ userId: payload.userId,
1581
+ streamType: payload.streamType
1582
+ };
1583
+ this.onTrtcEvent?.(event);
1584
+ }
1543
1585
  };
1544
1586
  function isRuntimeTrtcSource(source) {
1545
1587
  return source.status === "ready" && source.playback?.type === "trtc" && typeof source.playback.trtc === "object" && source.playback.trtc !== null;
@@ -1547,6 +1589,11 @@ function isRuntimeTrtcSource(source) {
1547
1589
  function isSameTrtcConfig(a, b) {
1548
1590
  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;
1549
1591
  }
1592
+ function isSameAIDenoiserOptions(a, b) {
1593
+ const left = resolveAIDenoiserOptions(a);
1594
+ const right = resolveAIDenoiserOptions(b);
1595
+ return left.enabled === right.enabled && left.mode === right.mode && left.assetsPath === right.assetsPath;
1596
+ }
1550
1597
  function shouldUseStringRoomId(roomId) {
1551
1598
  if (!/^\d+$/.test(roomId)) {
1552
1599
  return true;
@@ -1558,6 +1605,26 @@ function getTrtcString(trtc, key) {
1558
1605
  const value = trtc[key];
1559
1606
  return typeof value === "string" ? value : "";
1560
1607
  }
1608
+ function resolveAIDenoiserOptions(options) {
1609
+ if (options === false) {
1610
+ return {
1611
+ ...DEFAULT_DENOISER_OPTIONS,
1612
+ enabled: false
1613
+ };
1614
+ }
1615
+ if (options === true || options === void 0) {
1616
+ return DEFAULT_DENOISER_OPTIONS;
1617
+ }
1618
+ return {
1619
+ ...DEFAULT_DENOISER_OPTIONS,
1620
+ ...options,
1621
+ enabled: options.enabled ?? DEFAULT_DENOISER_OPTIONS.enabled,
1622
+ mode: options.mode ?? DEFAULT_DENOISER_OPTIONS.mode
1623
+ };
1624
+ }
1625
+ function getAIDenoiserModeValue(mode) {
1626
+ return mode === "far_field_reduction" ? 1 : 0;
1627
+ }
1561
1628
  function buildRemoteVideoKey(userId, streamType) {
1562
1629
  return `${userId}::${streamType}`;
1563
1630
  }
@@ -2038,6 +2105,7 @@ var IviRuntimeCoordinator = class {
2038
2105
  this.conversationManager = new ConversationManager();
2039
2106
  this.stateListeners = /* @__PURE__ */ new Set();
2040
2107
  this.eventListeners = /* @__PURE__ */ new Set();
2108
+ this.trtcEventListeners = /* @__PURE__ */ new Set();
2041
2109
  this.pendingUserTextToResponseFlows = /* @__PURE__ */ new Map();
2042
2110
  this.userTextFlowCounter = 0;
2043
2111
  this.waitingTracksListValidation = false;
@@ -2057,7 +2125,11 @@ var IviRuntimeCoordinator = class {
2057
2125
  syncStageOnSessionCreated: true,
2058
2126
  ...config
2059
2127
  };
2060
- this.trtcSourceManager = new TrtcSourceManager(this.config.onLog);
2128
+ this.trtcSourceManager = new TrtcSourceManager(
2129
+ this.config.onLog,
2130
+ (event) => this.emitTrtcEvent(event),
2131
+ this.config.trtcAIDenoiser
2132
+ );
2061
2133
  this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
2062
2134
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
2063
2135
  onSessionCreated: (event) => this.onSessionCreated(event),
@@ -2130,6 +2202,12 @@ var IviRuntimeCoordinator = class {
2130
2202
  this.eventListeners.delete(listener);
2131
2203
  };
2132
2204
  }
2205
+ onTrtcEvent(listener) {
2206
+ this.trtcEventListeners.add(listener);
2207
+ return () => {
2208
+ this.trtcEventListeners.delete(listener);
2209
+ };
2210
+ }
2133
2211
  emitLog(entry) {
2134
2212
  this.config.onLog?.(entry);
2135
2213
  }
@@ -2384,6 +2462,10 @@ var IviRuntimeCoordinator = class {
2384
2462
  this.progressUserTextToResponseFlows(event);
2385
2463
  this.eventListeners.forEach((listener) => listener(event, this.state));
2386
2464
  }
2465
+ emitTrtcEvent(event) {
2466
+ this.config.onTrtcEvent?.(event);
2467
+ this.trtcEventListeners.forEach((listener) => listener(event));
2468
+ }
2387
2469
  resetStoppedState() {
2388
2470
  this.rejectPendingUserTextToResponseFlows(
2389
2471
  new Error("Runtime stopped before user text to response flow completed.")
@@ -3126,7 +3208,8 @@ function IVITrtcPlayer(props) {
3126
3208
  style,
3127
3209
  loadingFallback = null,
3128
3210
  errorFallback = null,
3129
- muted = false
3211
+ muted = false,
3212
+ trtcAIDenoiser
3130
3213
  } = props;
3131
3214
  const containerRef = react.useRef(null);
3132
3215
  const viewIdRef = react.useRef(`trtc-view-${Math.random().toString(36).slice(2, 10)}`);
@@ -3147,7 +3230,7 @@ function IVITrtcPlayer(props) {
3147
3230
  }
3148
3231
  let disposed = false;
3149
3232
  if (shouldManageSourceLifecycle) {
3150
- manager.upsertSource(resolvedSourceId, trtc);
3233
+ manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
3151
3234
  }
3152
3235
  const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
3153
3236
  if (disposed) {
@@ -3178,7 +3261,8 @@ function IVITrtcPlayer(props) {
3178
3261
  trtc.app_id,
3179
3262
  trtc.room_id,
3180
3263
  trtc.user_id,
3181
- trtc.user_sig
3264
+ trtc.user_sig,
3265
+ trtcAIDenoiser
3182
3266
  ]);
3183
3267
  react.useEffect(() => {
3184
3268
  manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);