From c21bb693b4da07e27881d84733518230d2fa0122 Mon Sep 17 00:00:00 2001
From: gustavotrott <gustavo@trott.com.br>
Date: Mon, 14 Jan 2019 22:45:32 -0200
Subject: [PATCH] Store in session if can generate ice candidates, change
 timeout to 5secs, display error msgs

---
 .../components/video-provider/component.jsx   |  23 +--
 .../ui/services/audio-manager/index.js        |   8 +-
 .../imports/utils/safari-webrtc.js            | 136 ++++++++++--------
 3 files changed, 100 insertions(+), 67 deletions(-)

diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
index 80aae0fd16..c0cc7b83b4 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
@@ -101,14 +101,7 @@ const MAX_CAMERA_SHARE_FAILED_WAIT_TIME = 60000;
 const PING_INTERVAL = 15000;
 
 class VideoProvider extends Component {
-  static checkIceConnectivity() {
-    // Webkit ICE restrictions demand a capture device permission to release
-    // host candidates
-    if (browser().name === 'safari') {
-      tryGenerateIceCandidates();
-    }
-  }
-
+  
   constructor(props) {
     super(props);
 
@@ -146,6 +139,7 @@ class VideoProvider extends Component {
     this.customGetStats = this.customGetStats.bind(this);
   }
 
+  
 
   componentWillMount() {
     this.ws.onopen = this.onWsOpen;
@@ -156,7 +150,7 @@ class VideoProvider extends Component {
   }
 
   componentDidMount() {
-    VideoProvider.checkIceConnectivity();
+    this.checkIceConnectivity();
     document.addEventListener('joinVideo', this.shareWebcam); // TODO find a better way to do this
     document.addEventListener('exitVideo', this.unshareWebcam);
     this.ws.onmessage = this.onWsMessage;
@@ -284,6 +278,17 @@ class VideoProvider extends Component {
     }
   }
 
+  checkIceConnectivity() {
+    // Webkit ICE restrictions demand a capture device permission to release
+    // host candidates
+    if (browser().name === 'safari') {
+      const { intl } = this.props;
+      tryGenerateIceCandidates().catch((e) => {
+        this.notifyError(intl.formatMessage(intlSFUErrors[2021]));
+      });
+    }
+  }
+  
   logger(type, message, options = {}) {
     const { userId, userName } = this.props;
     const topic = options.topic || 'video';
diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
index 691450b179..d311901fb1 100755
--- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
+++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js
@@ -89,7 +89,7 @@ class AudioManager {
 
     this.isWaitingPermissions = false;
     this.devicesInitialized = false;
-    
+
     return Promise.all([
       this.setDefaultInputDevice(),
       this.setDefaultOutputDevice(),
@@ -150,7 +150,11 @@ class AudioManager {
     // Webkit ICE restrictions demand a capture device permission to release
     // host candidates
     if (name === 'safari') {
-      await tryGenerateIceCandidates();
+      try {
+        await tryGenerateIceCandidates();
+      } catch (e) {
+        this.notify(this.messages.error.ICE_NEGOTIATION_FAILED);
+      }
     }
 
     // Call polyfills for webrtc client if navigator is "iOS Webview"
diff --git a/bigbluebutton-html5/imports/utils/safari-webrtc.js b/bigbluebutton-html5/imports/utils/safari-webrtc.js
index 4d569dc6df..524ceb737b 100644
--- a/bigbluebutton-html5/imports/utils/safari-webrtc.js
+++ b/bigbluebutton-html5/imports/utils/safari-webrtc.js
@@ -1,65 +1,89 @@
-const iceServersList = [
-	{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"}
-	];
+import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import Auth from '/imports/ui/services/auth';
+import { Session } from 'meteor/session';
 
+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;
+  } catch (error) {
+    return defaultIceServersList;
+  }
+}
 
 export function canGenerateIceCandidates() {
+  return new Promise((resolve, reject) => {
+    if (Session.get('canGenerateIceCandidates')) {
+      resolve();
+      return;
+    }
     
-    return new Promise((resolve, reject) => {
-		pc = new RTCPeerConnection({iceServers: iceServersList});
-		countIceCandidates = 0;
+    getIceServersList().catch((e) => {
+      reject();
+    }).then((iceServersReceived) => {
+      const pc = new RTCPeerConnection({ iceServers: iceServersReceived });
+      let countIceCandidates = 0;
+
+      try { pc.addTransceiver('audio'); } catch (e) { }
+
+      pc.onicecandidate = function (e) {
+        if (countIceCandidates) return;
+        if (e.candidate) {
+          countIceCandidates++;
+          Session.set('canGenerateIceCandidates', true);
+          resolve();
+        }
+      }
 
-		try{ pc.addTransceiver('audio'); } catch (e) {}
+      pc.onicegatheringstatechange = function (e) {
+        if (e.currentTarget.iceGatheringState == 'complete' && countIceCandidates == 0) reject();
+      }
 
-		pc.onicecandidate = function (e) {
-			if(countIceCandidates) return;
-			if (e.candidate) {
-				countIceCandidates++;
-				resolve();
-			}
-		}
-		
-		pc.onicegatheringstatechange  = function(e) {
-			if(e.currentTarget.iceGatheringState == 'complete' && countIceCandidates == 0) reject();
-		}
-		
-		setTimeout(function(){
-			pc.close();
-			if(!countIceCandidates) reject();
-		}, 3000);
+      setTimeout(function () {
+        pc.close();
+        if (!countIceCandidates) reject();
+    }, 5000);
 
-		p = pc.createOffer({offerToReceiveVideo: true});
-		p.then( answer => {pc.setLocalDescription(answer) ; } )
-	});
-};
+      const p = pc.createOffer({ offerToReceiveVideo: true });
+      p.then((answer) => { pc.setLocalDescription(answer); });
+    });
+  });
+}
 
 export function tryGenerateIceCandidates() {
-	return new Promise((resolve, reject) => {
-		canGenerateIceCandidates().then(ok => {
-			resolve();
-		}).catch(e => {
-			navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(function (stream) {
-				canGenerateIceCandidates().then(ok => {
-					resolve();
-				}).catch(e => {
-					reject();
-				});
-			}).catch(e => {
-				reject();
-			});
-		});
-	});
-};
\ No newline at end of file
+  return new Promise((resolve, reject) => {
+    canGenerateIceCandidates().then((ok) => {
+      resolve();
+    }).catch((e) => {
+      navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function (stream) {
+        canGenerateIceCandidates().then((ok) => {
+          resolve();
+        }).catch((e) => {
+          reject();
+        });
+      }).catch((e) => {
+        reject();
+      });
+    });
+  });
+}
\ No newline at end of file
-- 
GitLab