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