import AudioMixObserver from '../audiomixobserver/AudioMixObserver' ; import BrowserBehavior from '../browserbehavior/BrowserBehavior' ; import DefaultBrowserBehavior from '../browserbehavior/DefaultBrowserBehavior' ; import Logger from '../logger/Logger' ; import AsyncScheduler from '../scheduler/AsyncScheduler' ; import AudioMixController from './AudioMixController' ; internal interface AudioElementWithSinkId extends HTMLAudioElement { sinkId : string ; setSinkId : ( id : string ) => void ; } export default class DefaultAudioMixController implements AudioMixController { private audioDevice : MediaDeviceInfo | null = null ; private audioElement : HTMLAudioElement | null = null ; private audioStream : MediaStream | null = null ; private browserBehavior : BrowserBehavior = new DefaultBrowserBehavior ( ) ; private observers : Set < AudioMixObserver > = new Set < AudioMixObserver > ( ) ; constructor ( private logger ? ) { } async bindAudioElement ( element ) { if ( ! element ) { throw new Error ( `Cannot bind audio element: ${ element }` ) ; } this . audioElement = element ; this . audioElement . autoplay = true ; return this . bindAudioMix ( ) ; } unbindAudioElement ( ) { if ( ! this . audioElement ) { return ; } this . audioElement . srcObject = null ; this . audioElement = null ; this . forEachObserver ( ( observer ) => { if ( this . audioStream ) { observer . meetingAudioStreamBecameInactive ( this . audioStream ) ; } } ) ; } async bindAudioStream ( stream ) { if ( ! stream ) { return ; } this . audioStream = stream ; try { await this . bindAudioMix ( ) ; } catch ( error ) { if ( this . logger ) { this . logger . warn ( `Failed to bind audio stream: ${ error }` ) ; } } } async bindAudioDevice ( device ) { /**
         * Throw error if browser doesn't even support setSinkId
         * Read more: https://caniuse.com/?search=setSinkId
         */ if ( device && ! this . browserBehavior . supportsSetSinkId ( ) ) { throw new Error ( 'Cannot select audio output device. This browser does not support setSinkId.' ) ; } this . audioDevice = device ; return this . bindAudioMix ( ) ; } private forEachObserver ( observerFunc ) { for ( const observer of this . observers ) { AsyncScheduler . nextTick ( ( ) => { observerFunc ( observer ) ; } ) ; } } private async bindAudioMix ( ) { if ( ! this . audioElement ) { return ; } const previousStream = this . audioElement . srcObject as MediaStream ; if ( this . audioStream ) { this . audioElement . srcObject = this . audioStream ; } if ( previousStream !== this . audioStream ) { this . forEachObserver ( ( observer ) => { if ( previousStream ) { observer . meetingAudioStreamBecameInactive ( previousStream ) ; } if ( this . audioStream ) { observer . meetingAudioStreamBecameActive ( this . audioStream ) ; } } ) ; } const shouldSetSinkId = this . audioDevice ?. deviceId !== ( this . audioElement as AudioElementWithSinkId ) . sinkId ; if ( shouldSetSinkId && typeof ( this . audioElement as AudioElementWithSinkId ) . sinkId === 'undefined' ) { throw new Error ( 'Cannot select audio output device. This browser does not support setSinkId.' ) ; } const newSinkId = this . audioDevice ? this . audioDevice . deviceId : '' ; const oldSinkId = ( this . audioElement as AudioElementWithSinkId ) . sinkId ; if ( newSinkId === oldSinkId ) { return ; } const existingAudioElement = this . audioElement as AudioElementWithSinkId ; const existingStream = this . audioStream ; if ( this . browserBehavior . hasChromiumWebRTC ( ) ) { existingAudioElement . srcObject = null ; } if ( shouldSetSinkId ) { try { await existingAudioElement . setSinkId ( newSinkId ) ; } catch ( error ) { this . logger ?. error ( `Failed to set sinkId for audio element: ${ error }` ) ; throw error ; } } if ( this . browserBehavior . hasChromiumWebRTC ( ) ) { existingAudioElement . srcObject = existingStream ; } } async getCurrentMeetingAudioStream ( ) { return this . audioStream ; } async addAudioMixObserver ( observer ) { this . observers . add ( observer ) ; } async removeAudioMixObserver ( observer ) { this . observers . delete ( observer ) ; } }