@vivix-ai/ivi-frontend-sdk 0.3.2 → 0.3.4

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/dist/index.d.cts CHANGED
@@ -72,6 +72,21 @@ interface IviRuntimeTrtcEvent {
72
72
  streamType?: unknown;
73
73
  }
74
74
  type IviRuntimeTrtcEventListener = (event: IviRuntimeTrtcEvent) => void;
75
+ type IviRuntimeTrtcAIDenoiserMode = "normal" | "far_field_reduction";
76
+ interface IviRuntimeTrtcAIDenoiserOptions {
77
+ /**
78
+ * 是否启用 TRTC AIDenoiser 插件,默认 true。
79
+ */
80
+ enabled?: boolean;
81
+ /**
82
+ * 降噪模式,默认 "normal"。
83
+ */
84
+ mode?: IviRuntimeTrtcAIDenoiserMode;
85
+ /**
86
+ * TRTC AIDenoiser 资源路径;未配置时使用 TRTC SDK 默认加载逻辑。
87
+ */
88
+ assetsPath?: string;
89
+ }
75
90
  type IviRuntimeConversationLifecycle = "added" | "done";
76
91
  type IviRuntimeConversationStatus = "in_progress" | "completed" | "incomplete";
77
92
  interface IviRuntimeConversationItem {
@@ -103,6 +118,10 @@ interface IviRuntimeCoordinatorConfig {
103
118
  * TRTC SDK 原始事件回调;可用于业务监听远端音视频可用性等底层事件。
104
119
  */
105
120
  onTrtcEvent?: IviRuntimeTrtcEventListener;
121
+ /**
122
+ * TRTC SDK AIDenoiser 降噪配置。默认开启;传 false 可关闭。
123
+ */
124
+ trtcAIDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions;
106
125
  }
107
126
  /**
108
127
  * 用户输入触发回复链路配置。
@@ -177,9 +196,11 @@ type TrtcSourceListener = (snapshot: TrtcSourceSnapshot) => void;
177
196
  declare class TrtcSourceManager {
178
197
  private readonly onLog?;
179
198
  private readonly onTrtcEvent?;
199
+ private readonly aiDenoiser?;
180
200
  private readonly sessions;
201
+ private readonly sourceSessionKeys;
181
202
  private readonly listeners;
182
- constructor(onLog?: IviRuntimeLogCallback | undefined, onTrtcEvent?: IviRuntimeTrtcEventListener | undefined);
203
+ constructor(onLog?: IviRuntimeLogCallback | undefined, onTrtcEvent?: IviRuntimeTrtcEventListener | undefined, aiDenoiser?: (boolean | IviRuntimeTrtcAIDenoiserOptions) | undefined);
183
204
  /**
184
205
  * 将 runtime 当前 sources 与 TRTC 会话池对齐:
185
206
  * - 对 ready + trtc 的 source 进行 upsert(必要时建立连接)
@@ -190,7 +211,7 @@ declare class TrtcSourceManager {
190
211
  * 按 sourceId 注册/更新 TRTC 配置。
191
212
  * 若配置发生变化,会先销毁旧会话再按新参数重建连接。
192
213
  */
193
- upsertSource(sourceId: string, trtc: IviSourcePlaybackTrtc): void;
214
+ upsertSource(sourceId: string, trtc: IviSourcePlaybackTrtc, aiDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions): void;
194
215
  /**
195
216
  * 删除指定 source 的 TRTC 会话并释放连接资源。
196
217
  * 该操作通常由 source.deleted 或会话重置触发。
@@ -218,6 +239,11 @@ declare class TrtcSourceManager {
218
239
  attachView(sourceId: string, viewId: string, container: HTMLElement, muted: boolean): Promise<void>;
219
240
  detachView(sourceId: string, viewId: string): void;
220
241
  updateViewMuted(sourceId: string, viewId: string, muted: boolean): void;
242
+ private unlinkSourceFromSession;
243
+ private getSession;
244
+ private snapshotFromSession;
245
+ private emitSessionSnapshot;
246
+ private emitSessionTrtcEvent;
221
247
  private createSession;
222
248
  /**
223
249
  * 懒连接入口:同一会话只允许一次并发 connectPromise。
@@ -232,6 +258,7 @@ declare class TrtcSourceManager {
232
258
  * - 至少一个 view 需出声 -> 对可用远端用户解除静音
233
259
  */
234
260
  private applyAudioPolicy;
261
+ private applyAIDenoiserPolicy;
235
262
  /**
236
263
  * 销毁单个 source 会话:
237
264
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -546,6 +573,11 @@ interface IVITrtcPlayerProps {
546
573
  loadingFallback?: ReactNode;
547
574
  errorFallback?: ReactNode;
548
575
  muted?: boolean;
576
+ /**
577
+ * 独立使用 IVITrtcPlayer(未传 runtime)时的 TRTC AIDenoiser 降噪配置。
578
+ * 默认开启;传 false 可关闭。
579
+ */
580
+ trtcAIDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions;
549
581
  }
