import { AGCOptions , VoiceFocusAudioWorkletNode } from '../../libs/voicefocus/types' ; import { NodeArguments , VoiceFocus } from '../../libs/voicefocus/voicefocus' ; import AudioMixObserver from '../audiomixobserver/AudioMixObserver' ; import AudioVideoFacade from '../audiovideofacade/AudioVideoFacade' ; import DefaultBrowserBehavior from '../browserbehavior/DefaultBrowserBehavior' ; import AudioNodeSubgraph from '../devicecontroller/AudioNodeSubgraph' ; import type AudioTransformDevice from '../devicecontroller/AudioTransformDevice' ; import type Device from '../devicecontroller/Device' ; import VoiceFocusTransformDeviceDelegate from './VoiceFocusTransformDeviceDelegate' ; import VoiceFocusTransformDeviceObserver from './VoiceFocusTransformDeviceObserver' ; /**
 * A device that augments a  Device to apply Amazon Voice Focus
 * noise suppression to an audio input.
  class VoiceFocusTransformDevice implements AudioTransformDevice , AudioMixObserver { internal constructor ( private device , private voiceFocus , private delegate , private nodeOptions , private failed = false , private node = undefined , private browserBehavior = new DefaultBrowserBehavior ( ) , /** farEndStreams` maps from a stream that could cause echo or interfere with double talkto an `AudioSourceNode` that we use to mix multiple such streams.*/ private farEndStreamToAudioSourceNode = new Map ( ) , /** mixDestNode is the Audio Destination Node where farEndStreams got mixed into one stream.*/ private mixDestNode = undefined , /** mixSourceNode is the Audio Source Node where the stream out of mixDestNode got transfered into Audio Worklet Node for processing.*/ private mixSourceNode = undefined ) { } /**
     * Return the inner device as provided during construction, or updated via
     *  VoiceFocusTransformDevice . chooseNewInnerDevice . Do not confuse
     * this method with  VoiceFocusTransformDevice . intrinsicDevice .
      getInnerDevice ( ) { return this . device ; } param muted async mute ( muted ) { if ( ! this . node ) { return ; } if ( muted ) { await this . node . disable ( ) ; } else { await this . node . enable ( ) ; } } /**
     * Dispose of the inner workings of the transform device. After this method is called
     * you will need to create a new device to use Amazon Voice Focus again.
     */ async stop ( ) { if ( ! this . node ) { return ; } this . node . disconnect ( ) ; await this . node . stop ( ) ; } /**
     * If you wish to choose a different inner device, but continue to use Amazon Voice Focus, you
     * can use this method to efficiently create a new device that will reuse
     * the same internal state. Only one of the two devices can be used at a time: switch
     * between them using  DeviceController . chooseAudioInputDevice .
     *
     * If the same device is passed as is currently in use, `this` is returned.
     *
     *  param inner async chooseNewInnerDevice ( inner ) { if ( this . device === inner && ! isDefaultDevice ( inner ) ) { return this ; } return new VoiceFocusTransformDevice ( inner , this . voiceFocus , this . delegate , this . nodeOptions , this . failed , this . node , this . browserBehavior , this . farEndStreamToAudioSourceNode , this . mixDestNode , this . mixSourceNode ) ; } async intrinsicDevice ( ) { if ( this . failed ) { return this . device ; } const isUsingES = this . nodeOptions . es ; const trackConstraints = { echoCancellation : ! isUsingES , googEchoCancellation : ! isUsingES , googEchoCancellation2 : ! isUsingES , noiseSuppression : false , googNoiseSuppression : false , googHighpassFilter : false , googNoiseSuppression2 : false , } ; let useBuiltInAGC ; if ( this . nodeOptions && this . nodeOptions . agc !== undefined ) { useBuiltInAGC = this . nodeOptions . agc . useBuiltInAGC ; } else { useBuiltInAGC = true ; } trackConstraints . autoGainControl = useBuiltInAGC ; trackConstraints . googAutoGainControl = useBuiltInAGC ; trackConstraints . googAutoGainControl2 = useBuiltInAGC ; if ( ! this . device ) { return trackConstraints ; } if ( typeof this . device === 'string' ) { if ( this . browserBehavior . requiresNoExactMediaStreamConstraints ( ) ) { trackConstraints . deviceId = this . device ; } else { trackConstraints . deviceId = { exact : this . device } ; } return trackConstraints ; } if ( ( this . device as MediaStream ) . id ) { return this . device ; } return { ... this . device , ... trackConstraints , } ; } async createAudioNode ( context ) { if ( this . node ?. context === context ) { return { start : this . node , end : this . node , } ; } const agc = { useVoiceFocusAGC : false } ; const options = { enabled : true , agc , ... this . nodeOptions , } ; try { this . node ?. disconnect ( ) ; this . node = await this . voiceFocus . createNode ( context , options ) ; if ( this . nodeOptions . es ) { this . mixDestNode = new MediaStreamAudioDestinationNode ( context , { channelCount : 1 , channelCountMode : 'explicit' , } ) ; for ( const stream of this . farEndStreamToAudioSourceNode . keys ( ) ) { this . assignFarEndStreamToAudioSourceNode ( stream ) ; } this . createMixSourceNode ( ) ; } const start = this . node ; const end = this . node ; return { start , end } ; } catch ( e ) { this . failed = true ; this . delegate . onFallback ( this , e ) ; throw e ; } } async observeMeetingAudio ( audioVideo ) { if ( ! this . nodeOptions . es ) { return ; } audioVideo . addAudioMixObserver ( this ) ; const stream = await audioVideo . getCurrentMeetingAudioStream ( ) ; if ( stream ) { this . addFarEndStream ( stream ) ; } } async unObserveMeetingAudio ( audioVideo ) { if ( ! this . nodeOptions . es ) { return ; } audioVideo . removeAudioMixObserver ( this ) ; const stream = await audioVideo . getCurrentMeetingAudioStream ( ) ; if ( stream ) { this . removeFarendStream ( stream ) ; } } /**
     * Add an observer to receive notifications about Amazon Voice Focus lifecycle events.
     * See  VoiceFocusTransformDeviceObserver for details.
     * If the observer has already been added, this method call has no effect.
      addObserver ( observer ) { this . delegate . addObserver ( observer ) ; } /**
     * Remove an existing observer. If the observer has not been previously  {@link
     * VoiceFocusTransformDevice.addObserver|added} , this method call has no effect.
      removeObserver ( observer ) { this . delegate . removeObserver ( observer ) ; } async addFarEndStream ( activeStream ) { if ( ! this . nodeOptions . es || ! activeStream || this . farEndStreamToAudioSourceNode . has ( activeStream ) ) { return ; } if ( this . node ) { this . assignFarEndStreamToAudioSourceNode ( activeStream ) ; } else { this . farEndStreamToAudioSourceNode . set ( activeStream , null ) ; } } async removeFarendStream ( inactiveStream ) { this . farEndStreamToAudioSourceNode . get ( inactiveStream ) ?. disconnect ( ) ; this . farEndStreamToAudioSourceNode . delete ( inactiveStream ) ; } async meetingAudioStreamBecameActive ( activeStream ) { this . addFarEndStream ( activeStream ) ; } async meetingAudioStreamBecameInactive ( inactiveStream ) { this . removeFarendStream ( inactiveStream ) ; } private assignFarEndStreamToAudioSourceNode ( streamToAdd ) { const streamNodeToAdd = ( this . node . context as AudioContext ) . createMediaStreamSource ( streamToAdd ) ; streamNodeToAdd . channelCount = 1 ; streamNodeToAdd . channelCountMode = 'explicit' ; this . farEndStreamToAudioSourceNode . set ( streamToAdd , streamNodeToAdd ) ; streamNodeToAdd . connect ( this . mixDestNode , 0 ) ; } private createMixSourceNode ( ) { this . mixSourceNode = ( this . node . context as AudioContext ) . createMediaStreamSource ( this . mixDestNode . stream ) ; this . mixSourceNode . channelCount = 1 ; this . mixSourceNode . channelCountMode = 'explicit' ; this . mixSourceNode . connect ( this . node , 0 , 1 ) ; } } function isDefaultDevice ( device ) { if ( device === 'default' ) { return true ; } if ( ! device || typeof device !== 'object' ) { return false ; } if ( 'deviceId' in device && device . deviceId === 'default' ) { return true ; } if ( 'id' in device && device . id === 'default' ) { return true ; } return false ; } export default VoiceFocusTransformDevice ;