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" ;
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' ;
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' ,
} ;
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
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 ) {
2019-03-09 03:41:19 +08:00
this . bridge = new SIPBridge ( userData ) ; // no alternative as of 2019-03-08
2018-09-12 01:09:29 +08:00
if ( this . useKurento ) {
2018-05-02 23:49:57 +08:00
this . listenOnlyBridge = new KurentoBridge ( 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
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
}
2018-03-16 02:57:25 +08:00
joinMicrophone ( ) {
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 ( ) {
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 ) ( )
2019-06-13 05:01:20 +08:00
. then ( ( ) => {
const callOptions = {
isListenOnly : false ,
extension : ECHO _TEST _NUMBER ,
inputStream : this . inputStream ,
} ;
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 ) {
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
} ) ;
}
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' ) ;
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
}
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 ) {
this . inputStream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
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 ) {
2019-04-12 06:53:57 +08:00
this . notify ( this . intl . formatMessage ( this . messages . info . LEFT _AUDIO ) , false , 'audio_off' ) ;
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 ) {
const handleChangeInputDeviceSuccess = ( inputDevice ) => {
this . inputDevice = inputDevice ;
return Promise . resolve ( inputDevice ) ;
} ;
this . bridge . liveChangeInputDevice ( deviceId ) . then ( handleChangeInputDeviceSuccess ) ;
}
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 ( ) {
2018-04-13 20:39:26 +08:00
this . _inputDevice . tracker . depend ( ) ;
2021-01-30 06:05:51 +08:00
return ( this . bridge ? this . bridge . inputStream : null ) ;
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-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 ) {
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 ( ! url ) {
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 ) ;
}
2017-09-20 01:47:57 +08:00
}
const audioManager = new AudioManager ( ) ;
export default audioManager ;