550
582
  /**
551
583
  * 默认 TRTC 播放器:挂载即加入房间并拉流,连接由 TrtcSourceManager 统一复用。
@@ -724,4 +756,4 @@ declare function useManagedIviRuntime(config: IviManagedRuntimeConfig): IviRunti
724
756
 
725
757
  declare function useIviStageView(): IviStageViewContextValue;
726
758
 
727
- export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
759
+ export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
package/dist/index.d.ts CHANGED
@@ -72,6 +72,21 @@ interface IviRuntimeTrtcEvent {
72
72
  streamType?: unknown;
73
73
  }
74
74
  type IviRuntimeTrtcEventListener = (event: IviRuntimeTrtcEvent) => void;
75
+ type IviRuntimeTrtcAIDenoiserMode = "normal" | "far_field_reduction";
76
+ interface IviRuntimeTrtcAIDenoiserOptions {
77
+ /**
78
+ * 是否启用 TRTC AIDenoiser 插件,默认 true。
79
+ */
80
+ enabled?: boolean;
81
+ /**
82
+ * 降噪模式,默认 "normal"。
83
+ */
84
+ mode?: IviRuntimeTrtcAIDenoiserMode;
85
+ /**
86
+ * TRTC AIDenoiser 资源路径;未配置时使用 TRTC SDK 默认加载逻辑。
87
+ */
88
+ assetsPath?: string;
89
+ }
75
90
  type IviRuntimeConversationLifecycle = "added" | "done";
76
91
  type IviRuntimeConversationStatus = "in_progress" | "completed" | "incomplete";
77
92
  interface IviRuntimeConversationItem {
@@ -103,6 +118,10 @@ interface IviRuntimeCoordinatorConfig {
103
118
  * TRTC SDK 原始事件回调;可用于业务监听远端音视频可用性等底层事件。
104
119
  */
105
120
  onTrtcEvent?: IviRuntimeTrtcEventListener;
121
+ /**
122
+ * TRTC SDK AIDenoiser 降噪配置。默认开启;传 false 可关闭。
123
+ */
124
+ trtcAIDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions;
106
125
  }
107
126
  /**
108
127
  * 用户输入触发回复链路配置。
@@ -177,9 +196,11 @@ type TrtcSourceListener = (snapshot: TrtcSourceSnapshot) => void;
177
196
  declare class TrtcSourceManager {
178
197
  private readonly onLog?;
179
198
  private readonly onTrtcEvent?;
199
+ private readonly aiDenoiser?;
180
200
  private readonly sessions;
201
+ private readonly sourceSessionKeys;
181
202
  private readonly listeners;
182
- constructor(onLog?: IviRuntimeLogCallback | undefined, onTrtcEvent?: IviRuntimeTrtcEventListener | undefined);
203
+ constructor(onLog?: IviRuntimeLogCallback | undefined, onTrtcEvent?: IviRuntimeTrtcEventListener | undefined, aiDenoiser?: (boolean | IviRuntimeTrtcAIDenoiserOptions) | undefined);
183
204
  /**
184
205
  * 将 runtime 当前 sources 与 TRTC 会话池对齐:
185
206
  * - 对 ready + trtc 的 source 进行 upsert(必要时建立连接)
@@ -190,7 +211,7 @@ declare class TrtcSourceManager {
190
211
  * 按 sourceId 注册/更新 TRTC 配置。
191
212
  * 若配置发生变化,会先销毁旧会话再按新参数重建连接。
192
213
  */
193
- upsertSource(sourceId: string, trtc: IviSourcePlaybackTrtc): void;
214
+ upsertSource(sourceId: string, trtc: IviSourcePlaybackTrtc, aiDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions): void;
194
215
  /**
195
216
  * 删除指定 source 的 TRTC 会话并释放连接资源。
196
217
  * 该操作通常由 source.deleted 或会话重置触发。
@@ -218,6 +239,11 @@ declare class TrtcSourceManager {
218
239
  attachView(sourceId: string, viewId: string, container: HTMLElement, muted: boolean): Promise<void>;
219
240
  detachView(sourceId: string, viewId: string): void;
220
241
  updateViewMuted(sourceId: string, viewId: string, muted: boolean): void;
242
+ private unlinkSourceFromSession;
243
+ private getSession;
244
+ private snapshotFromSession;
245
+ private emitSessionSnapshot;
246
+ private emitSessionTrtcEvent;
221
247
  private createSession;
222
248
  /**
223
249
  * 懒连接入口:同一会话只允许一次并发 connectPromise。
@@ -232,6 +258,7 @@ declare class TrtcSourceManager {
232
258
  * - 至少一个 view 需出声 -> 对可用远端用户解除静音
233
259
  */
234
260
  private applyAudioPolicy;
261
+ private applyAIDenoiserPolicy;
235
262
  /**
236
263
  * 销毁单个 source 会话:
237
264
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -546,6 +573,11 @@ interface IVITrtcPlayerProps {
546
573
  loadingFallback?: ReactNode;
547
574
  errorFallback?: ReactNode;
548
575
  muted?: boolean;
576
+ /**
577
+ * 独立使用 IVITrtcPlayer(未传 runtime)时的 TRTC AIDenoiser 降噪配置。
578
+ * 默认开启;传 false 可关闭。
579
+ */
580
+ trtcAIDenoiser?: boolean | IviRuntimeTrtcAIDenoiserOptions;
549
581
  }
