From cc7513279eb6cfd6bb0dc8714885043413b484e0 Mon Sep 17 00:00:00 2001 From: Chad Pilkey <capilkey@gmail.com> Date: Wed, 12 Jun 2019 21:01:20 +0000 Subject: [PATCH] IPv4 FS audio fallback, auto-reconnect, and fixed input select --- .../imports/api/audio/client/bridge/base.js | 1 + .../imports/api/audio/client/bridge/sip.js | 217 +++++++++++++----- .../components/audio/audio-modal/styles.scss | 11 +- .../imports/ui/components/audio/container.jsx | 5 + .../ui/services/audio-manager/index.js | 62 +++-- .../private/config/settings.yml | 1 + bigbluebutton-html5/private/locales/en.json | 1 + .../public/resources/sounds/LeftCall.mp3 | Bin 0 -> 20257 bytes 8 files changed, 218 insertions(+), 80 deletions(-) create mode 100644 bigbluebutton-html5/public/resources/sounds/LeftCall.mp3 diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/base.js b/bigbluebutton-html5/imports/api/audio/client/bridge/base.js index a9c63969a2..c2155b08d7 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/base.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/base.js @@ -16,6 +16,7 @@ export default class BaseAudioBridge { started: 'started', ended: 'ended', failed: 'failed', + reconnecting: 'reconnecting', }; } diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index 92d6542bd3..d42f1337e5 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -11,38 +11,18 @@ const MEDIA_TAG = MEDIA.mediaTag; const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout; const CALL_HANGUP_TIMEOUT = MEDIA.callHangupTimeout; const CALL_HANGUP_MAX_RETRIES = MEDIA.callHangupMaximumRetries; +const IPV4_FALLBACK_DOMAIN = Meteor.settings.public.app.ipv4FallbackDomain; const ICE_NEGOTIATION_FAILED = ['iceConnectionFailed']; const CALL_CONNECT_TIMEOUT = 15000; const ICE_NEGOTIATION_TIMEOUT = 20000; -export default class SIPBridge extends BaseAudioBridge { - constructor(userData) { - super(userData); - - const { - userId, - username, - sessionToken, - } = userData; - - this.user = { - userId, - sessionToken, - name: username, - }; - - this.media = { - inputDevice: {}, - }; - - this.protocol = window.document.location.protocol; - this.hostname = window.document.location.hostname; - - // SDP conversion utilitary methods to be used inside SIP.js - window.isUnifiedPlan = isUnifiedPlan; - window.toUnifiedPlan = toUnifiedPlan; - window.toPlanB = toPlanB; - window.stripMDnsCandidates = stripMDnsCandidates; +class SIPSession { + constructor(user, userData, protocol, hostname, baseCallStates) { + this.user = user; + this.userData = userData; + this.protocol = protocol; + this.hostname = hostname; + this.baseCallStates = baseCallStates; } static parseDTMF(message) { @@ -58,11 +38,25 @@ export default class SIPBridge extends BaseAudioBridge { const callExtension = extension ? `${extension}${this.userData.voiceBridge}` : this.userData.voiceBridge; const callback = (message) => { + // There will sometimes we erroneous errors put out like timeouts and improper shutdowns, + // but only the first error ever matters + if (this.alreadyErrored) { + logger.info({ logCode: 'sip_js_absorbing_callback_message' }, `Absorbing a redundant callback message. ${JSON.stringify(message)}`); + return; + } + + if (message.status === this.baseCallStates.failed) { + this.alreadyErrored = true; + } + managerCallback(message).then(resolve); }; this.callback = callback; + // If there's an extension passed it means that we're joining the echo test first + this.inEchoTest = !!extension; + return this.doCall({ callExtension, isListenOnly, inputStream }) .catch((reason) => { reject(reason); @@ -98,6 +92,8 @@ export default class SIPBridge extends BaseAudioBridge { transferCall(onTransferSuccess) { return new Promise((resolve, reject) => { + this.inEchoTest = false; + const timeout = setInterval(() => { clearInterval(timeout); logger.error({ logCode: 'sip_js_transfer_timed_out' }, 'Timeout on transfering from echo test to conference'); @@ -114,7 +110,7 @@ export default class SIPBridge extends BaseAudioBridge { this.currentSession.on('dtmf', (event) => { if (event.body && (typeof event.body === 'string')) { - const key = SIPBridge.parseDTMF(event.body); + const key = SIPSession.parseDTMF(event.body); if (key === '7') { clearInterval(timeout); onTransferSuccess(); @@ -141,6 +137,8 @@ export default class SIPBridge extends BaseAudioBridge { } this.currentSession.bye(); + this.userAgent.stop(); + hangupRetries += 1; setTimeout(() => { @@ -178,8 +176,6 @@ export default class SIPBridge extends BaseAudioBridge { callerIdName, } = this.user; - let userAgentConnected = false; - // WebView safari needs a transceiver to be added. Made it a SIP.js hack. // Don't like the UA picking though, we should straighten everything to user // transceivers - prlanzarin 2019/05/21 @@ -193,7 +189,18 @@ export default class SIPBridge extends BaseAudioBridge { logger.debug('Creating the user agent'); - let userAgent = new window.SIP.UA({ + if (this.userAgent && this.userAgent.isConnected()) { + if (this.userAgent.configuration.hostPortParams === this.hostname) { + logger.debug('Reusing the user agent'); + resolve(this.userAgent); + return; + } + logger.debug('different host name. need to kill'); + } + + let userAgentConnected = false; + + this.userAgent = new window.SIP.UA({ uri: `sip:${encodeURIComponent(callerIdName)}@${hostname}`, wsServers: `${(protocol === 'https:' ? 'wss://' : 'ws://')}${hostname}/ws`, displayName: callerIdName, @@ -207,21 +214,23 @@ export default class SIPBridge extends BaseAudioBridge { hackAddAudioTransceiver: isSafariWebview, }); - userAgent.removeAllListeners('connected'); - userAgent.removeAllListeners('disconnected'); - const handleUserAgentConnection = () => { userAgentConnected = true; - resolve(userAgent); + resolve(this.userAgent); }; const handleUserAgentDisconnection = () => { - userAgent.stop(); - userAgent = null; + if (this.userAgent) { + this.userAgent.removeAllListeners(); + this.userAgent.stop(); + this.userAgent = null; + } let error; let bridgeError; + if (this.userRequestedHangup) return; + if (userAgentConnected) { error = 1001; bridgeError = 'Websocket disconnected'; @@ -238,10 +247,10 @@ export default class SIPBridge extends BaseAudioBridge { reject(this.baseErrorCodes.CONNECTION_ERROR); }; - userAgent.on('connected', handleUserAgentConnection); - userAgent.on('disconnected', handleUserAgentDisconnection); + this.userAgent.on('connected', handleUserAgentConnection); + this.userAgent.on('disconnected', handleUserAgentDisconnection); - userAgent.start(); + this.userAgent.start(); }); } @@ -279,8 +288,10 @@ export default class SIPBridge extends BaseAudioBridge { return new Promise((resolve) => { const { mediaHandler } = currentSession; - this.connectionCompleted = false; - this.inEcho = false; + let iceCompleted = false; + let fsReady = false; + + this.currentSession = currentSession; let connectionCompletedEvents = ['iceConnectionCompleted', 'iceConnectionConnected']; // Edge sends a connected first and then a completed, but the call isn't ready until @@ -291,7 +302,8 @@ export default class SIPBridge extends BaseAudioBridge { } const checkIfCallReady = () => { - if (this.connectionCompleted && this.inEcho) { + if (iceCompleted && fsReady) { + this.webrtcConnected = true; this.callback({ status: this.baseCallStates.started }); resolve(); } @@ -312,9 +324,10 @@ export default class SIPBridge extends BaseAudioBridge { const handleSessionAccepted = () => { logger.info({ logCode: 'sip_js_session_accepted' }, 'Audio call session accepted'); clearTimeout(callTimeout); + currentSession.off('accepted', handleSessionAccepted); // If ICE isn't connected yet then start timeout waiting for ICE to finish - if (!this.connectionCompleted) { + if (!iceCompleted) { iceNegotiationTimeout = setTimeout(() => { this.callback({ status: this.baseCallStates.failed, @@ -338,7 +351,7 @@ export default class SIPBridge extends BaseAudioBridge { clearTimeout(callTimeout); clearTimeout(iceNegotiationTimeout); connectionCompletedEvents.forEach(e => mediaHandler.off(e, handleConnectionCompleted)); - this.connectionCompleted = true; + iceCompleted = true; checkIfCallReady(); }; @@ -347,6 +360,8 @@ export default class SIPBridge extends BaseAudioBridge { const handleSessionTerminated = (message, cause) => { clearTimeout(callTimeout); clearTimeout(iceNegotiationTimeout); + currentSession.off('terminated', handleSessionTerminated); + if (!message && !cause && !!this.userRequestedHangup) { return this.callback({ status: this.baseCallStates.ended, @@ -356,7 +371,7 @@ export default class SIPBridge extends BaseAudioBridge { logger.error({ logCode: 'sip_js_call_terminated' }, `Audio call terminated. cause=${cause}`); let mappedCause; - if (!this.connectionCompleted) { + if (!iceCompleted) { mappedCause = '1004'; } else { mappedCause = '1005'; @@ -371,7 +386,7 @@ export default class SIPBridge extends BaseAudioBridge { currentSession.on('terminated', handleSessionTerminated); const handleIceNegotiationFailed = (peer) => { - if (this.connectionCompleted) { + if (iceCompleted) { logger.error({ logCode: 'sipjs_ice_failed_after' }, 'ICE connection failed after success'); } else { logger.error({ logCode: 'sipjs_ice_failed_before' }, 'ICE connection failed before success'); @@ -404,44 +419,131 @@ export default class SIPBridge extends BaseAudioBridge { const inEchoDTMF = (event) => { if (event.body && typeof event.body === 'string') { - const dtmf = SIPBridge.parseDTMF(event.body); + const dtmf = SIPSession.parseDTMF(event.body); if (dtmf === '0') { - this.inEcho = true; + fsReady = true; checkIfCallReady(); } } currentSession.off('dtmf', inEchoDTMF); }; currentSession.on('dtmf', inEchoDTMF); + }); + } +} - this.currentSession = currentSession; +export default class SIPBridge extends BaseAudioBridge { + constructor(userData) { + super(userData); + + const { + userId, + username, + sessionToken, + } = userData; + + this.user = { + userId, + sessionToken, + name: username, + }; + + this.media = { + inputDevice: {}, + }; + + this.protocol = window.document.location.protocol; + this.hostname = window.document.location.hostname; + + // SDP conversion utilitary methods to be used inside SIP.js + window.isUnifiedPlan = isUnifiedPlan; + window.toUnifiedPlan = toUnifiedPlan; + window.toPlanB = toPlanB; + window.stripMDnsCandidates = stripMDnsCandidates; + } + + joinAudio({ isListenOnly, extension, inputStream }, managerCallback) { + const hasFallbackDomain = typeof IPV4_FALLBACK_DOMAIN === 'string' && IPV4_FALLBACK_DOMAIN !== ''; + + return new Promise((resolve, reject) => { + let { hostname } = this; + + this.activeSession = new SIPSession(this.user, this.userData, this.protocol, hostname, this.baseCallStates); + + const callback = (message) => { + if (message.status === this.baseCallStates.failed) { + let shouldTryReconnect = false; + + // Try and get the call to clean up and end on an error + this.activeSession.exitAudio().catch(() => {}); + + if (this.activeSession.webrtcConnected) { + // webrtc was able to connect so just try again + message.silenceNotifications = true; + callback({ status: this.baseCallStates.reconnecting }); + shouldTryReconnect = true; + } else if (hasFallbackDomain === true && hostname !== IPV4_FALLBACK_DOMAIN) { + message.silenceNotifications = true; + logger.info({ logCode: 'sip_js_attempt_ipv4_fallback' }, 'Attempting to fallback to IPv4 domain for audio'); + hostname = IPV4_FALLBACK_DOMAIN; + shouldTryReconnect = true; + } + + if (shouldTryReconnect) { + const fallbackExtension = this.activeSession.inEchoTest ? extension : undefined; + this.activeSession = new SIPSession(this.user, this.userData, this.protocol, hostname, this.baseCallStates); + this.activeSession.joinAudio({ isListenOnly, extension: fallbackExtension, inputStream }, callback) + .then((value) => { + resolve(value); + }).catch((reason) => { + reject(reason); + }); + } + } + + return managerCallback(message); + }; + + this.activeSession.joinAudio({ isListenOnly, extension, inputStream }, callback) + .then((value) => { + resolve(value); + }).catch((reason) => { + reject(reason); + }); }); } + transferCall(onTransferSuccess) { + return this.activeSession.transferCall(onTransferSuccess); + } + + exitAudio() { + return this.activeSession.exitAudio(); + } + setDefaultInputDevice() { const handleMediaSuccess = (mediaStream) => { const deviceLabel = mediaStream.getAudioTracks()[0].label; window.defaultInputStream = mediaStream.getTracks(); return navigator.mediaDevices.enumerateDevices().then((mediaDevices) => { const device = mediaDevices.find(d => d.label === deviceLabel); - return this.changeInputDevice(device.deviceId); + return this.changeInputDevice(device.deviceId, deviceLabel); }); }; return navigator.mediaDevices.getUserMedia({ audio: true }).then(handleMediaSuccess); } - changeInputDevice(value) { + changeInputDevice(deviceId, deviceLabel) { const { media, } = this; - if (media.inputDevice.audioContext) { const handleAudioContextCloseSuccess = () => { media.inputDevice.audioContext = null; media.inputDevice.scriptProcessor = null; media.inputDevice.source = null; - return this.changeInputDevice(value); + return this.changeInputDevice(deviceId); }; return media.inputDevice.audioContext.close().then(handleAudioContextCloseSuccess); @@ -453,14 +555,15 @@ export default class SIPBridge extends BaseAudioBridge { media.inputDevice.audioContext = new window.webkitAudioContext(); } - media.inputDevice.id = value; + media.inputDevice.id = deviceId; + media.inputDevice.label = deviceLabel; media.inputDevice.scriptProcessor = media.inputDevice.audioContext .createScriptProcessor(2048, 1, 1); media.inputDevice.source = null; const constraints = { audio: { - deviceId: value, + deviceId, }, }; diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss index bbbefe01c0..c42b8f4c8a 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/styles.scss @@ -162,14 +162,17 @@ } .connectingAnimation { + margin: auto; + display: inline-block; + width: 1.5em; + &:after { overflow: hidden; display: inline-block; vertical-align: bottom; content: "\2026"; /* ascii code for the ellipsis character */ width: 0; - margin-right: 0.75em; - margin-left: 0.75em; + margin-left: 0.25em; :global(.animationsEnabled) & { animation: ellipsis steps(4, end) 900ms infinite; @@ -179,9 +182,7 @@ @keyframes ellipsis { to { - width: 1.25em; - margin-right: 0; - margin-left: 0; + width: 1.5em; } } diff --git a/bigbluebutton-html5/imports/ui/components/audio/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/container.jsx index 7ef508254e..e26257d92f 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/container.jsx @@ -24,6 +24,10 @@ const intlMessages = defineMessages({ id: 'app.audioManager.leftAudio', description: 'Left audio toast message', }, + reconnectingAudio: { + id: 'app.audioManager.reconnectingAudio', + description: 'Reconnecting audio toast message', + }, genericError: { id: 'app.audioManager.genericError', description: 'Generic error message', @@ -116,6 +120,7 @@ export default withModalMounter(injectIntl(withTracker(({ mountModal, intl }) => JOINED_AUDIO: intlMessages.joinedAudio, JOINED_ECHO: intlMessages.joinedEcho, LEFT_AUDIO: intlMessages.leftAudio, + RECONNECTING_AUDIO: intlMessages.reconnectingAudio, }, error: { GENERIC_ERROR: intlMessages.genericError, diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 740fc59d84..2f370a6195 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -19,6 +19,7 @@ const CALL_STATES = { STARTED: 'started', ENDED: 'ended', FAILED: 'failed', + RECONNECTING: 'reconnecting', }; class AudioManager { @@ -81,6 +82,12 @@ class AudioManager { } askDevicesPermissions() { + // Check to see if the stream has already been retrieved becasue then we don't need to + // request. This is a fix for an issue with the input device selector. + if (this.inputStream) { + return Promise.resolve(); + } + // Only change the isWaitingPermissions for the case where the user didnt allowed it yet const permTimeout = setTimeout(() => { if (!this.devicesInitialized) { this.isWaitingPermissions = true; } @@ -107,35 +114,38 @@ class AudioManager { this.isListenOnly = false; this.isEchoTest = false; - const callOptions = { - isListenOnly: false, - extension: null, - inputStream: this.inputStream, - }; - return this.askDevicesPermissions() .then(this.onAudioJoining.bind(this)) - .then(() => this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this))); + .then(() => { + const callOptions = { + isListenOnly: false, + extension: null, + inputStream: this.inputStream, + }; + return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)); + }); } joinEchoTest() { this.isListenOnly = false; this.isEchoTest = true; - const callOptions = { - isListenOnly: false, - extension: ECHO_TEST_NUMBER, - inputStream: this.inputStream, - }; - return this.askDevicesPermissions() .then(this.onAudioJoining.bind(this)) - .then(() => this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this))); + .then(() => { + const callOptions = { + isListenOnly: false, + extension: ECHO_TEST_NUMBER, + inputStream: this.inputStream, + }; + return this.bridge.joinAudio(callOptions, this.callStateCallback.bind(this)); + }); } async joinListenOnly(retries = 0) { this.isListenOnly = true; this.isEchoTest = false; + const { name } = browser(); // The kurento bridge isn't a full audio bridge yet, so we have to differ it const bridge = this.useKurento ? this.listenOnlyBridge : this.bridge; @@ -222,7 +232,6 @@ class AudioManager { const bridge = (this.useKurento && this.isListenOnly) ? this.listenOnlyBridge : this.bridge; this.isHangingUp = true; - this.isEchoTest = false; return bridge.exitAudio(); } @@ -289,6 +298,10 @@ class AudioManager { if (!this.error && !this.isEchoTest) { this.notify(this.intl.formatMessage(this.messages.info.LEFT_AUDIO), false, 'audio_off'); } + if (!this.isEchoTest) { + this.playHangUpSound(); + } + window.parent.postMessage({ response: 'notInAudio' }, '*'); } @@ -298,12 +311,14 @@ class AudioManager { STARTED, ENDED, FAILED, + RECONNECTING, } = CALL_STATES; const { status, error, bridgeError, + silenceNotifications, } = response; if (status === STARTED) { @@ -316,10 +331,16 @@ class AudioManager { const errorKey = this.messages.error[error] || this.messages.error.GENERIC_ERROR; const errorMsg = this.intl.formatMessage(errorKey, { 0: bridgeError }); this.error = !!error; - this.notify(errorMsg, true); logger.error({ logCode: 'audio_failure', error, cause: bridgeError }, `Audio Error ${JSON.stringify(errorMsg)}`); - this.exitAudio(); - this.onAudioExit(); + if (silenceNotifications !== true) { + this.notify(errorMsg, true); + this.exitAudio(); + this.onAudioExit(); + } + } else if (status === RECONNECTING) { + logger.info({ logCode: 'audio_reconnecting' }, 'Attempting to reconnect audio'); + this.notify(this.intl.formatMessage(this.messages.info.RECONNECTING_AUDIO), true); + this.playHangUpSound(); } }); } @@ -409,6 +430,11 @@ class AudioManager { return this._userData; } + playHangUpSound() { + this.alert = new Audio(`${Meteor.settings.public.app.cdn + Meteor.settings.public.app.basename}/resources/sounds/LeftCall.mp3`); + this.alert.play(); + } + notify(message, error = false, icon = 'unmute') { const audioIcon = this.isListenOnly ? 'listen' : icon; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 8c45ed80c8..c5495a5d17 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -20,6 +20,7 @@ public: askForFeedbackOnLogout: false allowUserLookup: false enableNetworkInformation: false + ipv4FallbackDomain: "" defaultSettings: application: animations: true diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index cd59a3c82e..cd227e18b0 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -394,6 +394,7 @@ "app.audioManager.joinedAudio": "You have joined the audio conference", "app.audioManager.joinedEcho": "You have joined the echo test", "app.audioManager.leftAudio": "You have left the audio conference", + "app.audioManager.reconnectingAudio": "Attempting to reconnect audio", "app.audioManager.genericError": "Error: An error has occurred, please try again", "app.audioManager.connectionError": "Error: Connection error", "app.audioManager.requestTimeout": "Error: There was a timeout in the request", diff --git a/bigbluebutton-html5/public/resources/sounds/LeftCall.mp3 b/bigbluebutton-html5/public/resources/sounds/LeftCall.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..eaead7041bb34072a4f79bdef53c827f8009ecc7 GIT binary patch literal 20257 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 T!1xd7S_R<nE6~*})NnNb?Vo{S literal 0 HcmV?d00001 -- GitLab