2017-09-20 01:47:57 +08:00
import { Tracker } from 'meteor/tracker' ;
2018-05-02 23:49:57 +08:00
import KurentoBridge from '/imports/api/audio/client/bridge/kurento' ;
2020-12-02 02:24:24 +08:00
2017-11-17 19:52:48 +08:00
import Auth from '/imports/ui/services/auth' ;
2017-11-18 03:01:52 +08:00
import VoiceUsers from '/imports/api/voice-users' ;
2017-10-13 03:22:10 +08:00
import SIPBridge from '/imports/api/audio/client/bridge/sip' ;
2018-07-12 06:03:56 +08:00
import logger from '/imports/startup/client/logger' ;
2017-10-24 21:19:58 +08:00
import { notify } from '/imports/ui/services/notification' ;
2019-09-30 22:54:34 +08:00
import playAndRetry from '/imports/utils/mediaElementPlayRetry' ;
2019-11-15 03:07:35 +08:00
import iosWebviewAudioPolyfills from '/imports/utils/ios-webview-audio-polyfills' ;
2019-11-30 05:48:04 +08:00
import { monitorAudioConnection } from '/imports/utils/stats' ;
2019-09-30 22:54:34 +08:00
import AudioErrors from './error-codes' ;
2020-12-01 00:09:35 +08:00
import { Meteor } from "meteor/meteor" ;
2021-07-02 23:51:00 +08:00
import browserInfo from '/imports/utils/browserInfo' ;
2017-09-20 01:47:57 +08:00
2020-01-28 21:07:21 +08:00
const STATS = Meteor . settings . public . stats ;
2017-10-18 03:16:42 +08:00
const MEDIA = Meteor . settings . public . media ;
2018-08-30 03:12:34 +08:00
const MEDIA _TAG = MEDIA . mediaTag ;
2017-10-18 03:16:42 +08:00
const ECHO _TEST _NUMBER = MEDIA . echoTestNumber ;
2018-09-12 01:09:29 +08:00
const MAX _LISTEN _ONLY _RETRIES = 1 ;
2020-09-26 07:11:44 +08:00
const LISTEN _ONLY _CALL _TIMEOUT _MS = MEDIA . listenOnlyCallTimeout || 25000 ;
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
const DEFAULT _INPUT _DEVICE _ID = 'default' ;
const DEFAULT _OUTPUT _DEVICE _ID = 'default' ;
2021-07-02 23:51:00 +08:00
const EXPERIMENTAL _USE _KMS _TRICKLE _ICE _FOR _MICROPHONE = Meteor . settings
. public . app . experimentalUseKmsTrickleIceForMicrophone ;
2017-10-10 04:48:10 +08:00
2021-09-01 02:50:53 +08:00
const DEFAULT _AUDIO _BRIDGES _PATH = '/imports/api/audio/client/' ;
2017-10-10 04:48:10 +08:00
const CALL _STATES = {
2017-10-05 04:49:11 +08:00
STARTED : 'started' ,
ENDED : 'ended' ,
FAILED : 'failed' ,
2019-06-13 05:01:20 +08:00
RECONNECTING : 'reconnecting' ,
2019-08-03 05:32:42 +08:00
AUTOPLAY _BLOCKED : 'autoplayBlocked' ,
2017-10-05 04:49:11 +08:00
} ;
2017-09-20 01:47:57 +08:00
2021-03-07 09:09:43 +08:00
const BREAKOUT _AUDIO _TRANSFER _STATES = {
CONNECTED : 'connected' ,
DISCONNECTED : 'disconnected' ,
RETURNING : 'returning' ,
} ;
2021-08-13 03:39:04 +08:00
/ * *
* Audio status to be filtered in getStats ( )
* /
const FILTER _AUDIO _STATS = [
'outbound-rtp' ,
'inbound-rtp' ,
2021-08-26 03:27:46 +08:00
'candidate-pair' ,
'local-candidate' ,
'transport' ,
2021-08-13 03:39:04 +08:00
] ;
2017-09-20 01:47:57 +08:00
class AudioManager {
constructor ( ) {
2017-09-30 04:42:34 +08:00
this . _inputDevice = {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
value : DEFAULT _INPUT _DEVICE _ID ,
2017-10-11 02:03:29 +08:00
tracker : new Tracker . Dependency ( ) ,
2017-09-30 04:42:34 +08:00
} ;
2021-03-07 09:09:43 +08:00
this . _breakoutAudioTransferStatus = {
status : BREAKOUT _AUDIO _TRANSFER _STATES . DISCONNECTED ,
breakoutMeetingId : null ,
} ;
2021-02-26 02:36:11 +08:00
2017-09-20 01:47:57 +08:00
this . defineProperties ( {
isMuted : false ,
isConnected : false ,
isConnecting : false ,
2017-10-27 01:14:56 +08:00
isHangingUp : false ,
2017-09-29 21:38:10 +08:00
isListenOnly : false ,
isEchoTest : false ,
2018-06-20 23:36:26 +08:00
isTalking : false ,
2017-11-09 02:41:15 +08:00
isWaitingPermissions : false ,
2017-09-20 01:47:57 +08:00
error : null ,
2018-01-16 23:56:31 +08:00
muteHandle : null ,
2019-08-03 05:32:42 +08:00
autoplayBlocked : false ,
2021-02-27 02:05:17 +08:00
isReconnecting : false ,
2017-09-20 01:47:57 +08:00
} ) ;
2018-09-12 01:09:29 +08:00
this . useKurento = Meteor . settings . public . kurento . enableListenOnly ;
2019-08-03 05:32:42 +08:00
this . failedMediaElements = [ ] ;
this . handlePlayElementFailed = this . handlePlayElementFailed . bind ( this ) ;
2019-11-30 05:48:04 +08:00
this . monitor = this . monitor . bind ( this ) ;
2021-03-07 09:09:43 +08:00
2021-04-16 21:45:40 +08:00
this . _inputStream = null ;
this . _inputStreamTracker = new Tracker . Dependency ( ) ;
2021-03-07 09:09:43 +08:00
this . BREAKOUT _AUDIO _TRANSFER _STATES = BREAKOUT _AUDIO _TRANSFER _STATES ;
2017-09-20 01:47:57 +08:00
}
2021-02-12 10:55:34 +08:00
init ( userData , audioEventHandler ) {
2021-09-01 02:50:53 +08:00
this . loadBridges ( userData ) ;
2017-10-18 03:16:42 +08:00
this . userData = userData ;
2017-11-18 03:01:52 +08:00
this . initialized = true ;
2021-02-12 10:55:34 +08:00
this . audioEventHandler = audioEventHandler ;
2017-10-18 03:16:42 +08:00
}
2018-12-22 01:14:05 +08:00
2021-09-01 02:50:53 +08:00
/ * *
* Load audio bridges modules to be used the manager .
*
* Bridges can be configured in settings . yml file .
* @ param { Object } userData The Object representing user data to be passed to
* the bridge .
* /
2021-09-09 03:24:15 +08:00
async loadBridges ( userData ) {
2021-09-01 02:50:53 +08:00
let FullAudioBridge = SIPBridge ;
let ListenOnlyBridge = KurentoBridge ;
if ( MEDIA . audio ) {
const {
bridges ,
defaultFullAudioBridge ,
defaultListenOnlyBridge ,
} = MEDIA . audio ;
this . bridges = { } ;
2021-09-09 03:24:15 +08:00
await Promise . all ( Object . values ( bridges ) . map ( async ( bridge ) => {
2021-09-01 02:50:53 +08:00
// eslint-disable-next-line import/no-dynamic-require, global-require
2021-09-09 21:15:06 +08:00
this . bridges [ bridge . name ] = ( await import ( DEFAULT _AUDIO _BRIDGES _PATH
+ bridge . path ) || { } ) . default ;
2021-09-09 03:24:15 +08:00
} ) ) ;
2021-09-01 02:50:53 +08:00
if ( defaultFullAudioBridge && ( this . bridges [ defaultFullAudioBridge ] ) ) {
FullAudioBridge = this . bridges [ defaultFullAudioBridge ] ;
}
if ( defaultListenOnlyBridge && ( this . bridges [ defaultListenOnlyBridge ] ) ) {
ListenOnlyBridge = this . bridges [ defaultListenOnlyBridge ] ;
}
}
this . bridge = new FullAudioBridge ( userData ) ;
if ( this . useKurento ) {
this . listenOnlyBridge = new ListenOnlyBridge ( userData ) ;
}
}
2019-02-21 05:58:37 +08:00
setAudioMessages ( messages , intl ) {
2018-04-20 03:57:54 +08:00
this . messages = messages ;
2019-02-21 05:58:37 +08:00
this . intl = intl ;
2018-04-20 03:57:54 +08:00
}
2017-10-18 03:16:42 +08:00
2017-09-20 01:47:57 +08:00
defineProperties ( obj ) {
2017-09-29 21:38:10 +08:00
Object . keys ( obj ) . forEach ( ( key ) => {
const privateKey = ` _ ${ key } ` ;
this [ privateKey ] = {
2017-09-20 01:47:57 +08:00
value : obj [ key ] ,
2017-10-11 02:03:29 +08:00
tracker : new Tracker . Dependency ( ) ,
2017-09-29 21:38:10 +08:00
} ;
2017-09-20 01:47:57 +08:00
Object . defineProperty ( this , key , {
set : ( value ) => {
2017-09-29 21:38:10 +08:00
this [ privateKey ] . value = value ;
this [ privateKey ] . tracker . changed ( ) ;
2017-09-20 01:47:57 +08:00
} ,
get : ( ) => {
2017-09-29 21:38:10 +08:00
this [ privateKey ] . tracker . depend ( ) ;
return this [ privateKey ] . value ;
} ,
} ) ;
} ) ;
2017-09-20 01:47:57 +08:00
}
2021-06-30 01:47:59 +08:00
async trickleIce ( ) {
2021-07-02 23:51:00 +08:00
const { isFirefox , isIe , isSafari } = browserInfo ;
if ( ! this . listenOnlyBridge
|| isFirefox
|| isIe
|| isSafari ) return [ ] ;
2021-06-30 01:47:59 +08:00
if ( this . validIceCandidates && this . validIceCandidates . length ) {
2021-07-02 23:51:00 +08:00
logger . info ( { logCode : 'audiomanager_trickle_ice_reuse_candidate' } ,
'Reusing trickle-ice information before activating microphone' ) ;
2021-06-30 01:47:59 +08:00
return this . validIceCandidates ;
}
2021-07-02 23:51:00 +08:00
logger . info ( { logCode : 'audiomanager_trickle_ice_get_local_candidate' } ,
'Performing trickle-ice before activating microphone' ) ;
2021-06-30 01:47:59 +08:00
this . validIceCandidates = await this . listenOnlyBridge . trickleIce ( ) || [ ] ;
return this . validIceCandidates ;
}
2018-03-16 02:57:25 +08:00
joinMicrophone ( ) {
2021-06-30 01:47:59 +08:00
this . audioJoinStartTime = new Date ( ) ;
this . logAudioJoinTime = false ;
2018-03-16 02:57:25 +08:00
this . isListenOnly = false ;
this . isEchoTest = false ;
2020-10-15 05:12:09 +08:00
return this . onAudioJoining . bind ( this ) ( )
2019-06-13 05:01:20 +08:00
. then ( ( ) => {
const callOptions = {
isListenOnly : false ,
extension : null ,
inputStream : this . inputStream ,
} ;
2020-09-26 07:11:44 +08:00
return this . joinAudio ( callOptions , this . callStateCallback . bind ( this ) ) ;
2019-06-13 05:01:20 +08:00
} ) ;
2018-03-16 02:57:25 +08:00
}
joinEchoTest ( ) {
2021-06-30 01:47:59 +08:00
this . audioJoinStartTime = new Date ( ) ;
this . logAudioJoinTime = false ;
2018-03-16 02:57:25 +08:00
this . isListenOnly = false ;
this . isEchoTest = true ;
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
return this . onAudioJoining . bind ( this ) ( )
2021-06-30 01:47:59 +08:00
. then ( async ( ) => {
2021-07-02 23:51:00 +08:00
let validIceCandidates = [ ] ;
if ( EXPERIMENTAL _USE _KMS _TRICKLE _ICE _FOR _MICROPHONE ) {
validIceCandidates = await this . trickleIce ( ) ;
}
2019-06-13 05:01:20 +08:00
const callOptions = {
isListenOnly : false ,
extension : ECHO _TEST _NUMBER ,
inputStream : this . inputStream ,
2021-06-30 01:47:59 +08:00
validIceCandidates ,
2019-06-13 05:01:20 +08:00
} ;
2019-07-26 02:41:24 +08:00
logger . info ( { logCode : 'audiomanager_join_echotest' , extraInfo : { logType : 'user_action' } } , 'User requested to join audio conference with mic' ) ;
2020-09-26 07:11:44 +08:00
return this . joinAudio ( callOptions , this . callStateCallback . bind ( this ) ) ;
2019-06-13 05:01:20 +08:00
} ) ;
2018-03-16 02:57:25 +08:00
}
2020-09-26 07:11:44 +08:00
joinAudio ( callOptions , callStateCallback ) {
return this . bridge . joinAudio ( callOptions ,
callStateCallback . bind ( this ) ) . catch ( ( error ) => {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
const { name } = error ;
2020-09-26 07:11:44 +08:00
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
if ( ! name ) {
2020-09-26 07:11:44 +08:00
throw error ;
}
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
switch ( name ) {
case 'NotAllowedError' :
logger . error ( {
logCode : 'audiomanager_error_getting_device' ,
extraInfo : {
errorName : error . name ,
errorMessage : error . message ,
} ,
} , ` Error getting microphone - { ${ error . name } : ${ error . message } } ` ) ;
break ;
case 'NotFoundError' :
logger . error ( {
logCode : 'audiomanager_error_device_not_found' ,
extraInfo : {
errorName : error . name ,
errorMessage : error . message ,
} ,
} , ` Error getting microphone - { ${ error . name } : ${ error . message } } ` ) ;
break ;
default :
break ;
}
this . isConnecting = false ;
this . isWaitingPermissions = false ;
throw {
type : 'MEDIA_ERROR' ,
} ;
2020-09-26 07:11:44 +08:00
} ) ;
}
2019-07-26 02:41:24 +08:00
async joinListenOnly ( r = 0 ) {
2021-06-30 01:47:59 +08:00
this . audioJoinStartTime = new Date ( ) ;
this . logAudioJoinTime = false ;
2019-07-26 02:41:24 +08:00
let retries = r ;
2018-03-16 02:57:25 +08:00
this . isListenOnly = true ;
this . isEchoTest = false ;
2019-06-13 05:01:20 +08:00
2018-05-02 23:49:57 +08:00
// The kurento bridge isn't a full audio bridge yet, so we have to differ it
2018-11-14 01:14:30 +08:00
const bridge = this . useKurento ? this . listenOnlyBridge : this . bridge ;
2018-03-16 02:57:25 +08:00
const callOptions = {
isListenOnly : true ,
extension : null ,
} ;
2018-12-22 01:14:05 +08:00
// Call polyfills for webrtc client if navigator is "iOS Webview"
const userAgent = window . navigator . userAgent . toLocaleLowerCase ( ) ;
if ( ( userAgent . indexOf ( 'iphone' ) > - 1 || userAgent . indexOf ( 'ipad' ) > - 1 )
2019-02-21 05:58:37 +08:00
&& userAgent . indexOf ( 'safari' ) === - 1 ) {
2018-12-22 01:14:05 +08:00
iosWebviewAudioPolyfills ( ) ;
}
2018-03-16 02:57:25 +08:00
// We need this until we upgrade to SIP 9x. See #4690
2019-12-03 06:15:46 +08:00
const listenOnlyCallTimeoutErr = this . useKurento ? 'KURENTO_CALL_TIMEOUT' : 'SIP_CALL_TIMEOUT' ;
2018-03-16 02:57:25 +08:00
const iceGatheringTimeout = new Promise ( ( resolve , reject ) => {
2019-12-03 06:15:46 +08:00
setTimeout ( reject , LISTEN _ONLY _CALL _TIMEOUT _MS , listenOnlyCallTimeoutErr ) ;
2018-03-16 02:57:25 +08:00
} ) ;
2019-12-03 06:15:46 +08:00
const exitKurentoAudio = ( ) => {
if ( this . useKurento ) {
2020-12-02 02:24:24 +08:00
bridge . exitAudio ( ) ;
2019-12-03 06:15:46 +08:00
const audio = document . querySelector ( MEDIA _TAG ) ;
audio . muted = false ;
}
} ;
2019-07-26 02:41:24 +08:00
2019-12-03 06:15:46 +08:00
const handleListenOnlyError = ( err ) => {
2018-09-12 01:09:29 +08:00
if ( iceGatheringTimeout ) {
clearTimeout ( iceGatheringTimeout ) ;
}
2019-12-03 06:15:46 +08:00
const errorReason = ( typeof err === 'string' ? err : undefined ) || err . errorReason || err . errorMessage ;
const bridgeInUse = ( this . useKurento ? 'Kurento' : 'SIP' ) ;
2019-06-29 05:45:50 +08:00
logger . error ( {
logCode : 'audiomanager_listenonly_error' ,
extraInfo : {
2019-12-03 06:15:46 +08:00
errorReason ,
audioBridge : bridgeInUse ,
2019-06-29 05:45:50 +08:00
retries ,
} ,
2020-12-02 02:24:24 +08:00
} , ` Listen only error - ${ errorReason } - bridge: ${ bridgeInUse } ` ) ;
2018-11-14 01:14:30 +08:00
} ;
2018-09-12 01:09:29 +08:00
2019-07-26 02:41:24 +08:00
logger . info ( { logCode : 'audiomanager_join_listenonly' , extraInfo : { logType : 'user_action' } } , 'user requested to connect to audio conference as listen only' ) ;
2019-08-03 05:32:42 +08:00
window . addEventListener ( 'audioPlayFailed' , this . handlePlayElementFailed ) ;
2018-03-16 02:57:25 +08:00
return this . onAudioJoining ( )
. then ( ( ) => Promise . race ( [
2018-05-02 23:49:57 +08:00
bridge . joinAudio ( callOptions , this . callStateCallback . bind ( this ) ) ,
2018-03-16 02:57:25 +08:00
iceGatheringTimeout ,
] ) )
2018-09-12 01:09:29 +08:00
. catch ( async ( err ) => {
2019-12-03 06:15:46 +08:00
handleListenOnlyError ( err ) ;
2018-10-04 02:39:55 +08:00
if ( retries < MAX _LISTEN _ONLY _RETRIES ) {
2018-09-12 01:09:29 +08:00
// Fallback to SIP.js listen only in case of failure
if ( this . useKurento ) {
2019-12-03 06:15:46 +08:00
exitKurentoAudio ( ) ;
2018-09-12 01:09:29 +08:00
this . useKurento = false ;
2018-10-04 02:39:55 +08:00
2019-12-03 06:15:46 +08:00
const errorReason = ( typeof err === 'string' ? err : undefined ) || err . errorReason || err . errorMessage ;
logger . info ( {
logCode : 'audiomanager_listenonly_fallback' ,
extraInfo : {
logType : 'fallback' ,
errorReason ,
} ,
} , ` Falling back to FreeSWITCH listenOnly - cause: ${ errorReason } ` ) ;
2018-09-12 01:09:29 +08:00
}
2019-12-03 06:15:46 +08:00
retries += 1 ;
this . joinListenOnly ( retries ) ;
2018-03-16 02:57:25 +08:00
}
2019-07-26 02:41:24 +08:00
return null ;
2017-11-02 20:10:01 +08:00
} ) ;
2017-09-20 01:47:57 +08:00
}
2018-03-16 02:57:25 +08:00
onAudioJoining ( ) {
this . isConnecting = true ;
this . isMuted = false ;
this . error = false ;
return Promise . resolve ( ) ;
}
2017-09-20 01:47:57 +08:00
exitAudio ( ) {
2017-11-02 20:10:01 +08:00
if ( ! this . isConnected ) return Promise . resolve ( ) ;
2018-11-14 01:14:30 +08:00
const bridge = ( this . useKurento && this . isListenOnly ) ? this . listenOnlyBridge : this . bridge ;
2018-05-02 23:49:57 +08:00
2017-10-27 01:14:56 +08:00
this . isHangingUp = true ;
2018-06-28 01:44:11 +08:00
2018-05-02 23:49:57 +08:00
return bridge . exitAudio ( ) ;
2017-09-20 01:47:57 +08:00
}
2017-10-12 20:50:23 +08:00
transferCall ( ) {
2017-10-18 03:16:42 +08:00
this . onTransferStart ( ) ;
return this . bridge . transferCall ( this . onAudioJoin . bind ( this ) ) ;
2017-10-12 20:50:23 +08:00
}
2020-08-27 05:23:01 +08:00
onVoiceUserChanges ( fields ) {
if ( fields . muted !== undefined && fields . muted !== this . isMuted ) {
2020-09-10 01:03:27 +08:00
let muteState ;
2020-08-27 05:23:01 +08:00
this . isMuted = fields . muted ;
2020-09-10 01:03:27 +08:00
if ( this . isMuted ) {
muteState = 'selfMuted' ;
this . mute ( ) ;
} else {
muteState = 'selfUnmuted' ;
this . unmute ( ) ;
}
2020-08-27 05:23:01 +08:00
window . parent . postMessage ( { response : muteState } , '*' ) ;
}
if ( fields . talking !== undefined && fields . talking !== this . isTalking ) {
this . isTalking = fields . talking ;
}
if ( this . isMuted ) {
this . isTalking = false ;
}
}
2017-09-20 01:47:57 +08:00
onAudioJoin ( ) {
this . isConnecting = false ;
2017-10-19 03:40:01 +08:00
this . isConnected = true ;
2017-10-20 18:11:51 +08:00
2018-01-16 05:01:57 +08:00
// listen to the VoiceUsers changes and update the flag
2018-03-16 02:57:25 +08:00
if ( ! this . muteHandle ) {
2019-08-22 20:05:06 +08:00
const query = VoiceUsers . find ( { intId : Auth . userID } , { fields : { muted : 1 , talking : 1 } } ) ;
2018-01-16 05:13:18 +08:00
this . muteHandle = query . observeChanges ( {
2020-08-27 05:23:01 +08:00
added : ( id , fields ) => this . onVoiceUserChanges ( fields ) ,
changed : ( id , fields ) => this . onVoiceUserChanges ( fields ) ,
2018-01-16 05:01:57 +08:00
} ) ;
}
2021-06-30 01:47:59 +08:00
const secondsToActivateAudio = ( new Date ( ) - this . audioJoinStartTime ) / 1000 ;
if ( ! this . logAudioJoinTime ) {
this . logAudioJoinTime = true ;
2021-07-10 00:43:43 +08:00
logger . info ( {
logCode : 'audio_mic_join_time' ,
extraInfo : {
secondsToActivateAudio ,
} ,
} , ` Time needed to connect audio (seconds): ${ secondsToActivateAudio } ` ) ;
2021-06-30 01:47:59 +08:00
}
2018-01-16 05:01:57 +08:00
2017-10-20 18:11:51 +08:00
if ( ! this . isEchoTest ) {
2018-11-29 05:42:24 +08:00
window . parent . postMessage ( { response : 'joinedAudio' } , '*' ) ;
2019-02-21 05:58:37 +08:00
this . notify ( this . intl . formatMessage ( this . messages . info . JOINED _AUDIO ) ) ;
logger . info ( { logCode : 'audio_joined' } , 'Audio Joined' ) ;
2021-04-16 21:45:40 +08:00
this . inputStream = ( this . bridge ? this . bridge . inputStream : null ) ;
2020-01-28 21:07:21 +08:00
if ( STATS . enabled ) this . monitor ( ) ;
2021-02-27 02:52:11 +08:00
this . audioEventHandler ( {
name : 'started' ,
isListenOnly : this . isListenOnly ,
} ) ;
2017-10-20 18:11:51 +08:00
}
2021-04-13 02:59:43 +08:00
Session . set ( 'audioModalIsOpen' , false ) ;
2017-09-20 01:47:57 +08:00
}
2017-10-12 20:50:23 +08:00
onTransferStart ( ) {
this . isEchoTest = false ;
this . isConnecting = true ;
}
2017-09-20 01:47:57 +08:00
onAudioExit ( ) {
this . isConnected = false ;
2017-10-05 04:49:11 +08:00
this . isConnecting = false ;
2017-10-27 01:14:56 +08:00
this . isHangingUp = false ;
2019-08-03 05:32:42 +08:00
this . autoplayBlocked = false ;
this . failedMediaElements = [ ] ;
2017-09-29 21:38:10 +08:00
2018-04-18 01:09:05 +08:00
if ( this . inputStream ) {
2021-04-16 21:45:40 +08:00
this . inputStream . getTracks ( ) . forEach ( ( track ) => track . stop ( ) ) ;
this . inputStream = null ;
2018-04-18 01:09:05 +08:00
this . inputDevice = { id : 'default' } ;
}
2018-04-13 20:39:26 +08:00
2017-10-20 18:11:51 +08:00
if ( ! this . error && ! this . isEchoTest ) {
2021-10-19 04:26:01 +08:00
this . notify (
this . intl . formatMessage ( this . messages . info . LEFT _AUDIO ) ,
false ,
'no_audio'
) ;
2017-09-29 21:38:10 +08:00
}
2019-06-13 05:01:20 +08:00
if ( ! this . isEchoTest ) {
this . playHangUpSound ( ) ;
}
2018-11-29 05:42:24 +08:00
window . parent . postMessage ( { response : 'notInAudio' } , '*' ) ;
2019-08-03 05:32:42 +08:00
window . removeEventListener ( 'audioPlayFailed' , this . handlePlayElementFailed ) ;
2017-09-20 01:47:57 +08:00
}
2017-10-05 04:49:11 +08:00
callStateCallback ( response ) {
2017-09-29 21:38:10 +08:00
return new Promise ( ( resolve ) => {
const {
2017-10-05 04:49:11 +08:00
STARTED ,
ENDED ,
FAILED ,
2019-06-13 05:01:20 +08:00
RECONNECTING ,
2019-08-03 05:32:42 +08:00
AUTOPLAY _BLOCKED ,
2017-10-05 04:49:11 +08:00
} = CALL _STATES ;
2017-09-29 21:38:10 +08:00
2017-10-05 04:49:11 +08:00
const {
status ,
error ,
2017-10-23 20:41:09 +08:00
bridgeError ,
2019-06-13 05:01:20 +08:00
silenceNotifications ,
2020-12-02 02:24:24 +08:00
bridge ,
2017-10-05 04:49:11 +08:00
} = response ;
if ( status === STARTED ) {
2021-02-27 02:05:17 +08:00
this . isReconnecting = false ;
2017-09-29 21:38:10 +08:00
this . onAudioJoin ( ) ;
2017-10-05 04:49:11 +08:00
resolve ( STARTED ) ;
} else if ( status === ENDED ) {
2021-02-27 02:05:17 +08:00
this . isReconnecting = false ;
2021-03-09 01:51:03 +08:00
this . setBreakoutAudioTransferStatus ( {
breakoutMeetingId : '' ,
status : BREAKOUT _AUDIO _TRANSFER _STATES . DISCONNECTED ,
} ) ;
2019-06-05 02:16:43 +08:00
logger . info ( { logCode : 'audio_ended' } , 'Audio ended without issue' ) ;
2017-09-29 21:38:10 +08:00
this . onAudioExit ( ) ;
2017-10-05 04:49:11 +08:00
} else if ( status === FAILED ) {
2021-02-27 02:05:17 +08:00
this . isReconnecting = false ;
2021-03-09 01:51:03 +08:00
this . setBreakoutAudioTransferStatus ( {
breakoutMeetingId : '' ,
status : BREAKOUT _AUDIO _TRANSFER _STATES . DISCONNECTED ,
} )
2019-02-21 05:58:37 +08:00
const errorKey = this . messages . error [ error ] || this . messages . error . GENERIC _ERROR ;
const errorMsg = this . intl . formatMessage ( errorKey , { 0 : bridgeError } ) ;
this . error = ! ! error ;
2019-06-29 05:45:50 +08:00
logger . error ( {
logCode : 'audio_failure' ,
extraInfo : {
2019-09-05 02:16:14 +08:00
errorCode : error ,
2019-06-29 05:45:50 +08:00
cause : bridgeError ,
2020-12-02 02:24:24 +08:00
bridge ,
2019-06-29 05:45:50 +08:00
} ,
2019-12-03 06:15:46 +08:00
} , ` Audio error - errorCode= ${ error } , cause= ${ bridgeError } ` ) ;
2019-06-13 05:01:20 +08:00
if ( silenceNotifications !== true ) {
this . notify ( errorMsg , true ) ;
this . exitAudio ( ) ;
this . onAudioExit ( ) ;
}
} else if ( status === RECONNECTING ) {
2021-02-27 02:05:17 +08:00
this . isReconnecting = true ;
2021-03-09 01:51:03 +08:00
this . setBreakoutAudioTransferStatus ( {
breakoutMeetingId : '' ,
status : BREAKOUT _AUDIO _TRANSFER _STATES . DISCONNECTED ,
} )
2019-06-13 05:01:20 +08:00
logger . info ( { logCode : 'audio_reconnecting' } , 'Attempting to reconnect audio' ) ;
this . notify ( this . intl . formatMessage ( this . messages . info . RECONNECTING _AUDIO ) , true ) ;
this . playHangUpSound ( ) ;
2019-08-03 05:32:42 +08:00
} else if ( status === AUTOPLAY _BLOCKED ) {
2021-03-09 01:51:03 +08:00
this . setBreakoutAudioTransferStatus ( {
breakoutMeetingId : '' ,
status : BREAKOUT _AUDIO _TRANSFER _STATES . DISCONNECTED ,
} )
2021-02-27 02:05:17 +08:00
this . isReconnecting = false ;
2019-08-03 05:32:42 +08:00
this . autoplayBlocked = true ;
this . onAudioJoin ( ) ;
resolve ( AUTOPLAY _BLOCKED ) ;
2017-09-29 21:38:10 +08:00
}
2017-10-11 20:05:57 +08:00
} ) ;
2017-09-29 21:38:10 +08:00
}
2018-06-20 23:36:26 +08:00
isUsingAudio ( ) {
2018-12-22 01:14:05 +08:00
return this . isConnected || this . isConnecting
|| this . isHangingUp || this . isEchoTest ;
2018-06-20 23:36:26 +08:00
}
2017-10-27 01:14:56 +08:00
setDefaultInputDevice ( ) {
2017-11-02 20:10:01 +08:00
return this . changeInputDevice ( ) ;
2017-10-27 01:14:56 +08:00
}
2017-11-02 20:10:01 +08:00
setDefaultOutputDevice ( ) {
return this . changeOutputDevice ( 'default' ) ;
}
changeInputDevice ( deviceId ) {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
if ( ! deviceId ) {
return Promise . resolve ( ) ;
}
const handleChangeInputDeviceSuccess = ( inputDeviceId ) => {
this . inputDevice . id = inputDeviceId ;
return Promise . resolve ( inputDeviceId ) ;
2017-11-02 20:10:01 +08:00
} ;
2019-09-07 00:35:55 +08:00
const handleChangeInputDeviceError = ( error ) => {
logger . error ( {
logCode : 'audiomanager_error_getting_device' ,
extraInfo : {
errorName : error . name ,
errorMessage : error . message ,
} ,
} , ` Error getting microphone - { ${ error . name } : ${ error . message } } ` ) ;
2019-09-27 21:52:29 +08:00
2019-09-30 22:54:34 +08:00
const { MIC _ERROR } = AudioErrors ;
2019-09-27 21:52:29 +08:00
const disabledSysSetting = error . message . includes ( 'Permission denied by system' ) ;
2019-09-30 22:54:34 +08:00
const isMac = navigator . platform . indexOf ( 'Mac' ) !== - 1 ;
2019-09-27 21:52:29 +08:00
2019-09-30 22:54:34 +08:00
let code = MIC _ERROR . NO _PERMISSION ;
if ( isMac && disabledSysSetting ) code = MIC _ERROR . MAC _OS _BLOCK ;
2019-09-27 21:52:29 +08:00
2019-09-07 00:35:55 +08:00
return Promise . reject ( {
2019-07-26 02:41:24 +08:00
type : 'MEDIA_ERROR' ,
message : this . messages . error . MEDIA _ERROR ,
2019-09-27 21:52:29 +08:00
code ,
2019-07-26 02:41:24 +08:00
} ) ;
2019-09-07 00:35:55 +08:00
} ;
2017-11-02 20:10:01 +08:00
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
return this . bridge . changeInputDeviceId ( deviceId )
2017-11-18 03:01:52 +08:00
. then ( handleChangeInputDeviceSuccess )
. catch ( handleChangeInputDeviceError ) ;
2017-10-18 03:16:42 +08:00
}
2017-09-30 04:42:34 +08:00
2020-07-28 03:49:26 +08:00
liveChangeInputDevice ( deviceId ) {
2021-04-16 21:45:40 +08:00
// we force stream to be null, so MutedAlert will deallocate it and
// a new one will be created for the new stream
this . inputStream = null ;
this . bridge . liveChangeInputDevice ( deviceId ) . then ( ( stream ) => {
this . setSenderTrackEnabled ( ! this . isMuted ) ;
this . inputStream = stream ;
} ) ;
2020-07-28 03:49:26 +08:00
}
2021-03-17 22:30:07 +08:00
async changeOutputDevice ( deviceId , isLive ) {
2021-04-02 02:53:43 +08:00
await this
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
. bridge
2021-03-17 22:30:07 +08:00
. changeOutputDevice ( deviceId || DEFAULT _OUTPUT _DEVICE _ID , isLive ) ;
2017-09-30 04:42:34 +08:00
}
2017-10-18 03:16:42 +08:00
set inputDevice ( value ) {
2018-04-13 20:39:26 +08:00
this . _inputDevice . value = value ;
2017-10-18 03:16:42 +08:00
this . _inputDevice . tracker . changed ( ) ;
2017-10-10 04:48:10 +08:00
}
2017-10-11 02:03:29 +08:00
get inputStream ( ) {
2021-04-16 21:45:40 +08:00
this . _inputStreamTracker . depend ( ) ;
return this . _inputStream ;
}
set inputStream ( stream ) {
// We store reactive information about input stream
// because mutedalert component needs to track when it changes
// and then update hark with the new value for inputStream
if ( this . _inputStream !== stream ) {
this . _inputStreamTracker . changed ( ) ;
}
this . _inputStream = stream ;
2017-09-30 04:42:34 +08:00
}
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
get inputDevice ( ) {
return this . _inputDevice ;
}
2017-10-11 02:03:29 +08:00
get inputDeviceId ( ) {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
return ( this . bridge && this . bridge . inputDeviceId )
? this . bridge . inputDeviceId : DEFAULT _INPUT _DEVICE _ID ;
2017-09-30 04:42:34 +08:00
}
2017-10-18 03:16:42 +08:00
2021-04-02 02:53:43 +08:00
get outputDeviceId ( ) {
return ( this . bridge && this . bridge . outputDeviceId )
? this . bridge . outputDeviceId : DEFAULT _OUTPUT _DEVICE _ID ;
}
2021-03-07 09:09:43 +08:00
/ * *
* Sets the current status for breakout audio transfer
2021-03-08 02:01:12 +08:00
* @ param { Object } newStatus The status Object to be set for
2021-03-07 09:09:43 +08:00
* audio transfer .
2021-03-08 02:01:12 +08:00
* @ param { string } newStatus . breakoutMeetingId The meeting id of the current
* breakout audio transfer .
* @ param { string } newStatus . status The status of the current audio
* transfer . Valid values are
* 'connected' , 'disconnected' and
* 'returning' .
2021-03-07 09:09:43 +08:00
* /
2021-03-08 02:01:12 +08:00
setBreakoutAudioTransferStatus ( newStatus ) {
const currentStatus = this . _breakoutAudioTransferStatus ;
const { breakoutMeetingId , status } = newStatus ;
2021-03-07 09:09:43 +08:00
if ( typeof breakoutMeetingId === 'string' ) {
2021-03-08 02:01:12 +08:00
currentStatus . breakoutMeetingId = breakoutMeetingId ;
2021-03-07 09:09:43 +08:00
}
2021-03-17 22:30:07 +08:00
2021-03-07 09:09:43 +08:00
if ( typeof status === 'string' ) {
2021-03-08 02:01:12 +08:00
currentStatus . status = status ;
2021-09-21 22:22:05 +08:00
if ( this . bridge && ! this . isListenOnly ) {
if ( status !== BREAKOUT _AUDIO _TRANSFER _STATES . CONNECTED ) {
this . bridge . ignoreCallState = false ;
} else {
this . bridge . ignoreCallState = true ;
}
}
2021-03-07 09:09:43 +08:00
}
2021-02-26 02:36:11 +08:00
}
2021-03-07 09:09:43 +08:00
getBreakoutAudioTransferStatus ( ) {
return this . _breakoutAudioTransferStatus ;
2021-02-26 02:36:11 +08:00
}
2017-10-18 03:16:42 +08:00
set userData ( value ) {
this . _userData = value ;
}
get userData ( ) {
return this . _userData ;
}
2017-10-23 20:41:09 +08:00
2019-06-13 05:01:20 +08:00
playHangUpSound ( ) {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
this . playAlertSound ( ` ${ Meteor . settings . public . app . cdn
2020-12-01 00:09:35 +08:00
+ Meteor . settings . public . app . basename + Meteor . settings . public . app . instanceId } `
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
+ '/resources/sounds/LeftCall.mp3' ) ;
2019-06-13 05:01:20 +08:00
}
2019-04-12 21:55:14 +08:00
notify ( message , error = false , icon = 'unmute' ) {
const audioIcon = this . isListenOnly ? 'listen' : icon ;
2019-04-12 06:53:57 +08:00
2017-11-18 03:01:52 +08:00
notify (
message ,
2018-03-16 02:57:25 +08:00
error ? 'error' : 'info' ,
2019-04-12 21:55:14 +08:00
audioIcon ,
2017-11-18 03:01:52 +08:00
) ;
2017-10-23 20:41:09 +08:00
}
2019-08-03 05:32:42 +08:00
2019-11-30 05:48:04 +08:00
monitor ( ) {
const bridge = ( this . useKurento && this . isListenOnly ) ? this . listenOnlyBridge : this . bridge ;
const peer = bridge . getPeerConnection ( ) ;
monitorAudioConnection ( peer ) ;
}
2019-08-03 05:32:42 +08:00
handleAllowAutoplay ( ) {
window . removeEventListener ( 'audioPlayFailed' , this . handlePlayElementFailed ) ;
2019-09-07 02:58:22 +08:00
logger . info ( {
logCode : 'audiomanager_autoplay_allowed' ,
} , 'Listen only autoplay allowed by the user' ) ;
2019-08-03 05:32:42 +08:00
while ( this . failedMediaElements . length ) {
const mediaElement = this . failedMediaElements . shift ( ) ;
if ( mediaElement ) {
2019-09-07 02:58:22 +08:00
playAndRetry ( mediaElement ) . then ( ( played ) => {
if ( ! played ) {
logger . error ( {
logCode : 'audiomanager_autoplay_handling_failed' ,
} , 'Listen only autoplay handling failed to play media' ) ;
} else {
// logCode is listenonly_* to make it consistent with the other tag play log
logger . info ( {
logCode : 'listenonly_media_play_success' ,
} , 'Listen only media played successfully' ) ;
}
2019-08-03 05:32:42 +08:00
} ) ;
}
}
this . autoplayBlocked = false ;
}
handlePlayElementFailed ( e ) {
const { mediaElement } = e . detail ;
e . stopPropagation ( ) ;
this . failedMediaElements . push ( mediaElement ) ;
if ( ! this . autoplayBlocked ) {
2019-09-07 02:58:22 +08:00
logger . info ( {
logCode : 'audiomanager_autoplay_prompt' ,
} , 'Prompting user for action to play listen only media' ) ;
2019-08-03 05:32:42 +08:00
this . autoplayBlocked = true ;
}
}
2020-09-10 01:03:27 +08:00
2020-09-17 22:37:28 +08:00
setSenderTrackEnabled ( shouldEnable ) {
2020-09-12 07:06:56 +08:00
// If the bridge is set to listen only mode, nothing to do here. This method
// is solely for muting outbound tracks.
if ( this . isListenOnly ) return ;
2020-09-10 22:50:50 +08:00
// Bridge -> SIP.js bridge, the only full audio capable one right now
const peer = this . bridge . getPeerConnection ( ) ;
2020-09-26 07:11:44 +08:00
if ( ! peer ) {
return ;
}
2020-09-10 01:03:27 +08:00
peer . getSenders ( ) . forEach ( sender => {
const { track } = sender ;
if ( track && track . kind === 'audio' ) {
2020-09-10 22:50:50 +08:00
track . enabled = shouldEnable ;
2020-09-10 01:03:27 +08:00
}
} ) ;
}
2020-09-17 22:37:28 +08:00
mute ( ) {
2020-09-10 22:50:50 +08:00
this . setSenderTrackEnabled ( false ) ;
}
2020-09-17 22:37:28 +08:00
unmute ( ) {
2020-09-10 22:50:50 +08:00
this . setSenderTrackEnabled ( true ) ;
2020-09-10 01:03:27 +08:00
}
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
2021-04-02 02:53:43 +08:00
playAlertSound ( url ) {
2021-04-13 00:57:02 +08:00
if ( ! url || ! this . bridge ) {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
return Promise . resolve ( ) ;
}
const audioAlert = new Audio ( url ) ;
2021-02-23 22:12:37 +08:00
audioAlert . addEventListener ( 'ended' , ( ) => { audioAlert . src = null ; } ) ;
2021-04-02 02:53:43 +08:00
const { outputDeviceId } = this . bridge ;
if ( outputDeviceId && ( typeof audioAlert . setSinkId === 'function' ) ) {
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
return audioAlert
2021-04-02 02:53:43 +08:00
. setSinkId ( outputDeviceId )
Correctly set audio input/output devices
When refusing ("thumbs down" button) echo test, user is able to select a different input device. This should work fine for chrome, firefox and safari (once user grants permission when asked by html5client).
For output devices, we depend on setSinkId function, which is enabled by default on current chrome release (2020) but not in Firefox (user needs to enable "setSinkId in about:config page). This implementation is listed as (?) in MDN.
In other words, output device selection should work out of the box for chrome, only.
When selecting an outputDevice, all alert sounds (hangup, screenshare , polling, etc) also goes to the same output device.
This solves #10592
2020-10-07 07:37:55 +08:00
. then ( ( ) => audioAlert . play ( ) ) ;
}
return audioAlert . play ( ) ;
}
2021-01-23 03:30:42 +08:00
async updateAudioConstraints ( constraints ) {
await this . bridge . updateAudioConstraints ( constraints ) ;
}
2021-08-13 03:39:04 +08:00
2021-08-13 20:46:19 +08:00
/ * *
* Helper for retrieving the current bridge being used by audio .
* @ returns An Object representing the current bridge .
* /
2021-08-13 03:39:04 +08:00
getCurrentBridge ( ) {
return this . isListenOnly ? this . listenOnlyBridge : this . bridge ;
}
2021-08-13 20:46:19 +08:00
/ * *
2021-08-26 03:27:46 +08:00
* Get the info about candidate - pair that is being used by the current peer .
* For firefox , or any other browser that doesn ' t support iceTransport
* property of RTCDtlsTransport , we retrieve the selected local candidate
* by looking into stats returned from getStats ( ) api . For other browsers ,
* we should use getSelectedCandidatePairFromPeer instead , because it has
* relatedAddress and relatedPort information about local candidate .
2021-08-13 20:46:19 +08:00
*
2021-08-26 03:27:46 +08:00
* @ param { Object } stats object returned by getStats ( ) api
* @ returns An Object of type RTCIceCandidatePairStats containing information
* about the candidate - pair being used by the peer .
2021-08-13 20:46:19 +08:00
*
2021-08-26 03:27:46 +08:00
* For firefox , we can use the 'selected' flag to find the candidate pair
* being used , while in chrome we can retrieved the selected pair
* by looking for the corresponding transport of the active peer .
2021-08-13 20:46:19 +08:00
* For more information see :
2021-08-26 03:27:46 +08:00
* https : //www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats
2021-08-13 20:46:19 +08:00
* and
2021-08-26 03:27:46 +08:00
* https : //developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/selected#value
* /
static getSelectedCandidatePairFromStats ( stats ) {
if ( ! stats || typeof stats !== 'object' ) return null ;
const transport = Object . values ( stats ) . find ( ( stat ) => stat . type
=== 'transport' ) || { } ;
return Object . values ( stats ) . find ( ( stat ) => stat . type === 'candidate-pair'
&& stat . nominated && ( stat . selected
|| stat . id === transport . selectedCandidatePairId ) ) ;
}
/ * *
* Get the info about candidate - pair that is being used by the current peer .
* This function ' s return value ( RTCIceCandidatePair object ) is different
* from getSelectedCandidatePairFromStats ( RTCIceCandidatePairStats object ) .
* The information returned here contains the relatedAddress and relatedPort
* fields ( only for candidates that are derived from another candidate , for
* host candidates , these fields are null ) . These field can be helpful for
* debugging network issues . For all the browsers that support iceTransport
* field of RTCDtlsTransport , we use this function as default to retrieve
* information about current selected - pair . For other browsers we retrieve it
* from getSelectedCandidatePairFromStats
*
* @ returns { Object } An RTCIceCandidatePair represented the selected
* candidate - pair of the active peer .
*
* For more info see :
* https : //www.w3.org/TR/webrtc/#dom-rtcicecandidatepair
* and
* https : //developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePair
* and
* https : //developer.mozilla.org/en-US/docs/Web/API/RTCDtlsTransport
2021-08-13 20:46:19 +08:00
* /
2021-08-26 03:27:46 +08:00
getSelectedCandidatePairFromPeer ( ) {
2021-08-13 03:39:04 +08:00
const bridge = this . getCurrentBridge ( ) ;
if ( ! bridge ) return null ;
const peer = bridge . getPeerConnection ( ) ;
2021-08-26 03:27:46 +08:00
if ( ! peer ) return null ;
2021-08-13 03:39:04 +08:00
2021-08-26 03:27:46 +08:00
let selectedPair = null ;
2021-08-13 03:39:04 +08:00
const receivers = peer . getReceivers ( ) ;
if ( receivers && receivers [ 0 ] && receivers [ 0 ] . transport
2021-08-26 03:27:46 +08:00
&& receivers [ 0 ] . transport . iceTransport
&& receivers [ 0 ] . transport . iceTransport ) {
selectedPair = receivers [ 0 ] . transport . iceTransport
2021-08-13 03:39:04 +08:00
. getSelectedCandidatePair ( ) ;
2021-08-26 03:27:46 +08:00
}
2021-08-13 03:39:04 +08:00
2021-08-26 03:27:46 +08:00
return selectedPair ;
}
2021-08-13 03:39:04 +08:00
2021-08-26 03:27:46 +08:00
/ * *
* Gets the selected local - candidate information . For browsers that support
* iceTransport property ( see getSelectedCandidatePairFromPeer ) we get this
* info from peer , otherwise we retrieve this information from getStats ( ) api
*
* @ param { Object } [ stats ] The status object returned from getStats ( ) api
* @ returns { Object } An Object containing the information about the
* local - candidate . For browsers that support iceTransport
* property , the object ' s type is RCIceCandidate . A
* RTCIceCandidateStats is returned , otherwise .
*
* For more info see :
* https : //www.w3.org/TR/webrtc/#dom-rtcicecandidate
* and
* https : //www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatestats
*
* /
getSelectedLocalCandidate ( stats ) {
let selectedPair = this . getSelectedCandidatePairFromPeer ( ) ;
if ( selectedPair ) return selectedPair . local ;
if ( ! stats ) return null ;
selectedPair = AudioManager . getSelectedCandidatePairFromStats ( stats ) ;
if ( selectedPair ) return stats [ selectedPair . localCandidateId ] ;
return null ;
}
/ * *
* Gets the information about private / public ip address from peer
* stats . The information retrieved from selected pair from the current
* RTCIceTransport and returned in a new Object with format :
* {
* address : String ,
* relatedAddress : String ,
* port : Number ,
* relatedPort : Number ,
* candidateType : String ,
* selectedLocalCandidate : Object ,
* }
*
* If users isn ' t behind NAT , relatedAddress and relatedPort may be null .
*
* @ returns An Object containing the information about private / public IP
* addresses and ports .
*
* For more information see :
* https : //www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats
* and
* https : //www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatestats
* and
* https : //www.w3.org/TR/webrtc/#rtcicecandidatetype-enum
* /
async getInternalExternalIpAddresses ( stats ) {
let transports = { } ;
if ( stats ) {
const selectedLocalCandidate = this . getSelectedLocalCandidate ( stats ) ;
if ( ! selectedLocalCandidate ) return transports ;
const candidateType = selectedLocalCandidate . candidateType
|| selectedLocalCandidate . type ;
2021-08-13 03:39:04 +08:00
transports = {
2021-08-26 03:27:46 +08:00
isUsingTurn : ( candidateType === 'relay' ) ,
address : selectedLocalCandidate . address ,
relatedAddress : selectedLocalCandidate . relatedAddress ,
port : selectedLocalCandidate . port ,
relatedPort : selectedLocalCandidate . relatedPort ,
candidateType ,
selectedLocalCandidate ,
2021-08-13 03:39:04 +08:00
} ;
}
return transports ;
}
2021-08-13 20:46:19 +08:00
/ * *
* Get stats about active audio peer .
2021-08-23 22:07:12 +08:00
* We filter the status based on FILTER _AUDIO _STATS constant .
2021-08-13 20:46:19 +08:00
* We also append to the returned object the information about peer ' s
* transport . This transport information is retrieved by
* getInternalExternalIpAddressesFromPeer ( ) .
*
* @ returns An Object containing the status about the active audio peer .
*
* For more information see :
* https : //developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/getStats
2021-08-23 23:36:01 +08:00
* and
* https : //developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport
2021-08-13 20:46:19 +08:00
* /
2021-08-13 03:39:04 +08:00
async getStats ( ) {
const bridge = this . getCurrentBridge ( ) ;
if ( ! bridge ) return null ;
const peer = bridge . getPeerConnection ( ) ;
if ( ! peer ) return null ;
const peerStats = await peer . getStats ( ) ;
const audioStats = { } ;
peerStats . forEach ( ( stat ) => {
if ( FILTER _AUDIO _STATS . includes ( stat . type ) ) {
2021-08-26 03:27:46 +08:00
audioStats [ stat . id ] = stat ;
2021-08-13 03:39:04 +08:00
}
} ) ;
const transportStats = await this
2021-08-26 03:27:46 +08:00
. getInternalExternalIpAddresses ( audioStats ) ;
2021-08-13 03:39:04 +08:00
return { transportStats , ... audioStats } ;
}
2017-09-20 01:47:57 +08:00
}
const audioManager = new AudioManager ( ) ;
export default audioManager ;