import AsyncScheduler from '../scheduler/AsyncScheduler' ; import IntervalScheduler from '../scheduler/IntervalScheduler' ; type ListenerFunction = ( type : string , listener : EventListenerOrEventListenerObject , options ? : boolean | AddEventListenerOptions ) => any ; export default class MediaDeviceProxyHandler implements ProxyHandler < MediaDevices > { private static INTERVAL_MS : number = 1000 ; private scheduler : IntervalScheduler | null = null ; private devices : MediaDeviceInfo [ ] | null = null ; private deviceChangeListeners : Set < EventListenerOrEventListenerObject > = new Set ( ) ; get = ( target , property , receiver ) : any => { if ( ! Reflect . has ( target , property ) ) { return undefined ; } if ( ! ( 'ondevicechange' in navigator . mediaDevices ) ) { if ( property === 'addEventListener' ) { return this . patchAddEventListener ( target , property , receiver ) ; } else if ( property === 'removeEventListener' ) { return this . patchRemoveEventListener ( target , property , receiver ) ; } } const value = Reflect . get ( target , property , receiver ) ; return typeof value === 'function' ? value . bind ( target ) : value ; } ; private patchAddEventListener = ( target , property , receiver ) : ListenerFunction => { const value = Reflect . get ( target , property , receiver ) ; return ( type , listener , options ? ) => { if ( type === 'devicechange' ) { this . deviceChangeListeners . add ( listener ) ; if ( ! this . scheduler ) { this . scheduler = new IntervalScheduler ( MediaDeviceProxyHandler . INTERVAL_MS ) ; this . scheduler . start ( this . pollDeviceLists ) ; } } else { return Reflect . apply ( value , target , [ type , listener , options ] ) ; } } ; } ; private patchRemoveEventListener = ( target , property , receiver ) : ListenerFunction => { const value = Reflect . get ( target , property , receiver ) ; return ( type , listener , options ? ) => { if ( type === 'devicechange' ) { this . deviceChangeListeners . delete ( listener ) ; if ( this . deviceChangeListeners . size === 0 && this . scheduler ) { this . scheduler . stop ( ) ; this . scheduler = null ; } } else { return Reflect . apply ( value , target , [ type , listener , options ] ) ; } } ; } ; private pollDeviceLists = async ( ) : Promise < void > => { const newDevices = await this . sortedDeviceList ( ) ; if ( this . devices ) { const changed = newDevices . length !== this . devices . length || newDevices . some ( ( device , index ) => { return device . deviceId !== this . devices [ index ] . deviceId ; } ) ; if ( changed ) { this . handleDeviceChangeEvent ( ) ; } } this . devices = newDevices ; } ; private async sortedDeviceList ( ) { const newDevices = await navigator . mediaDevices . enumerateDevices ( ) ; return newDevices . sort ( ( device1 , device2 ) => { if ( device1 . deviceId < device2 . deviceId ) { return 1 ; } if ( device1 . deviceId > device2 . deviceId ) { return - 1 ; } return 0 ; } ) ; } private handleDeviceChangeEvent ( ) { for ( const listener of this . deviceChangeListeners ) { AsyncScheduler . nextTick ( ( ) => { if ( this . deviceChangeListeners . has ( listener ) ) { const event = new Event ( 'devicechange' ) ; if ( typeof listener === 'function' ) { listener ( event ) ; } else { listener . handleEvent ( event ) ; } } } ) ; } } }