From 4dc17799e149286d197b667d9612bdfcf87c3674 Mon Sep 17 00:00:00 2001 From: Yassir Bisteni Date: Fri, 3 Oct 2025 08:31:15 -0600 Subject: [PATCH] Added Voice Isolation support --- .../AudioEffects/AudioEffectsContainer.js | 119 +++++++++++++++++- Project/src/MakeCall/Login.js | 18 ++- Project/src/MakeCall/MakeCall.js | 8 ++ 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js b/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js index e2cb87e..b7c9397 100644 --- a/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js +++ b/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js @@ -2,7 +2,8 @@ import React from 'react'; import { Features, LocalAudioStream } from '@azure/communication-calling'; import { EchoCancellationEffect, - DeepNoiseSuppressionEffect + DeepNoiseSuppressionEffect, + VoiceIsolationEffect } from '@azure/communication-calling-effects'; import { Dropdown, PrimaryButton } from '@fluentui/react'; @@ -41,10 +42,17 @@ export default class AudioEffectsContainer extends React.Component { noiseSuppressionList: [], currentSelected: undefined }, + voiceIsolation: { + startLoading: false, + stopLoading: false, + voiceIsolationList: [], + currentSelected: undefined + }, activeEffects: { autoGainControl: [], echoCancellation: [], - noiseSuppression: [] + noiseSuppression: [], + voiceIsolation: [] } }; @@ -123,6 +131,7 @@ export default class AudioEffectsContainer extends React.Component { const autoGainControlList = []; const echoCancellationList = []; const noiseSuppressionList = []; + const voiceIsolationList = []; if (this.localAudioStreamFeatureApi) { if (await this.localAudioStreamFeatureApi.isSupported('BrowserAutoGainControl')) { @@ -167,6 +176,15 @@ export default class AudioEffectsContainer extends React.Component { }); } + const voiceIsolation = new VoiceIsolationEffect(); + if (await this.localAudioStreamFeatureApi.isSupported(voiceIsolation)) { + supported.push(voiceIsolation); + voiceIsolationList.push({ + key: voiceIsolation.name, + text: 'Voice Isolation' + }); + } + this.setState({ supportedAudioEffects: [ ...supported ], supportedAudioEffectsPopulated: true, @@ -182,10 +200,15 @@ export default class AudioEffectsContainer extends React.Component { ...this.state.noiseSuppression, noiseSuppressionList }, + voiceIsolation: { + ...this.state.voiceIsolation, + voiceIsolationList + }, activeEffects: { autoGainControl: this.localAudioStreamFeatureApi?.activeEffects?.autoGainControl, echoCancellation: this.localAudioStreamFeatureApi?.activeEffects?.echoCancellation, - noiseSuppression: this.localAudioStreamFeatureApi?.activeEffects?.noiseSuppression + noiseSuppression: this.localAudioStreamFeatureApi?.activeEffects?.noiseSuppression, + voiceIsolation: this.localAudioStreamFeatureApi?.activeEffects?.voiceIsolation } }); } @@ -377,6 +400,64 @@ export default class AudioEffectsContainer extends React.Component { } /* ------------ NS control functions - end ---------------- */ + /* ------------ VI control functions - start ---------------- */ + viSelectionChanged(e, item) { + const effect = this.findEffectFromSupportedList(item.key); + if (effect) { + this.setState({ + voiceIsolation: { + ...this.state.voiceIsolation, + currentSelected: effect + } + }); + } + } + + async startVi() { + this.setState({ + voiceIsolation: { + ...this.state.voiceIsolation, + startLoading: true + } + }); + + if (this.localAudioStreamFeatureApi) { + await this.localAudioStreamFeatureApi.startEffects({ + voiceIsolation: this.state.voiceIsolation.currentSelected + }); + } + + this.setState({ + voiceIsolation: { + ...this.state.voiceIsolation, + startLoading: false + } + }); + } + + async stopVi() { + this.setState({ + voiceIsolation: { + ...this.state.voiceIsolation, + stopLoading: true + } + }); + + if (this.localAudioStreamFeatureApi) { + await this.localAudioStreamFeatureApi.stopEffects({ + voiceIsolation: true + }); + } + + this.setState({ + voiceIsolation: { + ...this.state.voiceIsolation, + stopLoading: false + } + }); + } + /* ------------ VI control functions - end ---------------- */ + render() { return ( <> @@ -403,6 +484,11 @@ export default class AudioEffectsContainer extends React.Component { {this.state.activeEffects.noiseSuppression[0]} } + {this.state.activeEffects.voiceIsolation?.length > 0 && +
+ {this.state.activeEffects.voiceIsolation[0]} +
+ }
@@ -484,6 +570,33 @@ export default class AudioEffectsContainer extends React.Component {
+ +
+
+ this.viSelectionChanged(e, item)} + options={this.state.voiceIsolation.voiceIsolationList} + placeholder={'Select an option'} + styles={{ dropdown: { width: 300, color: 'black' }, label: { color: 'white' } }} + /> +
+
+ this.startNs()} + > + {this.state.noiseSuppression.startLoading ? : 'Start NS'} + + + this.stopNs()} + > + {this.state.noiseSuppression.stopLoading ? : 'Stop NS'} + +
+
:
diff --git a/Project/src/MakeCall/Login.js b/Project/src/MakeCall/Login.js index e3c032e..69dcacf 100644 --- a/Project/src/MakeCall/Login.js +++ b/Project/src/MakeCall/Login.js @@ -49,7 +49,8 @@ export default class Login extends React.Component { }, isTeamsUser: false, isEntraUser: false, - isJoinOnlyToken: false + isJoinOnlyToken: false, + headsetEnhancement: false, } } @@ -121,7 +122,8 @@ export default class Login extends React.Component { proxy: this.state.proxy, customTurn: this.state.customTurn, isTeamsUser: this.state.isTeamsUser, - isEntraUser: this.state.isEntraUser + isEntraUser: this.state.isEntraUser, + headsetEnhancement: this.state.headsetEnhancement }); } console.log('Login response: ', this.userDetailsResponse); @@ -211,7 +213,8 @@ export default class Login extends React.Component { displayName: this.displayName, clientTag:this.clientTag, proxy: this.state.proxy, - customTurn: this.state.customTurn + customTurn: this.state.customTurn, + headsetEnhancement: this.state.headsetEnhancement }); this._callAgentInitPromise = new Promise((resolve) => { this._callAgentInitPromiseResolve = resolve }); await this._callAgentInitPromise; @@ -705,6 +708,15 @@ const isSupportedEnvironment = this.environmentInfo.isSupportedEnvironment; onChange={(e, isChecked) => this.setState({isJoinOnlyToken: isChecked})} />
+
+
+ this.setState({headsetEnhancement: isChecked})} /> +
+