diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index 7f1e4aeffda6bf3ab08218edeb2837bdeb394aad..3d4625731c6e82eb82729a24e1921e4e6cd35d57 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -1,6 +1,6 @@ import BaseAudioBridge from './base'; import Auth from '/imports/ui/services/auth'; -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; import logger from '/imports/startup/client/logger'; @@ -64,6 +64,7 @@ export default class KurentoAudioBridge extends BaseAudioBridge { } catch (error) { logger.error({ logCode: 'sfuaudiobridge_stunturn_fetch_failed' }, 'SFU audio bridge failed to fetch STUN/TURN info, using default servers'); + iceServers = getMappedFallbackStun(); } finally { logger.debug({ logCode: 'sfuaudiobridge_stunturn_fetch_sucess', extraInfo: { iceServers } }, 'SFU audio bridge got STUN/TURN servers'); diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index 6411026f5477d95647b3cb8c8b00dd1f767679c1..55688ef261e5c3c86fbc1e23b0c46a08b99513dd 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -1,7 +1,7 @@ import browser from 'browser-detect'; import BaseAudioBridge from './base'; import logger from '/imports/startup/client/logger'; -import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchStunTurnServers, getFallbackStun } from '/imports/utils/fetchStunTurnServers'; import { isUnifiedPlan, toUnifiedPlan, @@ -85,6 +85,22 @@ class SIPSession { }); } + async getIceServers (sessionToken) { + try { + const iceServers = await fetchStunTurnServers(sessionToken); + return iceServers; + } catch (error) { + logger.error({ + logCode: 'sip_js_fetchstunturninfo_error', + extraInfo: { + errorCode: error.code, + errorMessage: error.message, + }, + }, 'Full audio bridge failed to fetch STUN/TURN info'); + return getFallbackStun(); + } + } + doCall(options) { const { isListenOnly, @@ -105,7 +121,7 @@ class SIPSession { this.user.callerIdName = callerIdName; this.callOptions = options; - return fetchStunTurnServers(sessionToken) + return this.getIceServers(sessionToken) .then(this.createUserAgent.bind(this)) .then(this.inviteUserAgent.bind(this)) .then(this.setupEventHandlers.bind(this)); diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index 3ad3aa1c251133caebe974ee5a7c9b34479a732d..47a1401d1cce0728daf290b0bfd51a13c616a741 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -1,6 +1,6 @@ import Auth from '/imports/ui/services/auth'; import BridgeService from './service'; -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; import logger from '/imports/startup/client/logger'; @@ -72,6 +72,7 @@ export default class KurentoScreenshareBridge { } catch (error) { logger.error({ logCode: 'screenshare_viwer_fetchstunturninfo_error', extraInfo: { error } }, 'Screenshare bridge failed to fetch STUN/TURN info, using default'); + iceServers = getMappedFallbackStun(); } finally { const options = { wsUrl: Auth.authenticateURL(SFU_URL), @@ -168,6 +169,7 @@ export default class KurentoScreenshareBridge { } catch (error) { logger.error({ logCode: 'screenshare_presenter_fetchstunturninfo_error' }, 'Screenshare bridge failed to fetch STUN/TURN info, using default'); + iceServers = getMappedFallbackStun(); } finally { const options = { wsUrl: Auth.authenticateURL(SFU_URL), diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index d2ef0306a02da5e2b7fd39ae08466c443425ec95..79070fa000d2745a506106873be269b3927e2f2b 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -3,7 +3,10 @@ import { defineMessages, injectIntl } from 'react-intl'; import { Session } from 'meteor/session'; import { notify } from '/imports/ui/services/notification'; import VisibilityEvent from '/imports/utils/visibilityEvent'; -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { + fetchWebRTCMappedStunTurnServers, + getMappedFallbackStun, +} from '/imports/utils/fetchStunTurnServers'; import PropTypes from 'prop-types'; import ReconnectingWebSocket from 'reconnecting-websocket'; import logger from '/imports/startup/client/logger'; @@ -608,9 +611,12 @@ class VideoProvider extends Component { logger.error({ logCode: 'video_provider_fetchstunturninfo_error', extraInfo: { - error, + errorCode: error.code, + errorMessage: error.message, }, }, 'video-provider failed to fetch STUN/TURN info, using default'); + // Use fallback STUN server + iceServers = getMappedFallbackStun(); } finally { const { constraints, bitrate, id: profileId } = VideoProvider.getCameraProfile(); this.outboundIceQueues[id] = []; diff --git a/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js b/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js index ee90b03d21796a26e44d15e2a8f3f7a399802563..4400364e503f6e46ec1148db4a870904905541ff 100644 --- a/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js +++ b/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js @@ -2,11 +2,18 @@ import _ from 'lodash'; const MEDIA = Meteor.settings.public.media; const STUN_TURN_FETCH_URL = MEDIA.stunTurnServersFetchAddress; +const CACHE_STUN_TURN = MEDIA.cacheStunTurnServers; +const FALLBACK_STUN_SERVER = MEDIA.fallbackStunServer; + +let STUN_TURN_DICT; +let MAPPED_STUN_TURN_DICT; const fetchStunTurnServers = function (sessionToken) { + if (STUN_TURN_DICT && CACHE_STUN_TURN) return Promise.resolve(STUN_TURN_DICT); + const handleStunTurnResponse = ({ stunServers, turnServers }) => { if (!stunServers && !turnServers) { - return { error: 404, stun: [], turn: [] }; + return Promise.reject(new Error('Could not fetch STUN/TURN servers')); } const turnReply = []; @@ -19,35 +26,57 @@ const fetchStunTurnServers = function (sessionToken) { }); }); - return { + const stDictionary = { stun: stunServers.map(server => server.url), turn: turnReply, }; + + STUN_TURN_DICT = stDictionary; + + return Promise.resolve(stDictionary); }; const url = `${STUN_TURN_FETCH_URL}?sessionToken=${sessionToken}`; return fetch(url, { credentials: 'same-origin' }) .then(res => res.json()) .then(handleStunTurnResponse) - .then((response) => { - if (response.error) { - return Promise.reject('Could not fetch the stuns/turns servers!'); - } - return response; - }); }; +const mapStunTurn = ({ stun, turn }) => { + const rtcStuns = stun.map(url => ({ urls: url })); + const rtcTurns = turn.map(t => ({ urls: t.urls, credential: t.password, username: t.username })); + return rtcStuns.concat(rtcTurns); +}; + +const getFallbackStun = () => { + const stun = FALLBACK_STUN_SERVER ? [FALLBACK_STUN_SERVER] : [] + return { stun, turn: [] }; +} + +const getMappedFallbackStun = () => { + return FALLBACK_STUN_SERVER ? [{ urls: FALLBACK_STUN_SERVER }] : []; +} + const fetchWebRTCMappedStunTurnServers = function (sessionToken) { return new Promise(async (resolve, reject) => { try { - const { stun, turn } = await fetchStunTurnServers(sessionToken); - const rtcStuns = stun.map(url => ({ urls: url })); - const rtcTurns = turn.map(t => ({ urls: t.urls, credential: t.password, username: t.username })); - return resolve(rtcStuns.concat(rtcTurns)); + if (MAPPED_STUN_TURN_DICT && CACHE_STUN_TURN) { + return resolve(MAPPED_STUN_TURN_DICT); + } + + const stDictionary = await fetchStunTurnServers(sessionToken); + + MAPPED_STUN_TURN_DICT = mapStunTurn(stDictionary); + return resolve(MAPPED_STUN_TURN_DICT); } catch (error) { return reject(error); } }); }; -export { fetchStunTurnServers, fetchWebRTCMappedStunTurnServers }; +export { + fetchStunTurnServers, + fetchWebRTCMappedStunTurnServers, + getFallbackStun, + getMappedFallbackStun, +}; diff --git a/bigbluebutton-html5/imports/utils/safari-webrtc.js b/bigbluebutton-html5/imports/utils/safari-webrtc.js index f1101cc146a0834cf999d86832d108acdd86df10..01d1149f7c1c1359f14f7e333394ca52cbb675c2 100644 --- a/bigbluebutton-html5/imports/utils/safari-webrtc.js +++ b/bigbluebutton-html5/imports/utils/safari-webrtc.js @@ -1,34 +1,19 @@ -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { + fetchWebRTCMappedStunTurnServers, + getMappedFallbackStun, +} from '/imports/utils/fetchStunTurnServers'; import Auth from '/imports/ui/services/auth'; import { Session } from 'meteor/session'; import logger from '/imports/startup/client/logger'; -const defaultIceServersList = [ - { urls: 'stun:stun.l.google.com:19302' }, - { urls: 'stun:stun1.l.google.com:19302' }, - { urls: 'stun:stun2.l.google.com:19302' }, - { urls: 'stun:stun3.l.google.com:19302' }, - { urls: 'stun:stun4.l.google.com:19302' }, - { urls: 'stun:stun.ekiga.net' }, - { urls: 'stun:stun.ideasip.com' }, - { urls: 'stun:stun.schlund.de' }, - { urls: 'stun:stun.stunprotocol.org:3478' }, - { urls: 'stun:stun.voiparound.com' }, - { urls: 'stun:stun.voipbuster.com' }, - { urls: 'stun:stun.voipstunt.com' }, - { urls: 'stun:stun.voxgratia.org' }, - { urls: 'stun:stun.services.mozilla.com' }, -]; - const getSessionToken = () => Auth.sessionToken; export async function getIceServersList() { try { const iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken()); - - return iceServers || defaultIceServersList; + return iceServers; } catch (error) { - return defaultIceServersList; + return getMappedFallbackStun(); } } diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index b40d1296e3782ce5725498319ac43cce96a61e7d..3959c8777cc7958b5e38f3031d78a37192d1bf8d 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -170,6 +170,8 @@ public: hidePresentation: false media: stunTurnServersFetchAddress: "/bigbluebutton/api/stuns" + cacheStunTurnServers: true + fallbackStunServer: '' mediaTag: "#remote-media" callTransferTimeout: 5000 callHangupTimeout: 2000