550
582
  /**
551
583
  * 默认 TRTC 播放器:挂载即加入房间并拉流,连接由 TrtcSourceManager 统一复用。
@@ -724,4 +756,4 @@ declare function useManagedIviRuntime(config: IviManagedRuntimeConfig): IviRunti
724
756
 
725
757
  declare function useIviStageView(): IviStageViewContextValue;
726
758
 
727
- export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
759
+ export { EMPTY_RUNTIME_STATE, IVILivekitPlayer, type IVILivekitPlayerProps, IVIStageView, type IVIStageViewProps, IVISubtitleOverlay, type IVISubtitleOverlayProps, type IVISubtitleOverlayStyle, IVITrackSlot, type IVITrackSlotProps, IVITrtcPlayer, type IVITrtcPlayerProps, type IviFrontendClientConfig, IviFrontendSdk, type IviManagedRuntimeConfig, type IviManagedRuntimeLogCallback, type IviManagedRuntimeLogEntry, type IviManagedRuntimeLogLevel, type IviManagedRuntimeLogSource, type IviRuntimeConversationItem, type IviRuntimeConversationLifecycle, type IviRuntimeConversationStatus, IviRuntimeCoordinator, type IviRuntimeCoordinatorConfig, IviRuntimeDispatcher, type IviRuntimeDispatcherConfig, type IviRuntimeEventListener, type IviRuntimeLogCallback, type IviRuntimeLogEntry, type IviRuntimeLogLevel, type IviRuntimeSource, type IviRuntimeSourcePreloadState, type IviRuntimeState, type IviRuntimeStatus, type IviRuntimeStream, type IviRuntimeTrtcAIDenoiserMode, type IviRuntimeTrtcAIDenoiserOptions, type IviRuntimeTrtcEvent, type IviRuntimeTrtcEventListener, type IviRuntimeTrtcEventType, type IviRuntimeUserTextToResponseCallbacks, type IviRuntimeUserTextToResponseOptions, type IviRuntimeUserTextToResponseResult, type IviSourcePlaybackLivekit, type IviSourcePlaybackLivekitDescriptor, type IviStageSlotBinding, type IviStageViewContextValue, type IviSubtitleItem, type IviSubtitleRole, type IviUseSubtitlesOptions, LivekitSourceManager, TrtcSourceManager, isLivekitSourcePlayback, isReadyLivekitRuntimeSource, isSameLivekitConfig, useIviStageView, useIviSubtitles, useManagedIviRuntime, useRuntimeState };
package/dist/index.js CHANGED
@@ -1105,11 +1105,17 @@ var ConversationManager = class {
1105
1105
 
1106
1106
  // src/runtime/managers/trtc-source-manager.ts
1107
1107
  var TAG = "[IVI-TRTC]";
1108
+ var DEFAULT_DENOISER_OPTIONS = {
1109
+ enabled: true,
1110
+ mode: "normal"
1111
+ };
1108
1112
  var TrtcSourceManager = class {
1109
- constructor(onLog, onTrtcEvent) {
1113
+ constructor(onLog, onTrtcEvent, aiDenoiser) {
1110
1114
  this.onLog = onLog;
1111
1115
  this.onTrtcEvent = onTrtcEvent;
1116
+ this.aiDenoiser = aiDenoiser;
1112
1117
  this.sessions = /* @__PURE__ */ new Map();
