File: source/peer-connection.js

  1. /**
  2. * <blockquote class="info">
  3. * Learn more about how ICE works in this
  4. * <a href="https://temasys.com.sg/ice-what-is-this-sorcery/">article here</a>.
  5. * </blockquote>
  6. * The list of Peer connection session description exchanging states.
  7. * @attribute PEER_CONNECTION_STATE
  8. * @param {String} STABLE <small>Value <code>"stable"</code></small>
  9. * The value of the state when there is no session description being exchanged between Peer connection.
  10. * @param {String} HAVE_LOCAL_OFFER <small>Value <code>"have-local-offer"</code></small>
  11. * The value of the state when local <code>"offer"</code> session description is set.
  12. * <small>This should transition to <code>STABLE</code> state after remote <code>"answer"</code>
  13. * session description is set.</small>
  14. * <small>See <a href="#event_handshakeProgress"><code>handshakeProgress</code> event</a> for a more
  15. * detailed exchanging of session description states.</small>
  16. * @param {String} HAVE_REMOTE_OFFER <small>Value <code>"have-remote-offer"</code></small>
  17. * The value of the state when remote <code>"offer"</code> session description is set.
  18. * <small>This should transition to <code>STABLE</code> state after local <code>"answer"</code>
  19. * session description is set.</small>
  20. * <small>See <a href="#event_handshakeProgress"><code>handshakeProgress</code> event</a> for a more
  21. * detailed exchanging of session description states.</small>
  22. * @param {String} CLOSED <small>Value <code>"closed"</code></small>
  23. * The value of the state when Peer connection is closed and no session description can be exchanged and set.
  24. * @type JSON
  25. * @readOnly
  26. * @for Skylink
  27. * @since 0.5.0
  28. */
  29. Skylink.prototype.PEER_CONNECTION_STATE = {
  30. STABLE: 'stable',
  31. HAVE_LOCAL_OFFER: 'have-local-offer',
  32. HAVE_REMOTE_OFFER: 'have-remote-offer',
  33. CLOSED: 'closed'
  34. };
  35.  
  36. /**
  37. * The list of <a href="#method_getConnectionStatus"><code>getConnectionStatus()</code>
  38. * method</a> retrieval states.
  39. * @attribute GET_CONNECTION_STATUS_STATE
  40. * @param {Number} RETRIEVING <small>Value <code>0</code></small>
  41. * The value of the state when <code>getConnectionStatus()</code> is retrieving the Peer connection stats.
  42. * @param {Number} RETRIEVE_SUCCESS <small>Value <code>1</code></small>
  43. * The value of the state when <code>getConnectionStatus()</code> has retrieved the Peer connection stats successfully.
  44. * @param {Number} RETRIEVE_ERROR <small>Value <code>-1</code></small>
  45. * The value of the state when <code>getConnectionStatus()</code> has failed retrieving the Peer connection stats.
  46. * @type JSON
  47. * @readOnly
  48. * @for Skylink
  49. * @since 0.1.0
  50. */
  51. Skylink.prototype.GET_CONNECTION_STATUS_STATE = {
  52. RETRIEVING: 0,
  53. RETRIEVE_SUCCESS: 1,
  54. RETRIEVE_ERROR: -1
  55. };
  56.  
  57. /**
  58. * <blockquote class="info">
  59. * As there are more features getting implemented, there will be eventually more different types of
  60. * server Peers.
  61. * </blockquote>
  62. * The list of available types of server Peer connections.
  63. * @attribute SERVER_PEER_TYPE
  64. * @param {String} MCU <small>Value <code>"mcu"</code></small>
  65. * The value of the server Peer type that is used for MCU connection.
  66. * @type JSON
  67. * @readOnly
  68. * @for Skylink
  69. * @since 0.6.1
  70. */
  71. Skylink.prototype.SERVER_PEER_TYPE = {
  72. MCU: 'mcu'
  73. //SIP: 'sip'
  74. };
  75.  
  76. /**
  77. * Stores the restart initiated timestamp to throttle the <code>refreshConnection</code> functionality.
  78. * @attribute _lastRestart
  79. * @type Object
  80. * @private
  81. * @for Skylink
  82. * @since 0.5.9
  83. */
  84. Skylink.prototype._lastRestart = null;
  85.  
  86. /**
  87. * Stores the global number of Peer connection retries that would increase the wait-for-response timeout
  88. * for the Peer connection health timer.
  89. * @attribute _retryCount
  90. * @type Number
  91. * @private
  92. * @for Skylink
  93. * @since 0.5.10
  94. */
  95. Skylink.prototype._retryCount = 0;
  96.  
  97. /**
  98. * Stores the list of the Peer connections.
  99. * @attribute _peerConnections
  100. * @param {Object} <#peerId> The Peer connection.
  101. * @type JSON
  102. * @private
  103. * @for Skylink
  104. * @since 0.1.0
  105. */
  106. Skylink.prototype._peerConnections = {};
  107.  
  108. /**
  109. * <blockquote class="info">
  110. * For MCU enabled Peer connections, the restart functionality may differ, you may learn more about how to workaround
  111. * it <a href="http://support.temasys.com.sg/support/discussions/topics/12000002853">in this article here</a>.<br>
  112. * For restarts with Peers connecting from Android or iOS SDKs, restarts might not work as written in
  113. * <a href="http://support.temasys.com.sg/support/discussions/topics/12000005188">in this article here</a>.<br>
  114. * Note that this functionality should be used when Peer connection stream freezes during a connection,
  115. * and is throttled when invoked many times in less than 3 seconds interval.
  116. * </blockquote>
  117. * Function that refreshes Peer connections to update with the current streaming.
  118. * @method refreshConnection
  119. * @param {String|Array} [targetPeerId] The target Peer ID to refresh connection with.
  120. * - When provided as an Array, it will refresh all connections with all the Peer IDs provided.
  121. * - When not provided, it will refresh all the currently connected Peers in the Room.
  122. * @param {Function} [callback] The callback function fired when request has completed.
  123. * <small>Function parameters signature is <code>function (error, success)</code></small>
  124. * <small>Function request completion is determined by the <a href="#event_peerRestart">
  125. * <code>peerRestart</code> event</a> triggering <code>isSelfInitiateRestart</code> parameter payload
  126. * value as <code>true</code> for all Peers targeted for request success.</small>
  127. * @param {JSON} callback.error The error result in request.
  128. * <small>Defined as <code>null</code> when there are no errors in request</small>
  129. * @param {Array} callback.error.listOfPeers The list of Peer IDs targeted.
  130. * @param {JSON} callback.error.refreshErrors The list of Peer connection refresh errors.
  131. * @param {Error|String} callback.error.refreshErrors.#peerId The Peer connection refresh error associated
  132. * with the Peer ID defined in <code>#peerId</code> property.
  133. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  134. * is no Peer connections to refresh with.</small>
  135. * @param {JSON} callback.success The success result in request.
  136. * <small>Defined as <code>null</code> when there are errors in request</small>
  137. * @param {Array} callback.success.listOfPeers The list of Peer IDs targeted.
  138. * @trigger <b class="desc-seq-header">&#8594; For Peer connections without MCU enabled:</b>
  139. * <ol class="desc-seq"><li><a href="#event_peerRestart"><code>peerRestart</code> event</a> triggers parameter
  140. * payload <code>isSelfInitiateRestart</code> as <code>true</code> for all targeted Peer connections.</li></ol>
  141. * <b class="desc-seq-header">&#8594; For Peer connections with MCU enabled:</b> <ol class="desc-seq">
  142. * <li><ol><li><a href="#event_peerRestart"><code>peerRestart</code> event</a> triggers parameter
  143. * payload <code>isSelfInitiateRestart</code> as <code>true</code> for all targeted Peer connections.</li>
  144. * <li><a href="#event_serverPeerRestart"><code>serverPeerRestart</code> event</a> triggers parameter
  145. * payload <code>serverPeerType</code> as <code>MCU</code>.</li></ol></li>
  146. * <li>Invokes <a href="#method_joinRoom"><code>joinRoom()</code> method</a>.<small><code>refreshConnection</code>
  147. * will retain the User session information except the Peer ID will be a different assigned ID due to restarting
  148. * the Room session.</small></li></ol>
  149. * @example
  150. * // Example 1: Refreshing a Peer connection
  151. * function refreshFrozenVideoStream (peerId) {
  152. * skylinkDemo.refreshConnection(peerId, function (error, success) {
  153. * if (error) return;
  154. * console.log("Refreshing connection for '" + peerId + "'");
  155. * });
  156. * }
  157. *
  158. * // Example 2: Refreshing a list of Peer connections
  159. * function refreshFrozenVideoStreamGroup (peerIdA, peerIdB) {
  160. * skylinkDemo.refreshConnection([peerIdA, peerIdB], function (error, success) {
  161. * if (error) {
  162. * if (error.transferErrors[peerIdA]) {
  163. * console.error("Failed refreshing connection for '" + peerIdA + "'");
  164. * } else {
  165. * console.log("Refreshing connection for '" + peerIdA + "'");
  166. * }
  167. * if (error.transferErrors[peerIdB]) {
  168. * console.error("Failed refreshing connection for '" + peerIdB + "'");
  169. * } else {
  170. * console.log("Refreshing connection for '" + peerIdB + "'");
  171. * }
  172. * } else {
  173. * console.log("Refreshing connection for '" + peerIdA + "' and '" + peerIdB + "'");
  174. * }
  175. * });
  176. * }
  177. *
  178. * // Example 3: Refreshing all Peer connections
  179. * function refreshFrozenVideoStreamAll () {
  180. * skylinkDemo.refreshConnection(function (error, success) {
  181. * if (error) {
  182. * for (var i = 0; i < error.listOfPeers.length; i++) {
  183. * if (error.refreshErrors[error.listOfPeers[i]]) {
  184. * console.error("Failed refreshing connection for '" + error.listOfPeers[i] + "'");
  185. * } else {
  186. * console.info("Refreshing connection for '" + error.listOfPeers[i] + "'");
  187. * }
  188. * }
  189. * } else {
  190. * console.log("Refreshing connection for all Peers", success.listOfPeers);
  191. * }
  192. * });
  193. * }
  194. * @for Skylink
  195. * @since 0.5.5
  196. */
  197. Skylink.prototype.refreshConnection = function(targetPeerId, callback) {
  198. var self = this;
  199.  
  200. var listOfPeers = Object.keys(self._peerConnections);
  201. var listOfPeerRestarts = [];
  202. var error = '';
  203. var listOfPeerRestartErrors = {};
  204.  
  205. if(Array.isArray(targetPeerId)) {
  206. listOfPeers = targetPeerId;
  207.  
  208. } else if (typeof targetPeerId === 'string') {
  209. listOfPeers = [targetPeerId];
  210. } else if (typeof targetPeerId === 'function') {
  211. callback = targetPeerId;
  212. }
  213.  
  214. if (listOfPeers.length === 0) {
  215. error = 'There is currently no peer connections to restart';
  216. log.warn([null, 'PeerConnection', null, error]);
  217.  
  218. listOfPeerRestartErrors.self = new Error(error);
  219.  
  220. if (typeof callback === 'function') {
  221. callback({
  222. refreshErrors: listOfPeerRestartErrors,
  223. listOfPeers: listOfPeers
  224. }, null);
  225. }
  226. return;
  227. }
  228.  
  229. // To fix jshint dont put functions within a loop
  230. var refreshSinglePeerCallback = function (peerId) {
  231. return function (error, success) {
  232. if (listOfPeerRestarts.indexOf(peerId) === -1) {
  233. if (error) {
  234. log.error([peerId, 'RTCPeerConnection', null, 'Failed restarting for peer'], error);
  235. listOfPeerRestartErrors[peerId] = error;
  236. }
  237. listOfPeerRestarts.push(peerId);
  238. }
  239.  
  240. if (listOfPeerRestarts.length === listOfPeers.length) {
  241. if (typeof callback === 'function') {
  242. log.log([null, 'PeerConnection', null, 'Invoked all peers to restart. Firing callback']);
  243.  
  244. if (Object.keys(listOfPeerRestartErrors).length > 0) {
  245. callback({
  246. refreshErrors: listOfPeerRestartErrors,
  247. listOfPeers: listOfPeers
  248. }, null);
  249. } else {
  250. callback(null, {
  251. listOfPeers: listOfPeers
  252. });
  253. }
  254. }
  255. }
  256. };
  257. };
  258.  
  259. var refreshSinglePeer = function(peerId, peerCallback){
  260. if (!self._peerConnections[peerId]) {
  261. error = 'There is currently no existing peer connection made ' +
  262. 'with the peer. Unable to restart connection';
  263. log.error([peerId, null, null, error]);
  264. listOfPeerRestartErrors[peerId] = new Error(error);
  265. return;
  266. }
  267.  
  268. var now = Date.now() || function() { return +new Date(); };
  269.  
  270. if (now - self.lastRestart < 3000) {
  271. error = 'Last restart was so tight. Aborting.';
  272. log.error([peerId, null, null, error]);
  273. listOfPeerRestartErrors[peerId] = new Error(error);
  274. return;
  275. }
  276.  
  277. log.log([peerId, 'PeerConnection', null, 'Restarting peer connection']);
  278.  
  279. // do a hard reset on variable object
  280. self._restartPeerConnection(peerId, true, false, peerCallback, true);
  281. };
  282.  
  283. var toRefresh = function() {
  284. if(!self._hasMCU) {
  285. var i;
  286.  
  287. for (i = 0; i < listOfPeers.length; i++) {
  288. var peerId = listOfPeers[i];
  289.  
  290. if (Object.keys(self._peerConnections).indexOf(peerId) > -1) {
  291. refreshSinglePeer(peerId, refreshSinglePeerCallback(peerId));
  292. } else {
  293. error = 'Peer connection with peer does not exists. Unable to restart';
  294. log.error([peerId, 'PeerConnection', null, error]);
  295. listOfPeerRestartErrors[peerId] = new Error(error);
  296. }
  297.  
  298. // there's an error to trigger for
  299. if (i === listOfPeers.length - 1 && Object.keys(listOfPeerRestartErrors).length > 0) {
  300. if (typeof callback === 'function') {
  301. callback({
  302. refreshErrors: listOfPeerRestartErrors,
  303. listOfPeers: listOfPeers
  304. }, null);
  305. }
  306. }
  307. }
  308. } else {
  309. self._restartMCUConnection(callback);
  310. }
  311. };
  312.  
  313. self._throttle(toRefresh,5000)();
  314.  
  315. };
  316.  
  317. /**
  318. * Function that retrieves Peer connection bandwidth and ICE connection stats.
  319. * @method getConnectionStatus
  320. * @param {String|Array} [targetPeerId] The target Peer ID to retrieve connection stats from.
  321. * - When provided as an Array, it will retrieve all connection stats from all the Peer IDs provided.
  322. * - When not provided, it will retrieve all connection stats from the currently connected Peers in the Room.
  323. * @param {Function} [callback] The callback function fired when request has completed.
  324. * <small>Function parameters signature is <code>function (error, success)</code></small>
  325. * <small>Function request completion is determined by the <a href="#event_getConnectionStatusStateChange">
  326. * <code>getConnectionStatusStateChange</code> event</a> triggering <code>state</code> parameter payload
  327. * value as <code>RETRIEVE_SUCCESS</code> for all Peers targeted for request success.</small>
  328. * [Rel: Skylink.GET_CONNECTION_STATUS_STATE]
  329. * @param {JSON} callback.error The error result in request.
  330. * <small>Defined as <code>null</code> when there are no errors in request</small>
  331. * @param {Array} callback.error.listOfPeers The list of Peer IDs targeted.
  332. * @param {JSON} callback.error.retrievalErrors The list of Peer connection stats retrieval errors.
  333. * @param {Error|String} callback.error.retrievalErrors.#peerId The Peer connection stats retrieval error associated
  334. * with the Peer ID defined in <code>#peerId</code> property.
  335. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  336. * are no Peer connections to refresh with.</small>
  337. * @param {JSON} callback.error.connectionStats The list of Peer connection stats.
  338. * <small>These are the Peer connection stats that has been managed to be successfully retrieved.</small>
  339. * @param {JSON} callback.error.connectionStats.#peerId The Peer connection stats associated with
  340. * the Peer ID defined in <code>#peerId</code> property.
  341. * <small>Object signature matches the <code>stats</code> parameter payload received in the
  342. * <a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>.</small>
  343. * @param {JSON} callback.success The success result in request.
  344. * <small>Defined as <code>null</code> when there are errors in request</small>
  345. * @param {Array} callback.success.listOfPeers The list of Peer IDs targeted.
  346. * @param {JSON} callback.success.connectionStats The list of Peer connection stats.
  347. * @param {JSON} callback.success.connectionStats.#peerId The Peer connection stats associated with
  348. * the Peer ID defined in <code>#peerId</code> property.
  349. * <small>Object signature matches the <code>stats</code> parameter payload received in the
  350. * <a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>.</small>
  351. * @trigger <ol class="desc-seq">
  352. * <li><a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>
  353. * triggers parameter payload <code>state</code> value as <code>RETRIEVING</code>.</li>
  354. * <li><ol><li>When retrieval of Peer connection stats is successful, <a href="#event_getConnectionStatusStateChange">
  355. * <code>getConnectionStatusStateChange</code> event</a> triggers parameter payload
  356. * <code>state</code> value as <code>RETRIEVE_SUCCESS</code>.</li>
  357. * <li>When retrieval of Peer connection stats had failed, <a href="#event_getConnectionStatusStateChange">
  358. * <code>getConnectionStatusStateChange</code> event</a> triggers parameter payload
  359. * <code>state</code> value as <code>RETRIEVE_ERROR</code>.</li></ol></li></ol>
  360. * @example
  361. * // Example 1: Retrieve a Peer connection stats
  362. * function startBWStatsInterval (peerId) {
  363. * setInterval(function () {
  364. * skylinkDemo.getConnectionStatus(peerId, function (error, success) {
  365. * if (error) return;
  366. * var sendVideoBytes = success.connectionStats[peerId].video.sending.bytes;
  367. * var sendAudioBytes = success.connectionStats[peerId].audio.sending.bytes;
  368. * var recvVideoBytes = success.connectionStats[peerId].video.receiving.bytes;
  369. * var recvAudioBytes = success.connectionStats[peerId].audio.receiving.bytes;
  370. * var localCandidate = success.connectionStats[peerId].selectedCandidate.local;
  371. * var remoteCandidate = success.connectionStats[peerId].selectedCandidate.remote;
  372. * console.log("Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  373. * console.log("Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  374. * console.log("Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  375. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  376. * console.log("Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  377. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  378. * });
  379. * }, 1000);
  380. * }
  381. *
  382. * // Example 2: Retrieve a list of Peer connection stats
  383. * function printConnStats (peerId, data) {
  384. * if (!data.connectionStats[peerId]) return;
  385. * var sendVideoBytes = data.connectionStats[peerId].video.sending.bytes;
  386. * var sendAudioBytes = data.connectionStats[peerId].audio.sending.bytes;
  387. * var recvVideoBytes = data.connectionStats[peerId].video.receiving.bytes;
  388. * var recvAudioBytes = data.connectionStats[peerId].audio.receiving.bytes;
  389. * var localCandidate = data.connectionStats[peerId].selectedCandidate.local;
  390. * var remoteCandidate = data.connectionStats[peerId].selectedCandidate.remote;
  391. * console.log(peerId + " - Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  392. * console.log(peerId + " - Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  393. * console.log(peerId + " - Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  394. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  395. * console.log(peerId + " - Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  396. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  397. * }
  398. *
  399. * function startBWStatsInterval (peerIdA, peerIdB) {
  400. * setInterval(function () {
  401. * skylinkDemo.getConnectionStatus([peerIdA, peerIdB], function (error, success) {
  402. * if (error) {
  403. * printConnStats(peerIdA, error.connectionStats);
  404. * printConnStats(peerIdB, error.connectionStats);
  405. * } else {
  406. * printConnStats(peerIdA, success.connectionStats);
  407. * printConnStats(peerIdB, success.connectionStats);
  408. * }
  409. * });
  410. * }, 1000);
  411. * }
  412. *
  413. * // Example 3: Retrieve all Peer connection stats
  414. * function printConnStats (listOfPeers, data) {
  415. * listOfPeers.forEach(function (peerId) {
  416. * if (!data.connectionStats[peerId]) return;
  417. * var sendVideoBytes = data.connectionStats[peerId].video.sending.bytes;
  418. * var sendAudioBytes = data.connectionStats[peerId].audio.sending.bytes;
  419. * var recvVideoBytes = data.connectionStats[peerId].video.receiving.bytes;
  420. * var recvAudioBytes = data.connectionStats[peerId].audio.receiving.bytes;
  421. * var localCandidate = data.connectionStats[peerId].selectedCandidate.local;
  422. * var remoteCandidate = data.connectionStats[peerId].selectedCandidate.remote;
  423. * console.log(peerId + " - Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  424. * console.log(peerId + " - Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  425. * console.log(peerId + " - Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  426. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  427. * console.log(peerId + " - Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  428. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  429. * });
  430. * }
  431. *
  432. * function startBWStatsInterval (peerIdA, peerIdB) {
  433. * setInterval(function () {
  434. * skylinkDemo.getConnectionStatus(function (error, success) {
  435. * if (error) {
  436. * printConnStats(error.listOfPeers, error.connectionStats);
  437. * } else {
  438. * printConnStats(success.listOfPeers, success.connectionStats);
  439. * }
  440. * });
  441. * }, 1000);
  442. * }
  443. * @for Skylink
  444. * @since 0.6.14
  445. */
  446. Skylink.prototype.getConnectionStatus = function (targetPeerId, callback) {
  447. var self = this;
  448. var listOfPeers = Object.keys(self._peerConnections);
  449. var listOfPeerStats = {};
  450. var listOfPeerErrors = {};
  451.  
  452. // getConnectionStatus([])
  453. if (Array.isArray(targetPeerId)) {
  454. listOfPeers = targetPeerId;
  455.  
  456. // getConnectionStatus('...')
  457. } else if (typeof targetPeerId === 'string' && !!targetPeerId) {
  458. listOfPeers = [targetPeerId];
  459.  
  460. // getConnectionStatus(function () {})
  461. } else if (typeof targetPeerId === 'function') {
  462. callback = targetPeerId;
  463. targetPeerId = undefined;
  464. }
  465.  
  466. // Check if Peers list is empty, in which we throw an Error if there isn't any
  467. if (listOfPeers.length === 0) {
  468. listOfPeerErrors.self = new Error('There is currently no peer connections to retrieve connection status');
  469.  
  470. log.error([null, 'RTCStatsReport', null, 'Retrieving request failure ->'], listOfPeerErrors.self);
  471.  
  472. if (typeof callback === 'function') {
  473. callback({
  474. listOfPeers: listOfPeers,
  475. retrievalErrors: listOfPeerErrors,
  476. connectionStats: listOfPeerStats
  477. }, null);
  478. }
  479. return;
  480. }
  481.  
  482. var completedTaskCounter = [];
  483.  
  484. var checkCompletedFn = function (peerId) {
  485. if (completedTaskCounter.indexOf(peerId) === -1) {
  486. completedTaskCounter.push(peerId);
  487. }
  488.  
  489. if (completedTaskCounter.length === listOfPeers.length) {
  490. if (typeof callback === 'function') {
  491. if (Object.keys(listOfPeerErrors).length > 0) {
  492. callback({
  493. listOfPeers: listOfPeers,
  494. retrievalErrors: listOfPeerErrors,
  495. connectionStats: listOfPeerStats
  496. }, null);
  497.  
  498. } else {
  499. callback(null, {
  500. listOfPeers: listOfPeers,
  501. connectionStats: listOfPeerStats
  502. });
  503. }
  504. }
  505. }
  506. };
  507.  
  508. var statsFn = function (peerId) {
  509. log.debug([peerId, 'RTCStatsReport', null, 'Retrieivng connection status']);
  510.  
  511. var pc = self._peerConnections[peerId];
  512. var result = {
  513. raw: null,
  514. connection: {
  515. iceConnectionState: pc.iceConnectionState,
  516. iceGatheringState: pc.iceGatheringState,
  517. signalingState: pc.signalingState,
  518. remoteDescription: pc.remoteDescription,
  519. localDescription: pc.localDescription,
  520. candidates: clone(self._gatheredCandidates[peerId] || {
  521. sending: { host: [], srflx: [], relay: [] },
  522. receiving: { host: [], srflx: [], relay: [] }
  523. })
  524. },
  525. audio: {
  526. sending: {
  527. ssrc: null,
  528. bytes: 0,
  529. packets: 0,
  530. packetsLost: 0,
  531. rtt: 0
  532. },
  533. receiving: {
  534. ssrc: null,
  535. bytes: 0,
  536. packets: 0,
  537. packetsLost: 0
  538. }
  539. },
  540. video: {
  541. sending: {
  542. ssrc: null,
  543. bytes: 0,
  544. packets: 0,
  545. packetsLost: 0,
  546. rtt: 0
  547. },
  548. receiving: {
  549. ssrc: null,
  550. bytes: 0,
  551. packets: 0,
  552. packetsLost: 0
  553. }
  554. },
  555. selectedCandidate: {
  556. local: { ipAddress: null, candidateType: null, portNumber: null, transport: null },
  557. remote: { ipAddress: null, candidateType: null, portNumber: null, transport: null }
  558. }
  559. };
  560. var loopFn = function (obj, fn) {
  561. for (var prop in obj) {
  562. if (obj.hasOwnProperty(prop) && obj[prop]) {
  563. fn(obj[prop], prop);
  564. }
  565. }
  566. };
  567. var formatCandidateFn = function (candidateDirType, candidate) {
  568. result.selectedCandidate[candidateDirType].ipAddress = candidate.ipAddress;
  569. result.selectedCandidate[candidateDirType].candidateType = candidate.candidateType;
  570. result.selectedCandidate[candidateDirType].portNumber = typeof candidate.portNumber !== 'number' ?
  571. parseInt(candidate.portNumber, 10) || null : candidate.portNumber;
  572. result.selectedCandidate[candidateDirType].transport = candidate.transport;
  573. };
  574.  
  575. pc.getStats(null, function (stats) {
  576. log.debug([peerId, 'RTCStatsReport', null, 'Retrieval success ->'], stats);
  577.  
  578. result.raw = stats;
  579.  
  580. if (window.webrtcDetectedBrowser === 'firefox') {
  581. loopFn(stats, function (obj, prop) {
  582. var dirType = '';
  583.  
  584. // Receiving/Sending RTP packets
  585. if (prop.indexOf('inbound_rtp') === 0 || prop.indexOf('outbound_rtp') === 0) {
  586. dirType = prop.indexOf('inbound_rtp') === 0 ? 'receiving' : 'sending';
  587.  
  588. result[obj.mediaType][dirType].bytes = dirType === 'sending' ? obj.bytesSent : obj.bytesReceived;
  589. result[obj.mediaType][dirType].packets = dirType === 'sending' ? obj.packetsSent : obj.packetsReceived;
  590. result[obj.mediaType][dirType].ssrc = obj.ssrc;
  591.  
  592. if (dirType === 'receiving') {
  593. result[obj.mediaType][dirType].packetsLost = obj.packetsLost || 0;
  594. }
  595.  
  596. // Sending RTP packets lost
  597. } else if (prop.indexOf('outbound_rtcp') === 0) {
  598. dirType = prop.indexOf('inbound_rtp') === 0 ? 'receiving' : 'sending';
  599.  
  600. result[obj.mediaType][dirType].packetsLost = obj.packetsLost || 0;
  601.  
  602. if (dirType === 'sending') {
  603. result[obj.mediaType].sending.rtt = obj.mozRtt || 0;
  604. }
  605.  
  606. // Candidates
  607. } else if (obj.nominated && obj.selected) {
  608. formatCandidateFn('remote', stats[obj.remoteCandidateId]);
  609. formatCandidateFn('local', stats[obj.localCandidateId]);
  610. }
  611. });
  612.  
  613. } else if (window.webrtcDetectedBrowser === 'edge') {
  614. if (pc.getRemoteStreams().length > 0) {
  615. var tracks = pc.getRemoteStreams()[0].getTracks();
  616.  
  617. loopFn(tracks, function (track) {
  618. loopFn(stats, function (obj, prop) {
  619. if (obj.type === 'track' && obj.trackIdentifier === track.id) {
  620. loopFn(stats, function (streamObj) {
  621. if (streamObj.associateStatsId === obj.id &&
  622. ['outboundrtp', 'inboundrtp'].indexOf(streamObj.type) > -1) {
  623. var dirType = streamObj.type === 'outboundrtp' ? 'sending' : 'receiving';
  624.  
  625. result[track.kind][dirType].bytes = dirType === 'sending' ? streamObj.bytesSent : streamObj.bytesReceived;
  626. result[track.kind][dirType].packets = dirType === 'sending' ? streamObj.packetsSent : streamObj.packetsReceived;
  627. result[track.kind][dirType].packetsLost = streamObj.packetsLost || 0;
  628. result[track.kind][dirType].ssrc = parseInt(streamObj.ssrc || '0', 10);
  629.  
  630. if (dirType === 'sending') {
  631. result[track.kind].sending.rtt = obj.roundTripTime || 0;
  632. }
  633. }
  634. });
  635. }
  636. });
  637. });
  638. }
  639.  
  640. } else {
  641. var reportedCandidate = false;
  642.  
  643. loopFn(stats, function (obj, prop) {
  644. if (prop.indexOf('ssrc_') === 0) {
  645. var dirType = prop.indexOf('_recv') > 0 ? 'receiving' : 'sending';
  646.  
  647. // Polyfill fix for plugin. Plugin should fix this though
  648. if (!obj.mediaType) {
  649. obj.mediaType = obj.hasOwnProperty('audioOutputLevel') ||
  650. obj.hasOwnProperty('audioInputLevel') ? 'audio' : 'video';
  651. }
  652.  
  653. // Receiving/Sending RTP packets
  654. result[obj.mediaType][dirType].bytes = parseInt((dirType === 'receiving' ?
  655. obj.bytesReceived : obj.bytesSent) || '0', 10);
  656. result[obj.mediaType][dirType].packets = parseInt((dirType === 'receiving' ?
  657. obj.packetsReceived : obj.packetsSent) || '0', 10);
  658. result[obj.mediaType][dirType].ssrc = parseInt(obj.ssrc || '0', 10);
  659. result[obj.mediaType][dirType].packetsLost = parseInt(obj.packetsLost || '0', 10);
  660.  
  661. if (dirType === 'sending') {
  662. // NOTE: Chrome sending audio does have it but plugin has..
  663. result[obj.mediaType].sending.rtt = parseInt(obj.googRtt || '0', 10);
  664. }
  665.  
  666. if (!reportedCandidate) {
  667. loopFn(stats, function (canObj, canProp) {
  668. if (!reportedCandidate && canProp.indexOf('Conn-') === 0) {
  669. if (obj.transportId === canObj.googChannelId) {
  670. formatCandidateFn('local', stats[canObj.localCandidateId]);
  671. formatCandidateFn('remote', stats[canObj.remoteCandidateId]);
  672. reportedCandidate = true;
  673. }
  674. }
  675. });
  676. }
  677. }
  678. });
  679. }
  680.  
  681. listOfPeerStats[peerId] = result;
  682.  
  683. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_SUCCESS,
  684. peerId, listOfPeerStats[peerId], null);
  685.  
  686. checkCompletedFn(peerId);
  687.  
  688. }, function (error) {
  689. log.error([peerId, 'RTCStatsReport', null, 'Retrieval failure ->'], error);
  690.  
  691. listOfPeerErrors[peerId] = error;
  692.  
  693. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_ERROR,
  694. peerId, null, error);
  695.  
  696. checkCompletedFn(peerId);
  697. });
  698. };
  699.  
  700. // Loop through all the list of Peers selected to retrieve connection status
  701. for (var i = 0; i < listOfPeers.length; i++) {
  702. var peerId = listOfPeers[i];
  703.  
  704. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVING,
  705. peerId, null, null);
  706.  
  707. // Check if the Peer connection exists first
  708. if (self._peerConnections.hasOwnProperty(peerId) && self._peerConnections[peerId]) {
  709. statsFn(peerId);
  710.  
  711. } else {
  712. listOfPeerErrors[peerId] = new Error('The peer connection object does not exists');
  713.  
  714. log.error([peerId, 'RTCStatsReport', null, 'Retrieval failure ->'], listOfPeerErrors[peerId]);
  715.  
  716. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_ERROR,
  717. peerId, null, listOfPeerErrors[peerId]);
  718.  
  719. checkCompletedFn(peerId);
  720. }
  721. }
  722. };
  723.  
  724. /**
  725. * Function that starts the Peer connection session.
  726. * Remember to remove previous method of reconnection (re-creating the Peer connection - destroy and create connection).
  727. * @method _addPeer
  728. * @private
  729. * @for Skylink
  730. * @since 0.5.4
  731. */
  732. Skylink.prototype._addPeer = function(targetMid, peerBrowser, toOffer, restartConn, receiveOnly, isSS) {
  733. var self = this;
  734. if (self._peerConnections[targetMid] && !restartConn) {
  735. log.error([targetMid, null, null, 'Connection to peer has already been made']);
  736. return;
  737. }
  738. log.log([targetMid, null, null, 'Starting the connection to peer. Options provided:'], {
  739. peerBrowser: peerBrowser,
  740. toOffer: toOffer,
  741. receiveOnly: receiveOnly,
  742. enableDataChannel: self._enableDataChannel
  743. });
  744.  
  745. log.info('Adding peer', isSS);
  746.  
  747. if (!restartConn) {
  748. self._peerConnections[targetMid] = self._createPeerConnection(targetMid, !!isSS);
  749. }
  750.  
  751. if (!self._peerConnections[targetMid]) {
  752. log.error([targetMid, null, null, 'Failed creating the connection to peer']);
  753. return;
  754. }
  755.  
  756. self._peerConnections[targetMid].receiveOnly = !!receiveOnly;
  757. self._peerConnections[targetMid].hasScreen = !!isSS;
  758. if (!receiveOnly) {
  759. self._addLocalMediaStreams(targetMid);
  760. }
  761. // I'm the callee I need to make an offer
  762. /*if (toOffer) {
  763. self._doOffer(targetMid, peerBrowser);
  764. }*/
  765.  
  766. // do a peer connection health check
  767. // let MCU handle this case
  768. if (!self._hasMCU) {
  769. this._startPeerConnectionHealthCheck(targetMid, toOffer);
  770. } else {
  771. log.warn([targetMid, 'PeerConnectionHealth', null, 'Not setting health timer for MCU connection']);
  772. return;
  773. }
  774. };
  775.  
  776. /**
  777. * Function that re-negotiates a Peer connection.
  778. * We currently do not implement the ICE restart functionality.
  779. * Remember to remove previous method of reconnection (re-creating the Peer connection - destroy and create connection).
  780. * @method _restartPeerConnection
  781. * @private
  782. * @for Skylink
  783. * @since 0.5.8
  784. */
  785. Skylink.prototype._restartPeerConnection = function (peerId, isSelfInitiatedRestart, isConnectionRestart, callback, explicit) {
  786. var self = this;
  787.  
  788. if (!self._peerConnections[peerId]) {
  789. log.error([peerId, null, null, 'Peer does not have an existing ' +
  790. 'connection. Unable to restart']);
  791. return;
  792. }
  793.  
  794. delete self._peerConnectionHealth[peerId];
  795.  
  796. self._stopPeerConnectionHealthCheck(peerId);
  797.  
  798. var pc = self._peerConnections[peerId];
  799.  
  800. var agent = (self.getPeerInfo(peerId) || {}).agent || {};
  801.  
  802. // prevent restarts for other SDK clients
  803. if (['Android', 'iOS', 'cpp'].indexOf(agent.name) > -1) {
  804. var notSupportedError = new Error('Failed restarting with other agents connecting from other SDKs as ' +
  805. 're-negotiation is not supported by other SDKs');
  806.  
  807. log.warn([peerId, 'RTCPeerConnection', null, 'Ignoring restart request as agent\'s SDK does not support it'],
  808. notSupportedError);
  809.  
  810. if (typeof callback === 'function') {
  811. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  812. callback(null, notSupportedError);
  813. }
  814. return;
  815. }
  816.  
  817. // This is when the state is stable and re-handshaking is possible
  818. // This could be due to previous connection handshaking that is already done
  819. if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  820. if (self._peerConnections[peerId] && !self._peerConnections[peerId].receiveOnly) {
  821. self._addLocalMediaStreams(peerId);
  822. }
  823.  
  824. if (isSelfInitiatedRestart){
  825. log.log([peerId, null, null, 'Sending restart message to signaling server']);
  826.  
  827. var lastRestart = Date.now() || function() { return +new Date(); };
  828.  
  829. self._sendChannelMessage({
  830. type: self._SIG_MESSAGE_TYPE.RESTART,
  831. mid: self._user.sid,
  832. rid: self._room.id,
  833. agent: window.webrtcDetectedBrowser,
  834. version: window.webrtcDetectedVersion,
  835. os: window.navigator.platform,
  836. userInfo: self.getPeerInfo(),
  837. target: peerId,
  838. isConnectionRestart: !!isConnectionRestart,
  839. lastRestart: lastRestart,
  840. // This will not be used based off the logic in _restartHandler
  841. weight: self._peerPriorityWeight,
  842. receiveOnly: self._peerConnections[peerId] && self._peerConnections[peerId].receiveOnly,
  843. enableIceTrickle: self._enableIceTrickle,
  844. enableDataChannel: self._enableDataChannel,
  845. sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
  846. explicit: !!explicit
  847. });
  848.  
  849. self._trigger('peerRestart', peerId, self.getPeerInfo(peerId), false);
  850.  
  851. if (typeof callback === 'function') {
  852. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart callback']);
  853. callback(null, null);
  854. }
  855. } else {
  856. if (typeof callback === 'function') {
  857. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart callback (receiving peer)']);
  858. callback(null, null);
  859. }
  860. }
  861.  
  862. // following the previous logic to do checker always
  863. self._startPeerConnectionHealthCheck(peerId, false);
  864.  
  865. } else {
  866. // Let's check if the signalingState is stable first.
  867. // In another galaxy or universe, where the local description gets dropped..
  868. // In the offerHandler or answerHandler, do the appropriate flags to ignore or drop "extra" descriptions
  869. if (pc.signalingState === self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
  870. // Checks if the local description is defined first
  871. var hasLocalDescription = pc.localDescription && pc.localDescription.sdp;
  872. // By then it should have at least the local description..
  873. if (hasLocalDescription) {
  874. self._sendChannelMessage({
  875. type: pc.localDescription.type,
  876. sdp: pc.localDescription.sdp,
  877. mid: self._user.sid,
  878. target: peerId,
  879. rid: self._room.id,
  880. restart: true
  881. });
  882. } else {
  883. var noLocalDescriptionError = 'Failed re-sending localDescription as there is ' +
  884. 'no localDescription set to connection. There could be a handshaking step error';
  885. log.error([peerId, 'RTCPeerConnection', null, noLocalDescriptionError], {
  886. localDescription: pc.localDescription,
  887. remoteDescription: pc.remoteDescription
  888. });
  889. if (typeof callback === 'function') {
  890. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  891. callback(null, new Error(noLocalDescriptionError));
  892. }
  893. }
  894. // It could have connection state closed
  895. } else {
  896. var unableToRestartError = 'Failed restarting as peer connection state is ' + pc.signalingState;
  897. log.warn([peerId, 'RTCPeerConnection', null, unableToRestartError]);
  898. if (typeof callback === 'function') {
  899. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  900. callback(null, new Error(unableToRestartError));
  901. }
  902. }
  903. }
  904. };
  905.  
  906. /**
  907. * Function that ends the Peer connection session.
  908. * @method _removePeer
  909. * @private
  910. * @for Skylink
  911. * @since 0.5.5
  912. */
  913. Skylink.prototype._removePeer = function(peerId) {
  914. var peerInfo = clone(this.getPeerInfo(peerId)) || {
  915. userData: '',
  916. settings: {},
  917. mediaStatus: {},
  918. agent: {},
  919. room: clone(this._selectedRoom)
  920. };
  921.  
  922. if (peerId !== 'MCU') {
  923. this._trigger('peerLeft', peerId, peerInfo, false);
  924. } else {
  925. this._hasMCU = false;
  926. log.log([peerId, null, null, 'MCU has stopped listening and left']);
  927. this._trigger('serverPeerLeft', peerId, this.SERVER_PEER_TYPE.MCU);
  928. }
  929. // stop any existing peer health timer
  930. this._stopPeerConnectionHealthCheck(peerId);
  931.  
  932. // check if health timer exists
  933. if (typeof this._peerConnections[peerId] !== 'undefined') {
  934. // new flag to check if datachannels are all closed
  935. this._peerConnections[peerId].dataChannelClosed = true;
  936.  
  937. if (this._peerConnections[peerId].signalingState !== 'closed') {
  938. this._peerConnections[peerId].close();
  939. }
  940.  
  941. if (this._peerConnections[peerId].hasStream) {
  942. this._trigger('streamEnded', peerId, this.getPeerInfo(peerId), false);
  943. }
  944.  
  945. delete this._peerConnections[peerId];
  946. }
  947. // remove peer informations session
  948. if (typeof this._peerInformations[peerId] !== 'undefined') {
  949. delete this._peerInformations[peerId];
  950. }
  951. if (typeof this._peerConnectionHealth[peerId] !== 'undefined') {
  952. delete this._peerConnectionHealth[peerId];
  953. }
  954. // close datachannel connection
  955. if (this._enableDataChannel) {
  956. this._closeDataChannel(peerId);
  957. }
  958.  
  959. log.log([peerId, null, null, 'Successfully removed peer']);
  960. };
  961.  
  962. /**
  963. * Function that creates the Peer connection.
  964. * @method _createPeerConnection
  965. * @private
  966. * @for Skylink
  967. * @since 0.5.1
  968. */
  969. Skylink.prototype._createPeerConnection = function(targetMid, isScreenSharing) {
  970. var pc, self = this;
  971. // currently the AdapterJS 0.12.1-2 causes an issue to prevent firefox from
  972. // using .urls feature
  973. try {
  974. pc = new window.RTCPeerConnection(
  975. self._room.connection.peerConfig,
  976. self._room.connection.peerConstraints);
  977. log.info([targetMid, null, null, 'Created peer connection']);
  978. log.debug([targetMid, null, null, 'Peer connection config:'],
  979. self._room.connection.peerConfig);
  980. log.debug([targetMid, null, null, 'Peer connection constraints:'],
  981. self._room.connection.peerConstraints);
  982. } catch (error) {
  983. log.error([targetMid, null, null, 'Failed creating peer connection:'], error);
  984. return null;
  985. }
  986. // attributes (added on by Temasys)
  987. pc.setOffer = '';
  988. pc.setAnswer = '';
  989. pc.hasStream = false;
  990. pc.hasScreen = !!isScreenSharing;
  991. pc.hasMainChannel = false;
  992. pc.firefoxStreamId = '';
  993. pc.processingLocalSDP = false;
  994. pc.processingRemoteSDP = false;
  995. pc.gathered = false;
  996.  
  997. // datachannels
  998. self._dataChannels[targetMid] = {};
  999. // candidates
  1000. self._gatheredCandidates[targetMid] = {
  1001. sending: { host: [], srflx: [], relay: [] },
  1002. receiving: { host: [], srflx: [], relay: [] }
  1003. };
  1004.  
  1005. // callbacks
  1006. // standard not implemented: onnegotiationneeded,
  1007. pc.ondatachannel = function(event) {
  1008. var dc = event.channel || event;
  1009. log.debug([targetMid, 'RTCDataChannel', dc.label, 'Received datachannel ->'], dc);
  1010. if (self._enableDataChannel) {
  1011.  
  1012. var channelType = self.DATA_CHANNEL_TYPE.DATA;
  1013. var channelKey = dc.label;
  1014.  
  1015. // if peer does not have main channel, the first item is main
  1016. if (!pc.hasMainChannel) {
  1017. channelType = self.DATA_CHANNEL_TYPE.MESSAGING;
  1018. channelKey = 'main';
  1019. pc.hasMainChannel = true;
  1020. }
  1021.  
  1022. self._dataChannels[targetMid][channelKey] =
  1023. self._createDataChannel(targetMid, channelType, dc, dc.label);
  1024.  
  1025. } else {
  1026. log.warn([targetMid, 'RTCDataChannel', dc.label, 'Not adding datachannel as enable datachannel ' +
  1027. 'is set to false']);
  1028. }
  1029. };
  1030. pc.onaddstream = function(event) {
  1031. var stream = event.stream || event;
  1032.  
  1033. if (targetMid === 'MCU') {
  1034. log.debug([targetMid, 'MediaStream', stream.id,
  1035. 'Ignoring received remote stream from MCU ->'], stream);
  1036. return;
  1037. }
  1038.  
  1039. pc.hasStream = true;
  1040.  
  1041. var agent = (self.getPeerInfo(targetMid) || {}).agent || {};
  1042. var timeout = 0;
  1043.  
  1044. // NOTE: Add timeouts to the firefox stream received because it seems to have some sort of black stream rendering at first
  1045. // This may not be advisable but that it seems to work after 1500s. (tried with ICE established but it does not work and getStats)
  1046. if (agent.name === 'firefox' && window.webrtcDetectedBrowser !== 'firefox') {
  1047. timeout = 1500;
  1048. }
  1049. setTimeout(function () {
  1050. self._onRemoteStreamAdded(targetMid, stream, !!pc.hasScreen);
  1051. }, timeout);
  1052. };
  1053. pc.onicecandidate = function(event) {
  1054. var candidate = event.candidate || event;
  1055.  
  1056. if (candidate.candidate) {
  1057. pc.gathered = false;
  1058. } else {
  1059. pc.gathered = true;
  1060. }
  1061.  
  1062. log.debug([targetMid, 'RTCIceCandidate', null, 'Ice candidate generated ->'], candidate);
  1063. self._onIceCandidate(targetMid, candidate);
  1064. };
  1065. pc.oniceconnectionstatechange = function(evt) {
  1066. checkIceConnectionState(targetMid, pc.iceConnectionState,
  1067. function(iceConnectionState) {
  1068. log.debug([targetMid, 'RTCIceConnectionState', null,
  1069. 'Ice connection state changed ->'], iceConnectionState);
  1070. self._trigger('iceConnectionState', iceConnectionState, targetMid);
  1071.  
  1072. // clear all peer connection health check
  1073. // peer connection is stable. now if there is a waiting check on it
  1074. if (iceConnectionState === self.ICE_CONNECTION_STATE.COMPLETED &&
  1075. pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  1076. log.debug([targetMid, 'PeerConnectionHealth', null,
  1077. 'Peer connection with user is stable']);
  1078. self._peerConnectionHealth[targetMid] = true;
  1079. self._stopPeerConnectionHealthCheck(targetMid);
  1080. self._retryCount = 0;
  1081. }
  1082.  
  1083. if (typeof self._ICEConnectionFailures[targetMid] === 'undefined') {
  1084. self._ICEConnectionFailures[targetMid] = 0;
  1085. }
  1086.  
  1087. if (iceConnectionState === self.ICE_CONNECTION_STATE.FAILED) {
  1088. self._ICEConnectionFailures[targetMid] += 1;
  1089.  
  1090. if (self._enableIceTrickle) {
  1091. self._trigger('iceConnectionState',
  1092. self.ICE_CONNECTION_STATE.TRICKLE_FAILED, targetMid);
  1093. }
  1094.  
  1095. // refresh when failed. ignore for MCU case since restart is handled by MCU in this case
  1096. if (!self._hasMCU) {
  1097. self._restartPeerConnection(targetMid, true, true, null, false);
  1098. }
  1099. }
  1100.  
  1101. /**** SJS-53: Revert of commit ******
  1102. // resend if failed
  1103. if (iceConnectionState === self.ICE_CONNECTION_STATE.FAILED) {
  1104. log.debug([targetMid, 'RTCIceConnectionState', null,
  1105. 'Ice connection state failed. Re-negotiating connection']);
  1106. self._removePeer(targetMid);
  1107. self._sendChannelMessage({
  1108. type: self._SIG_MESSAGE_TYPE.WELCOME,
  1109. mid: self._user.sid,
  1110. rid: self._room.id,
  1111. agent: window.webrtcDetectedBrowser,
  1112. version: window.webrtcDetectedVersion,
  1113. userInfo: self.getPeerInfo(),
  1114. target: targetMid,
  1115. restartNego: true,
  1116. hsPriority: -1
  1117. });
  1118. } *****/
  1119. });
  1120. };
  1121. // pc.onremovestream = function () {
  1122. // self._onRemoteStreamRemoved(targetMid);
  1123. // };
  1124. pc.onsignalingstatechange = function() {
  1125. log.debug([targetMid, 'RTCSignalingState', null,
  1126. 'Peer connection state changed ->'], pc.signalingState);
  1127. self._trigger('peerConnectionState', pc.signalingState, targetMid);
  1128.  
  1129. // clear all peer connection health check
  1130. // peer connection is stable. now if there is a waiting check on it
  1131. if ((pc.iceConnectionState === self.ICE_CONNECTION_STATE.COMPLETED ||
  1132. pc.iceConnectionState === self.ICE_CONNECTION_STATE.CONNECTED) &&
  1133. pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  1134. log.debug([targetMid, 'PeerConnectionHealth', null,
  1135. 'Peer connection with user is stable']);
  1136. self._peerConnectionHealth[targetMid] = true;
  1137. self._stopPeerConnectionHealthCheck(targetMid);
  1138. self._retryCount = 0;
  1139. }
  1140. };
  1141. pc.onicegatheringstatechange = function() {
  1142. log.log([targetMid, 'RTCIceGatheringState', null,
  1143. 'Ice gathering state changed ->'], pc.iceGatheringState);
  1144. self._trigger('candidateGenerationState', pc.iceGatheringState, targetMid);
  1145. };
  1146.  
  1147. if (window.webrtcDetectedBrowser === 'firefox') {
  1148. pc.removeStream = function (stream) {
  1149. var senders = pc.getSenders();
  1150. for (var s = 0; s < senders.length; s++) {
  1151. var tracks = stream.getTracks();
  1152. for (var t = 0; t < tracks.length; t++) {
  1153. if (tracks[t] === senders[s].track) {
  1154. pc.removeTrack(senders[s]);
  1155. }
  1156. }
  1157. }
  1158. };
  1159. }
  1160.  
  1161. return pc;
  1162. };
  1163.  
  1164. /**
  1165. * Function that handles the <code>_restartPeerConnection</code> scenario
  1166. * for MCU enabled Peer connections.
  1167. * This is implemented currently by making the user leave and join the Room again.
  1168. * The Peer ID will not stay the same though.
  1169. * @method _restartMCUConnection
  1170. * @private
  1171. * @for Skylink
  1172. * @since 0.6.1
  1173. */
  1174. Skylink.prototype._restartMCUConnection = function(callback) {
  1175. var self = this;
  1176. log.info([self._user.sid, null, null, 'Restarting with MCU enabled']);
  1177. // Save room name
  1178. /*var roomName = (self._room.id).substring((self._room.id)
  1179. .indexOf('_api_') + 5, (self._room.id).length);*/
  1180. var listOfPeers = Object.keys(self._peerConnections);
  1181. var listOfPeerRestartErrors = {};
  1182. var peerId; // j shint is whinning
  1183. var receiveOnly = false;
  1184. // for MCU case, these dont matter at all
  1185. var lastRestart = Date.now() || function() { return +new Date(); };
  1186. var weight = (new Date()).valueOf();
  1187.  
  1188. self._trigger('serverPeerRestart', 'MCU', self.SERVER_PEER_TYPE.MCU);
  1189.  
  1190. for (var i = 0; i < listOfPeers.length; i++) {
  1191. peerId = listOfPeers[i];
  1192.  
  1193. if (!self._peerConnections[peerId]) {
  1194. var error = 'Peer connection with peer does not exists. Unable to restart';
  1195. log.error([peerId, 'PeerConnection', null, error]);
  1196. listOfPeerRestartErrors[peerId] = new Error(error);
  1197. continue;
  1198. }
  1199.  
  1200. if (peerId === 'MCU') {
  1201. receiveOnly = !!self._peerConnections[peerId].receiveOnly;
  1202. }
  1203.  
  1204. if (peerId !== 'MCU') {
  1205. self._trigger('peerRestart', peerId, self.getPeerInfo(peerId), true);
  1206.  
  1207. log.log([peerId, null, null, 'Sending restart message to signaling server']);
  1208.  
  1209. self._sendChannelMessage({
  1210. type: self._SIG_MESSAGE_TYPE.RESTART,
  1211. mid: self._user.sid,
  1212. rid: self._room.id,
  1213. agent: window.webrtcDetectedBrowser,
  1214. version: window.webrtcDetectedVersion,
  1215. os: window.navigator.platform,
  1216. userInfo: self.getPeerInfo(),
  1217. target: peerId, //'MCU',
  1218. isConnectionRestart: false,
  1219. lastRestart: lastRestart,
  1220. weight: self._peerPriorityWeight,
  1221. receiveOnly: receiveOnly,
  1222. enableIceTrickle: self._enableIceTrickle,
  1223. enableDataChannel: self._enableDataChannel,
  1224. sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
  1225. explicit: true
  1226. });
  1227. }
  1228. }
  1229.  
  1230. // Restart with MCU = peer leaves then rejoins room
  1231. var peerJoinedFn = function (peerId, peerInfo, isSelf) {
  1232. log.log([null, 'PeerConnection', null, 'Invoked all peers to restart with MCU. Firing callback']);
  1233.  
  1234. if (typeof callback === 'function') {
  1235. if (Object.keys(listOfPeerRestartErrors).length > 0) {
  1236. callback({
  1237. refreshErrors: listOfPeerRestartErrors,
  1238. listOfPeers: listOfPeers
  1239. }, null);
  1240. } else {
  1241. callback(null, {
  1242. listOfPeers: listOfPeers
  1243. });
  1244. }
  1245. }
  1246. };
  1247.  
  1248. self.once('peerJoined', peerJoinedFn, function (peerId, peerInfo, isSelf) {
  1249. return isSelf;
  1250. });
  1251.  
  1252. self.leaveRoom(false, function (error, success) {
  1253. if (error) {
  1254. if (typeof callback === 'function') {
  1255. for (var i = 0; i < listOfPeers.length; i++) {
  1256. listOfPeerRestartErrors[listOfPeers[i]] = error;
  1257. }
  1258. callback({
  1259. refreshErrors: listOfPeerRestartErrors,
  1260. listOfPeers: listOfPeers
  1261. }, null);
  1262. }
  1263. } else {
  1264. //self._trigger('serverPeerLeft', 'MCU', self.SERVER_PEER_TYPE.MCU);
  1265. self.joinRoom(self._selectedRoom);
  1266. }
  1267. });
  1268. };
  1269.