diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index 97f0cbbe95..812d9f1c9f 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -8,6 +8,7 @@ import { getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers'; import getFromMeetingSettings from '/imports/ui/services/meeting-settings'; +import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils'; const SFU_URL = Meteor.settings.public.kurento.wsUrl; const DEFAULT_LISTENONLY_MEDIA_SERVER = Meteor.settings.public.kurento.listenOnlyMediaServer; @@ -267,6 +268,7 @@ export default class KurentoAudioBridge extends BaseAudioBridge { offering: OFFERING, mediaServer: getMediaServerAdapter(), signalCandidates: SIGNAL_CANDIDATES, + forceRelay: shouldForceRelay(), }; this.broker = new ListenOnlyBroker( diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index 157dafa555..e63eac9d8d 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -4,6 +4,7 @@ import BridgeService from './service'; import ScreenshareBroker from '/imports/ui/services/bbb-webrtc-sfu/screenshare-broker'; import { setSharingScreen, screenShareEndAlert } from '/imports/ui/components/screenshare/service'; import { SCREENSHARING_ERRORS } from './errors'; +import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils'; const SFU_CONFIG = Meteor.settings.public.kurento; const SFU_URL = SFU_CONFIG.wsUrl; @@ -227,6 +228,7 @@ export default class KurentoScreenshareBridge { offering: OFFERING, mediaServer: BridgeService.getMediaServerAdapter(), signalCandidates: SIGNAL_CANDIDATES, + forceRelay: shouldForceRelay(), }; this.broker = new ScreenshareBroker( @@ -287,6 +289,7 @@ export default class KurentoScreenshareBridge { offering: true, mediaServer: BridgeService.getMediaServerAdapter(), signalCandidates: SIGNAL_CANDIDATES, + forceRelay: shouldForceRelay(), }; this.broker = new ScreenshareBroker( diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx index 61629d74db..d85dd782f2 100755 --- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx @@ -19,6 +19,7 @@ import { getSessionVirtualBackgroundInfo, } from '/imports/ui/services/virtual-background/service'; import { notify } from '/imports/ui/services/notification'; +import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils'; // Default values and default empty object to be backwards compat with 2.2. // FIXME Remove hardcoded defaults 2.3. @@ -605,6 +606,9 @@ class VideoProvider extends Component { video: constraints, }, onicecandidate: this._getOnIceCandidateCallback(stream, isLocal), + configuration: { + iceTransportPolicy: shouldForceRelay() ? 'relay' : undefined, + } }; try { @@ -623,7 +627,6 @@ class VideoProvider extends Component { iceServers = getMappedFallbackStun(); } finally { if (iceServers.length > 0) { - peerOptions.configuration = {}; peerOptions.configuration.iceServers = iceServers; } diff --git a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/listenonly-broker.js b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/listenonly-broker.js index b5e606c24c..f98eb5e129 100644 --- a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/listenonly-broker.js +++ b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/listenonly-broker.js @@ -34,10 +34,9 @@ class ListenOnlyBroker extends BaseBroker { video: false, }, onicecandidate: this.signalCandidates ? this.onIceCandidate.bind(this) : null, + configuration: this.populatePeerConfiguration(), }; - this.addIceServers(options); - this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, (error) => { if (error) { // 1305: "PEER_NEGOTIATION_FAILED", diff --git a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/screenshare-broker.js b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/screenshare-broker.js index 589ff51d58..adaedc9e62 100644 --- a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/screenshare-broker.js +++ b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/screenshare-broker.js @@ -157,9 +157,9 @@ class ScreenshareBroker extends BaseBroker { const options = { onicecandidate: this.signalCandidates ? this.onIceCandidate.bind(this) : null, videoStream: this.stream, + configuration: this.populatePeerConfiguration(), }; - this.addIceServers(options); this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, (error) => { if (error) { // 1305: "PEER_NEGOTIATION_FAILED", @@ -226,10 +226,9 @@ class ScreenshareBroker extends BaseBroker { audio: !!this.hasAudio, }, onicecandidate: this.signalCandidates ? this.onIceCandidate.bind(this) : null, + configuration: this.populatePeerConfiguration(), }; - this.addIceServers(options); - this.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, (error) => { if (error) { // 1305: "PEER_NEGOTIATION_FAILED", diff --git a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/sfu-base-broker.js b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/sfu-base-broker.js index 6916df0a97..ecde2e88d8 100644 --- a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/sfu-base-broker.js +++ b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/sfu-base-broker.js @@ -25,6 +25,7 @@ class BaseBroker { this.started = false; this.signallingTransportOpen = false; this.logCodePrefix = `${this.sfuComponent}_broker`; + this.peerConfiguration = {}; this.onbeforeunload = this.onbeforeunload.bind(this); window.addEventListener('beforeunload', this.onbeforeunload); @@ -168,13 +169,23 @@ class BaseBroker { } } - addIceServers (options) { - if (this.iceServers && this.iceServers.length > 0) { - options.configuration = {}; - options.configuration.iceServers = this.iceServers; + populatePeerConfiguration () { + this.addIceServers(); + if (this.forceRelay) { + this.setRelayTransportPolicy(); } - return options; + return this.peerConfiguration; + } + + addIceServers () { + if (this.iceServers && this.iceServers.length > 0) { + this.peerConfiguration.iceServers = this.iceServers; + } + } + + setRelayTransportPolicy () { + this.peerConfiguration.iceTransportPolicy = 'relay'; } handleConnectionStateChange (eventIdentifier) { diff --git a/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/utils.js b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/utils.js new file mode 100644 index 0000000000..15cbbc905a --- /dev/null +++ b/bigbluebutton-html5/imports/ui/services/bbb-webrtc-sfu/utils.js @@ -0,0 +1,23 @@ +import browserInfo from '/imports/utils/browserInfo'; +import deviceInfo from '/imports/utils/deviceInfo'; + +const FORCE_RELAY_ON_FF = Meteor.settings.public.kurento.forceRelayOnFirefox; + +/* + * Whether TURN/relay usage should be forced to work around Firefox's lack of + * support for regular nomination when dealing with ICE-litee peers (e.g.: + * mediasoup). See: https://bugzilla.mozilla.org/show_bug.cgi?id=1034964 + * + * iOS endpoints are ignored from the trigger because _all_ iOS browsers + * are either native WebKit or WKWebView based (so they shouldn't be affected) + */ +const shouldForceRelay = () => { + const { isFirefox } = browserInfo; + const { isIos } = deviceInfo; + + return (isFirefox && !isIos) && FORCE_RELAY_ON_FF; +}; + +export { + shouldForceRelay, +}; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 2511ebe05e..6ccb4f9151 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -200,6 +200,7 @@ public: # Experiment(al). Controls whether ICE candidates should be signaled. # Applies to webcams, listen only and screen sharing. True is "stable behavior". signalCandidates: true + forceRelayOnFirefox: false cameraTimeouts: # Base camera timeout: used as the camera *sharing* timeout and # as the minimum camera subscribe reconnection timeout