1118
+ this.sourceSessionKeys = /* @__PURE__ */ new Map();
1113
1119
  this.listeners = /* @__PURE__ */ new Map();
1114
1120
  }
1115
1121
  /**
@@ -1118,15 +1124,16 @@ var TrtcSourceManager = class {
1118
1124
  * - 清理已不在 runtime 中的会话(释放资源)
1119
1125
  */
1120
1126
  syncRuntimeSources(sources) {
1121
- const existingIds = new Set(sources.keys());
1127
+ const trtcSourceIds = /* @__PURE__ */ new Set();
1122
1128
  sources.forEach((source, sourceId) => {
1123
1129
  if (!isRuntimeTrtcSource(source)) {
1124
1130
  return;
1125
1131
  }
1132
+ trtcSourceIds.add(sourceId);
1126
1133
  this.upsertSource(sourceId, source.playback.trtc);
1127
1134
  });
1128
- this.sessions.forEach((_session, sourceId) => {
1129
- if (!existingIds.has(sourceId)) {
1135
+ Array.from(this.sourceSessionKeys.keys()).forEach((sourceId) => {
1136
+ if (!trtcSourceIds.has(sourceId)) {
1130
1137
  this.removeSource(sourceId);
1131
1138
  }
1132
1139
  });
@@ -1135,22 +1142,29 @@ var TrtcSourceManager = class {
1135
1142
  * 按 sourceId 注册/更新 TRTC 配置。
1136
1143
  * 若配置发生变化,会先销毁旧会话再按新参数重建连接。
1137
1144
  */
1138
- upsertSource(sourceId, trtc) {
1139
- const existing = this.sessions.get(sourceId);
1140
- if (!existing) {
1141
- this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")} user=${getTrtcString(trtc, "user_id")}`);
1142
- const session2 = this.createSession(sourceId, trtc);
1143
- this.sessions.set(sourceId, session2);
1144
- void this.ensureConnected(session2);
1145
+ upsertSource(sourceId, trtc, aiDenoiser) {
1146
+ const existing = this.getSession(sourceId);
1147
+ if (existing && isSameTrtcConfig(existing.trtc, trtc) && isSameAIDenoiserOptions(existing.aiDenoiser, aiDenoiser)) {
1145
1148
  return;
1146
1149
  }
1147
- if (isSameTrtcConfig(existing.trtc, trtc)) {
1150
+ if (existing) {
1151
+ this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")}`);
1152
+ this.unlinkSourceFromSession(sourceId, existing);
1153
+ }
1154
+ const sessionKey = buildTrtcSessionKey(trtc, aiDenoiser);
1155
+ const reusable = this.sessions.get(sessionKey);
1156
+ if (reusable) {
1157
+ this.log("info", `\u590D\u7528\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")} user=${getTrtcString(trtc, "user_id")}`);
1158
+ reusable.sourceIds.add(sourceId);
1159
+ this.sourceSessionKeys.set(sourceId, sessionKey);
1160
+ this.emitSnapshot(sourceId, this.snapshotFromSession(reusable));
1161
+ void this.ensureConnected(reusable);
1148
1162
  return;
1149
1163
  }
1150
- this.log("info", `\u914D\u7F6E\u53D8\u66F4\uFF0C\u91CD\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")}`);
1151
- void this.disposeSession(existing);
1152
- const session = this.createSession(sourceId, trtc);
1153
- this.sessions.set(sourceId, session);
1164
+ this.log("info", `\u65B0\u5EFA\u4F1A\u8BDD source=${sourceId} room=${getTrtcString(trtc, "room_id")} user=${getTrtcString(trtc, "user_id")}`);
1165
+ const session = this.createSession(sourceId, trtc, aiDenoiser);
1166
+ this.sessions.set(session.sessionKey, session);
1167
+ this.sourceSessionKeys.set(sourceId, session.sessionKey);
1154
1168
  void this.ensureConnected(session);
1155
1169
  }
1156
1170
  /**
@@ -1158,13 +1172,12 @@ var TrtcSourceManager = class {
1158
1172
  * 该操作通常由 source.deleted 或会话重置触发。
1159
1173
  */
1160
1174
  removeSource(sourceId) {
1161
- const session = this.sessions.get(sourceId);
1175
+ const session = this.getSession(sourceId);
1162
1176
  if (!session) {
1163
1177
  return;
1164
1178
  }
1165
1179
  this.log("info", `\u79FB\u9664\u4F1A\u8BDD source=${sourceId}`);
1166
- this.sessions.delete(sourceId);
1167
- void this.disposeSession(session);
1180
+ this.unlinkSourceFromSession(sourceId, session);
1168
1181
  this.emitSnapshot(sourceId, {
1169
1182
  status: "idle"
1170
1183
  });
@@ -1174,6 +1187,7 @@ var TrtcSourceManager = class {
1174
1187
  this.log("info", `\u91CD\u7F6E\u5168\u90E8\u4F1A\u8BDD count=${this.sessions.size}`);
1175
1188
  const sessions = Array.from(this.sessions.values());
1176
1189
  this.sessions.clear();
1190
+ this.sourceSessionKeys.clear();
1177
1191
  sessions.forEach((session) => {
1178
1192
  void this.disposeSession(session);
1179
1193
  });
@@ -1196,7 +1210,7 @@ var TrtcSourceManager = class {
1196
1210
  };
1197
1211
  }
1198
1212
  getSnapshot(sourceId) {
1199
- const session = this.sessions.get(sourceId);
1213
+ const session = this.getSession(sourceId);
1200
1214
  if (!session) {
1201
1215
  return {
1202
1216
  status: "idle"
@@ -1211,7 +1225,7 @@ var TrtcSourceManager = class {
1211
1225
  * 检查指定 source 的 TRTC 会话是否曾收到过 REMOTE_VIDEO_AVAILABLE 事件。
1212
1226
  */
1213
1227
  hasRemoteVideoAvailable(sourceId) {
1214
- const session = this.sessions.get(sourceId);
1228
+ const session = this.getSession(sourceId);
1215
1229
  return session?.hasEverReceivedRemoteVideo ?? false;
1216
1230
  }
1217
1231
  /**
@@ -1220,7 +1234,7 @@ var TrtcSourceManager = class {
1220
1234
  * - 若会话被销毁或超时,返回 false。
1221
1235
  */
1222
1236
  waitForRemoteVideoAvailable(sourceId, timeoutMs = 3e4) {
1223
- const session = this.sessions.get(sourceId);
1237
+ const session = this.getSession(sourceId);
1224
1238
  if (!session) return Promise.resolve(false);
1225
1239
  if (session.hasEverReceivedRemoteVideo) return Promise.resolve(true);
1226
1240
  return new Promise((resolve) => {
@@ -1248,11 +1262,12 @@ var TrtcSourceManager = class {
1248
1262
  * - 应用全局音频策略(根据所有 view 的 muted 汇总)
1249
1263
  */
1250
1264
  async attachView(sourceId, viewId, container, muted) {
1251
- const session = this.sessions.get(sourceId);
1265
+ const session = this.getSession(sourceId);
1252
1266
  if (!session) {
1253
1267
  throw new Error(`TRTC source session not found: ${sourceId}`);
1254
1268
  }
1255
1269
  session.views.set(viewId, {
1270
+ sourceId,
1256
1271
  container,
1257
1272
  muted,
1258
1273
  startedVideoKeys: /* @__PURE__ */ new Set(),
@@ -1267,15 +1282,18 @@ var TrtcSourceManager = class {
1267
1282
  await this.applyAudioPolicy(session);
1268
1283
  }
1269
1284
  detachView(sourceId, viewId) {
1270
- const session = this.sessions.get(sourceId);
1285
+ const session = this.getSession(sourceId);
1271
1286
  if (!session) {
1272
1287
  return;
1273
1288
  }
1274
- session.views.delete(viewId);
1289
+ const binding = session.views.get(viewId);
1290
+ if (binding?.sourceId === sourceId) {
1291
+ session.views.delete(viewId);
1292
+ }
1275
1293
  void this.applyAudioPolicy(session);
1276
1294
  }
1277
1295
  updateViewMuted(sourceId, viewId, muted) {
1278
- const session = this.sessions.get(sourceId);
1296
+ const session = this.getSession(sourceId);
1279
1297
  if (!session) {
1280
1298
  return;
1281
1299
  }
@@ -1286,10 +1304,45 @@ var TrtcSourceManager = class {
1286
1304
  binding.muted = muted;
1287
1305
  void this.applyAudioPolicy(session);
1288
1306
  }
1289
- createSession(sourceId, trtc) {
1307
+ unlinkSourceFromSession(sourceId, session) {
1308
+ session.sourceIds.delete(sourceId);
1309
+ this.sourceSessionKeys.delete(sourceId);
1310
+ session.views.forEach((binding, viewId) => {
1311
+ if (binding.sourceId === sourceId) {
1312
+ session.views.delete(viewId);
1313
+ }
1314
+ });
1315
+ if (session.sourceIds.size > 0) {
1316
+ session.sourceId = session.sourceIds.values().next().value ?? session.sourceId;
1317
+ void this.applyAudioPolicy(session);
1318
+ return;
1319
+ }
1320
+ this.sessions.delete(session.sessionKey);
1321
+ void this.disposeSession(session);
1322
+ }
1323
+ getSession(sourceId) {
1324
+ const sessionKey = this.sourceSessionKeys.get(sourceId);
1325
+ return sessionKey ? this.sessions.get(sessionKey) : void 0;
1326
+ }
1327
+ snapshotFromSession(session) {
1328
+ return {
1329
+ status: session.status,
1330
+ error: session.error
1331
+ };
1332
+ }
1333
+ emitSessionSnapshot(session, snapshot) {
1334
+ session.sourceIds.forEach((sourceId) => this.emitSnapshot(sourceId, snapshot));
1335
+ }
1336
+ emitSessionTrtcEvent(session, type, rawType, payload) {
1337
+ session.sourceIds.forEach((sourceId) => this.emitTrtcEvent(sourceId, type, rawType, payload));
1338
+ }
1339
+ createSession(sourceId, trtc, aiDenoiser) {
1290
1340
  return {
1341
+ sessionKey: buildTrtcSessionKey(trtc, aiDenoiser),
1291
1342
  sourceId,
1343
+ sourceIds: /* @__PURE__ */ new Set([sourceId]),
1292
1344
  trtc,
1345
+ aiDenoiser,
1293
1346
  TRTC: null,
1294
1347
  client: null,
1295
1348
  connectPromise: null,
@@ -1315,7 +1368,7 @@ var TrtcSourceManager = class {
1315
1368
  }
1316
1369
  session.status = "connecting";
1317
1370
  session.error = void 0;
1318
- this.emitSnapshot(session.sourceId, {
1371
+ this.emitSessionSnapshot(session, {
1319
1372
  status: session.status
1320
1373
  });
1321
1374
  session.connectPromise = (async () => {
@@ -1344,7 +1397,7 @@ var TrtcSourceManager = class {
1344
1397
  session.views.forEach((binding) => {
1345
1398
  void this.startRemoteVideoForBinding(client, event.userId, event.streamType, binding, remoteVideoKey);
1346
1399
  });
1347
- this.emitTrtcEvent(session.sourceId, "remote_video_available", TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, event);
1400
+ this.emitSessionTrtcEvent(session, "remote_video_available", TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, event);
1348
1401
  };
1349
1402
  const onRemoteVideoUnavailable = (event) => {
1350
1403
  this.log("info", `\u8FDC\u7AEF\u89C6\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId} streamType=${event.streamType}`);
@@ -1358,19 +1411,19 @@ var TrtcSourceManager = class {
1358
1411
  userId: event.userId,
1359
1412
  streamType: event.streamType
1360
1413
  }).catch(() => void 0);
1361
- this.emitTrtcEvent(session.sourceId, "remote_video_unavailable", TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, event);
1414
+ this.emitSessionTrtcEvent(session, "remote_video_unavailable", TRTC.EVENT.REMOTE_VIDEO_UNAVAILABLE, event);
1362
1415
  };
1363
1416
  const onRemoteAudioAvailable = (event) => {
1364
1417
  this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
1365
1418
  session.remoteAudioUsers.add(event.userId);
1366
1419
  void this.applyAudioPolicy(session);
1367
- this.emitTrtcEvent(session.sourceId, "remote_audio_available", TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event);
1420
+ this.emitSessionTrtcEvent(session, "remote_audio_available", TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, event);
1368
1421
  };
1369
1422
  const onRemoteAudioUnavailable = (event) => {
1370
1423
  this.log("info", `\u8FDC\u7AEF\u97F3\u9891\u4E0D\u53EF\u7528 source=${session.sourceId} userId=${event.userId}`);
1371
1424
  session.remoteAudioUsers.delete(event.userId);
1372
1425
  void this.applyAudioPolicy(session);
1373
- this.emitTrtcEvent(session.sourceId, "remote_audio_unavailable", TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, event);
1426
+ this.emitSessionTrtcEvent(session, "remote_audio_unavailable", TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, event);
1374
1427
  };
1375
1428
  session.onRemoteVideoAvailable = onRemoteVideoAvailable;
1376
1429
  session.onRemoteVideoUnavailable = onRemoteVideoUnavailable;
@@ -1395,7 +1448,8 @@ var TrtcSourceManager = class {
1395
1448
  session.status = "connected";
1396
1449
  session.error = void 0;
1397
1450
  this.log("info", `\u8FDB\u623F\u6210\u529F source=${session.sourceId} room=${roomId}`);
1398
- this.emitSnapshot(session.sourceId, {
1451
+ await this.applyAIDenoiserPolicy(session, client, sdkAppId, userId, userSig);
1452
+ this.emitSessionSnapshot(session, {
1399
1453
  status: session.status
1400
1454
  });
1401
1455
  await this.applyAudioPolicy(session);
@@ -1403,7 +1457,7 @@ var TrtcSourceManager = class {
1403
1457
  session.status = "error";
1404
1458
  session.error = error instanceof Error ? error.message : String(error);
1405
1459
  this.log("error", `\u8FDE\u63A5\u5931\u8D25 source=${session.sourceId} error=${session.error}`);
1406
- this.emitSnapshot(session.sourceId, {
1460
+ this.emitSessionSnapshot(session, {
1407
1461
  status: session.status,
1408
1462
  error: session.error
1409
1463
  });
@@ -1480,6 +1534,25 @@ var TrtcSourceManager = class {
1480
1534
  await client.muteRemoteAudio(userId, false);
1481
1535
  }
1482
1536
  }
1537
+ async applyAIDenoiserPolicy(session, client, sdkAppId, userId, userSig) {
1538
+ const options = resolveAIDenoiserOptions(session.aiDenoiser ?? this.aiDenoiser);
1539
+ if (!options.enabled) {
1540
+ this.log("info", `TRTC AIDenoiser \u5DF2\u5173\u95ED source=${session.sourceId}`);
1541
+ return;
1542
+ }
1543
+ try {
1544
+ await client.startPlugin("AIDenoiser", {
1545
+ sdkAppId,
1546
+ userId,
1547
+ userSig,
1548
+ mode: getAIDenoiserModeValue(options.mode),
1549
+ ...options.assetsPath ? { assetsPath: options.assetsPath } : {}
1550
+ });
1551
+ this.log("info", `TRTC AIDenoiser \u5DF2\u5F00\u542F source=${session.sourceId} mode=${options.mode}`);
1552
+ } catch (error) {
1553
+ this.log("warn", `TRTC AIDenoiser \u5F00\u542F\u5931\u8D25 source=${session.sourceId}`, error);
1554
+ }
1555
+ }
1483
1556
  /**
1484
1557
  * 销毁单个 source 会话:
1485
1558
  * 解绑事件、停止远端视频、退出房间并销毁客户端。
@@ -1561,6 +1634,25 @@ function isRuntimeTrtcSource(source) {
1561
1634
  function isSameTrtcConfig(a, b) {
1562
1635
  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;
1563
1636
  }
1637
+ function buildTrtcSessionKey(trtc, aiDenoiser) {
1638
+ const denoiser = resolveAIDenoiserOptions(aiDenoiser);
1639
+ return JSON.stringify({
1640
+ appId: trtc.app_id,
1641
+ roomId: trtc.room_id,
1642
+ userId: trtc.user_id,
1643
+ userSig: trtc.user_sig,
1644
+ aiDenoiser: {
1645
+ enabled: denoiser.enabled,
1646
+ mode: denoiser.mode,
1647
+ assetsPath: denoiser.assetsPath
1648
+ }
1649
+ });
1650
+ }
1651
+ function isSameAIDenoiserOptions(a, b) {
1652
+ const left = resolveAIDenoiserOptions(a);
1653
+ const right = resolveAIDenoiserOptions(b);
1654
+ return left.enabled === right.enabled && left.mode === right.mode && left.assetsPath === right.assetsPath;
1655
+ }
1564
1656
  function shouldUseStringRoomId(roomId) {
1565
1657
  if (!/^\d+$/.test(roomId)) {
1566
1658
  return true;
@@ -1572,6 +1664,26 @@ function getTrtcString(trtc, key) {
1572
1664
  const value = trtc[key];
1573
1665
  return typeof value === "string" ? value : "";
1574
1666
  }
1667
+ function resolveAIDenoiserOptions(options) {
1668
+ if (options === false) {
1669
+ return {
1670
+ ...DEFAULT_DENOISER_OPTIONS,
1671
+ enabled: false
1672
+ };
1673
+ }
1674
+ if (options === true || options === void 0) {
1675
+ return DEFAULT_DENOISER_OPTIONS;
1676
+ }
1677
+ return {
1678
+ ...DEFAULT_DENOISER_OPTIONS,
1679
+ ...options,
1680
+ enabled: options.enabled ?? DEFAULT_DENOISER_OPTIONS.enabled,
1681
+ mode: options.mode ?? DEFAULT_DENOISER_OPTIONS.mode
1682
+ };
1683
+ }
1684
+ function getAIDenoiserModeValue(mode) {
1685
+ return mode === "far_field_reduction" ? 1 : 0;
1686
+ }
1575
1687
  function buildRemoteVideoKey(userId, streamType) {
1576
1688
  return `${userId}::${streamType}`;
1577
1689
  }
@@ -2074,7 +2186,8 @@ var IviRuntimeCoordinator = class {
2074
2186
  };
2075
2187
  this.trtcSourceManager = new TrtcSourceManager(
2076
2188
  this.config.onLog,
2077
- (event) => this.emitTrtcEvent(event)
2189
+ (event) => this.emitTrtcEvent(event),
2190
+ this.config.trtcAIDenoiser
2078
2191
  );
2079
2192
  this.livekitSourceManager = new LivekitSourceManager(this.config.onLog);
2080
2193
  this.sessionHandler = new SessionEventHandler(this.sessionManager, {
@@ -3154,7 +3267,8 @@ function IVITrtcPlayer(props) {
3154
3267
  style,
3155
3268
  loadingFallback = null,
3156
3269
  errorFallback = null,
3157
- muted = false
3270
+ muted = false,
3271
+ trtcAIDenoiser
3158
3272
  } = props;
3159
3273
  const containerRef = useRef(null);
3160
3274
  const viewIdRef = useRef(`trtc-view-${Math.random().toString(36).slice(2, 10)}`);
@@ -3175,7 +3289,7 @@ function IVITrtcPlayer(props) {
3175
3289
  }
3176
3290
  let disposed = false;
3177
3291
  if (shouldManageSourceLifecycle) {
3178
- manager.upsertSource(resolvedSourceId, trtc);
3292
+ manager.upsertSource(resolvedSourceId, trtc, trtcAIDenoiser);
3179
3293
  }
3180
3294
  const unsubscribe = manager.subscribe(resolvedSourceId, (snapshot) => {
3181
3295
  if (disposed) {
@@ -3206,7 +3320,8 @@ function IVITrtcPlayer(props) {
3206
3320
  trtc.app_id,
3207
3321
  trtc.room_id,
3208
3322
  trtc.user_id,
3209
- trtc.user_sig
3323
+ trtc.user_sig,
3324
+ trtcAIDenoiser
3210
3325
  ]);
3211
3326
  useEffect(() => {
3212
3327
  manager.updateViewMuted(resolvedSourceId, viewIdRef.current, muted);