File: source/peer-connection.js

  1. /**
  2. * <blockquote class="info">
  3. * Note that Edge browser does not support renegotiation.
  4. * For MCU enabled Peer connections with <code>options.mcuUseRenegoRestart</code> set to <code>false</code>
  5. * in the <a href="#method_init"><code>init()</code> method</a>, the restart functionality may differ, you
  6. * may learn more about how to workaround it
  7. * <a href="http://support.temasys.io/support/discussions/topics/12000002853">in this article here</a>.
  8. * For restarts with Peers connecting from Android, iOS or C++ SDKs, restarts might not work as written in
  9. * <a href="http://support.temasys.io/support/discussions/topics/12000005188">in this article here</a>.
  10. * Note that this functionality should be used when Peer connection stream freezes during a connection.
  11. * For a better user experience for only MCU enabled Peer connections, the functionality is throttled when invoked many
  12. * times in less than the milliseconds interval configured in the <a href="#method_init"><code>init()</code> method</a>.
  13. * </blockquote>
  14. * Function that refreshes Peer connections to update with the current streaming.
  15. * @method refreshConnection
  16. * @param {String|Array} [targetPeerId] <blockquote class="info">
  17. * Note that this is ignored if MCU is enabled for the App Key provided in
  18. * <a href="#method_init"><code>init()</code> method</a>. <code>refreshConnection()</code> will "refresh"
  19. * all Peer connections. See the <u>Event Sequence</u> for more information.</blockquote>
  20. * The target Peer ID to refresh connection with.
  21. * - When provided as an Array, it will refresh all connections with all the Peer IDs provided.
  22. * - When not provided, it will refresh all the currently connected Peers in the Room.
  23. * @param {Boolean} [iceRestart=false] <blockquote class="info">
  24. * Note that this flag will not be honoured for MCU enabled Peer connections where
  25. * <code>options.mcuUseRenegoRestart</code> flag is set to <code>false</code> as it is not necessary since for MCU
  26. * "restart" case is to invoke <a href="#method_joinRoom"><code>joinRoom()</code> method</a> again, or that it is
  27. * not supported by the MCU.</blockquote>
  28. * The flag if ICE connections should restart when refreshing Peer connections.
  29. * <small>This is used when ICE connection state is <code>FAILED</code> or <code>DISCONNECTED</code>, which state
  30. * can be retrieved with the <a href="#event_iceConnectionState"><code>iceConnectionState</code> event</a>.</small>
  31. * @param {JSON} [options] <blockquote class="info">
  32. * Note that for MCU connections, the <code>bandwidth</code> or <code>googleXBandwidth</code>
  33. * settings will override for all Peers or the current Room connection session settings.</blockquote>
  34. * The custom Peer configuration settings.
  35. * @param {JSON} [options.bandwidth] The configuration to set the maximum streaming bandwidth to send to Peers.
  36. * <small>Object signature follows <a href="#method_joinRoom"><code>joinRoom()</code> method</a>
  37. * <code>options.bandwidth</code> settings.</small>
  38. * @param {JSON} [options.googleXBandwidth] The configuration to set the experimental google
  39. * video streaming bandwidth sent to Peers.
  40. * <small>Object signature follows <a href="#method_joinRoom"><code>joinRoom()</code> method</a>
  41. * <code>options.googleXBandwidth</code> settings.</small>
  42. * @param {Function} [callback] The callback function fired when request has completed.
  43. * <small>Function parameters signature is <code>function (error, success)</code></small>
  44. * <small>Function request completion is determined by the <a href="#event_peerRestart">
  45. * <code>peerRestart</code> event</a> triggering <code>isSelfInitiateRestart</code> parameter payload
  46. * value as <code>true</code> for all Peers targeted for request success.</small>
  47. * @param {JSON} callback.error The error result in request.
  48. * <small>Defined as <code>null</code> when there are no errors in request</small>
  49. * @param {Array} callback.error.listOfPeers The list of Peer IDs targeted.
  50. * @param {JSON} callback.error.refreshErrors The list of Peer connection refresh errors.
  51. * @param {Error|String} callback.error.refreshErrors.#peerId The Peer connection refresh error associated
  52. * with the Peer ID defined in <code>#peerId</code> property.
  53. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  54. * is no Peer connections to refresh with.</small>
  55. * @param {JSON} callback.error.refreshSettings The list of Peer connection refresh settings.
  56. * @param {JSON} callback.error.refreshSettings.#peerId The Peer connection refresh settings associated
  57. * with the Peer ID defined in <code>#peerId</code> property.
  58. * @param {Boolean} callback.error.refreshSettings.#peerId.iceRestart The flag if ICE restart is enabled for
  59. * this Peer connection refresh session.
  60. * @param {JSON} callback.error.refreshSettings.#peerId.customSettings The Peer connection custom settings.
  61. * <small>Object signature follows <a href="#method_getPeersCustomSettings"><code>getPeersCustomSettings</code>
  62. * method</a> returned per <code>#peerId</code> object.</small>
  63. * @param {JSON} callback.success The success result in request.
  64. * <small>Defined as <code>null</code> when there are errors in request</small>
  65. * @param {Array} callback.success.listOfPeers The list of Peer IDs targeted.
  66. * @param {JSON} callback.success.refreshSettings The list of Peer connection refresh settings.
  67. * @param {JSON} callback.success.refreshSettings.#peerId The Peer connection refresh settings associated
  68. * with the Peer ID defined in <code>#peerId</code> property.
  69. * @param {Boolean} callback.success.refreshSettings.#peerId.iceRestart The flag if ICE restart is enabled for
  70. * this Peer connection refresh session.
  71. * @param {JSON} callback.success.refreshSettings.#peerId.customSettings The Peer connection custom settings.
  72. * <small>Object signature follows <a href="#method_getPeersCustomSettings"><code>getPeersCustomSettings</code>
  73. * method</a> returned per <code>#peerId</code> object.</small>
  74. * @trigger <ol class="desc-seq">
  75. * <li>Checks if MCU is enabled for App Key provided in <a href="#method_init"><code>init()</code> method</a><ol>
  76. * <li>If MCU is enabled: <ol><li>If there are connected Peers in the Room: <ol>
  77. * <li><a href="#event_peerRestart"><code>peerRestart</code> event</a> triggers parameter payload
  78. * <code>isSelfInitiateRestart</code> value as <code>true</code> for all connected Peer connections.</li>
  79. * <li><a href="#event_serverPeerRestart"><code>serverPeerRestart</code> event</a> triggers for
  80. * connected MCU server Peer connection.</li></ol></li>
  81. * <li>If <code>options.mcuUseRenegoRestart</code> value is <code>false</code> set in the
  82. * <a href="#method_init"><code>init()</code> method</a>: <ol><li>
  83. * Invokes <a href="#method_joinRoom"><code>joinRoom()</code> method</a> <small><code>refreshConnection()</code>
  84. * will retain the User session information except the Peer ID will be a different assigned ID due to restarting the
  85. * Room session.</small> <ol><li>If request has errors <ol><li><b>ABORT</b> and return error.
  86. * </li></ol></li></ol></li></ol></li></ol></li>
  87. * <li>Else: <ol><li>If there are connected Peers in the Room: <ol>
  88. * <li>Refresh connections for all targeted Peers. <ol>
  89. * <li>If Peer connection exists: <ol>
  90. * <li><a href="#event_peerRestart"><code>peerRestart</code> event</a> triggers parameter payload
  91. * <code>isSelfInitiateRestart</code> value as <code>true</code> for all targeted Peer connections.</li></ol></li>
  92. * <li>Else: <ol><li><b>ABORT</b> and return error.</li></ol></li>
  93. * </ol></li></ol></li></ol></ol></li></ol></li></ol>
  94. * @example
  95. * // Example 1: Refreshing a Peer connection
  96. * function refreshFrozenVideoStream (peerId) {
  97. * skylinkDemo.refreshConnection(peerId, function (error, success) {
  98. * if (error) return;
  99. * console.log("Refreshing connection for '" + peerId + "'");
  100. * });
  101. * }
  102. *
  103. * // Example 2: Refreshing a list of Peer connections
  104. * function refreshFrozenVideoStreamGroup (peerIdA, peerIdB) {
  105. * skylinkDemo.refreshConnection([peerIdA, peerIdB], function (error, success) {
  106. * if (error) {
  107. * if (error.transferErrors[peerIdA]) {
  108. * console.error("Failed refreshing connection for '" + peerIdA + "'");
  109. * } else {
  110. * console.log("Refreshing connection for '" + peerIdA + "'");
  111. * }
  112. * if (error.transferErrors[peerIdB]) {
  113. * console.error("Failed refreshing connection for '" + peerIdB + "'");
  114. * } else {
  115. * console.log("Refreshing connection for '" + peerIdB + "'");
  116. * }
  117. * } else {
  118. * console.log("Refreshing connection for '" + peerIdA + "' and '" + peerIdB + "'");
  119. * }
  120. * });
  121. * }
  122. *
  123. * // Example 3: Refreshing all Peer connections
  124. * function refreshFrozenVideoStreamAll () {
  125. * skylinkDemo.refreshConnection(function (error, success) {
  126. * if (error) {
  127. * for (var i = 0; i < error.listOfPeers.length; i++) {
  128. * if (error.refreshErrors[error.listOfPeers[i]]) {
  129. * console.error("Failed refreshing connection for '" + error.listOfPeers[i] + "'");
  130. * } else {
  131. * console.info("Refreshing connection for '" + error.listOfPeers[i] + "'");
  132. * }
  133. * }
  134. * } else {
  135. * console.log("Refreshing connection for all Peers", success.listOfPeers);
  136. * }
  137. * });
  138. * }
  139. *
  140. * // Example 4: Refresh Peer connection when ICE connection has failed or disconnected
  141. * // and do a ICE connection refresh (only for non-MCU case)
  142. * skylinkDemo.on("iceConnectionState", function (state, peerId) {
  143. * if (!usesMCUKey && [skylinkDemo.ICE_CONNECTION_STATE.FAILED,
  144. * skylinkDemo.ICE_CONNECTION_STATE.DISCONNECTED].indexOf(state) > -1) {
  145. * skylinkDemo.refreshConnection(peerId, true);
  146. * }
  147. * });
  148. * @for Skylink
  149. * @since 0.5.5
  150. */
  151. Skylink.prototype.refreshConnection = function(targetPeerId, iceRestart, options, callback) {
  152. var self = this;
  153.  
  154. var listOfPeers = Object.keys(self._peerConnections);
  155. var doIceRestart = false;
  156. var bwOptions = {};
  157.  
  158. if(Array.isArray(targetPeerId)) {
  159. listOfPeers = targetPeerId;
  160. } else if (typeof targetPeerId === 'string') {
  161. listOfPeers = [targetPeerId];
  162. } else if (typeof targetPeerId === 'boolean') {
  163. doIceRestart = targetPeerId;
  164. } else if (targetPeerId && typeof targetPeerId === 'object') {
  165. bwOptions = targetPeerId;
  166. } else if (typeof targetPeerId === 'function') {
  167. callback = targetPeerId;
  168. }
  169.  
  170. if (typeof iceRestart === 'boolean') {
  171. doIceRestart = iceRestart;
  172. } else if (iceRestart && typeof iceRestart === 'object') {
  173. bwOptions = iceRestart;
  174. } else if (typeof iceRestart === 'function') {
  175. callback = iceRestart;
  176. }
  177.  
  178. if (options && typeof options === 'object') {
  179. bwOptions = options;
  180. } else if (typeof options === 'function') {
  181. callback = options;
  182. }
  183.  
  184. var emitErrorForPeersFn = function (error) {
  185. log.error(error);
  186.  
  187. if (typeof callback === 'function') {
  188. var listOfPeerErrors = {};
  189.  
  190. if (listOfPeers.length === 0) {
  191. listOfPeerErrors.self = new Error(error);
  192. } else {
  193. for (var i = 0; i < listOfPeers.length; i++) {
  194. listOfPeerErrors[listOfPeers[i]] = new Error(error);
  195. }
  196. }
  197.  
  198. callback({
  199. refreshErrors: listOfPeerRestartErrors,
  200. listOfPeers: listOfPeers
  201. }, null);
  202. }
  203. };
  204.  
  205. if (listOfPeers.length === 0 && !(self._hasMCU && !self._initOptions.mcuUseRenegoRestart)) {
  206. emitErrorForPeersFn('There is currently no peer connections to restart');
  207. return;
  208. }
  209.  
  210. if (AdapterJS.webrtcDetectedBrowser === 'edge') {
  211. emitErrorForPeersFn('Edge browser currently does not support renegotiation.');
  212. return;
  213. }
  214.  
  215. self._throttle(function (runFn) {
  216. if (!runFn && self._hasMCU && !self._initOptions.mcuUseRenegoRestart) {
  217. if (self._initOptions.throttlingShouldThrowError) {
  218. emitErrorForPeersFn('Unable to run as throttle interval has not reached (' + self._initOptions.throttleIntervals.refreshConnection + 'ms).');
  219. }
  220. return;
  221. }
  222. self._refreshPeerConnection(listOfPeers, doIceRestart, bwOptions, callback);
  223. }, 'refreshConnection', self._initOptions.throttleIntervals.refreshConnection);
  224.  
  225. };
  226.  
  227. /**
  228. * Function that refresh connections.
  229. * @method _refreshPeerConnection
  230. * @private
  231. * @for Skylink
  232. * @since 0.6.15
  233. */
  234. Skylink.prototype._refreshPeerConnection = function(listOfPeers, doIceRestart, bwOptions, callback) {
  235. var self = this;
  236. var listOfPeerRestarts = [];
  237. var error = '';
  238. var listOfPeerRestartErrors = {};
  239. var listOfPeersSettings = {};
  240.  
  241. // To fix jshint dont put functions within a loop
  242. var refreshSinglePeerCallback = function (peerId) {
  243. return function (error) {
  244. if (listOfPeerRestarts.indexOf(peerId) === -1) {
  245. if (error) {
  246. log.error([peerId, 'RTCPeerConnection', null, 'Failed restarting for peer'], error);
  247. listOfPeerRestartErrors[peerId] = error;
  248. } else {
  249. listOfPeersSettings[peerId] = {
  250. iceRestart: !self._hasMCU && self._peerInformations[peerId] && self._peerInformations[peerId].config &&
  251. self._peerInformations[peerId].config.enableIceRestart && self._enableIceRestart && doIceRestart,
  252. customSettings: self.getPeersCustomSettings()[peerId] || {}
  253. };
  254. }
  255. listOfPeerRestarts.push(peerId);
  256. }
  257.  
  258. if (listOfPeerRestarts.length === listOfPeers.length) {
  259. if (typeof callback === 'function') {
  260. log.log([null, 'PeerConnection', null, 'Invoked all peers to restart. Firing callback']);
  261.  
  262. if (Object.keys(listOfPeerRestartErrors).length > 0) {
  263. callback({
  264. refreshErrors: listOfPeerRestartErrors,
  265. listOfPeers: listOfPeers,
  266. refreshSettings: listOfPeersSettings
  267. }, null);
  268. } else {
  269. callback(null, {
  270. listOfPeers: listOfPeers,
  271. refreshSettings: listOfPeersSettings
  272. });
  273. }
  274. }
  275. }
  276. };
  277. };
  278.  
  279. var refreshSinglePeer = function(peerId, peerCallback){
  280. if (!self._peerConnections[peerId]) {
  281. error = 'There is currently no existing peer connection made ' +
  282. 'with the peer. Unable to restart connection';
  283. log.error([peerId, null, null, error]);
  284. peerCallback(error);
  285. return;
  286. }
  287.  
  288. log.log([peerId, 'PeerConnection', null, 'Restarting peer connection']);
  289.  
  290. // do a hard reset on variable object
  291. self._restartPeerConnection(peerId, doIceRestart, bwOptions, peerCallback);
  292. };
  293.  
  294. if(!self._hasMCU) {
  295. var i;
  296.  
  297. for (i = 0; i < listOfPeers.length; i++) {
  298. var peerId = listOfPeers[i];
  299.  
  300. if (Object.keys(self._peerConnections).indexOf(peerId) > -1) {
  301. refreshSinglePeer(peerId, refreshSinglePeerCallback(peerId));
  302. } else {
  303. error = 'Peer connection with peer does not exists. Unable to restart';
  304. log.error([peerId, 'PeerConnection', null, error]);
  305. refreshSinglePeerCallback(peerId)(error);
  306. }
  307. }
  308. } else {
  309. self._restartMCUConnection(callback, doIceRestart, bwOptions);
  310. }
  311. };
  312.  
  313. /**
  314. * <blockquote class="info">
  315. * Note that this is not well supported in the Edge browser.
  316. * </blockquote>
  317. * Function that retrieves Peer connection bandwidth and ICE connection stats.
  318. * @method getConnectionStatus
  319. * @param {String|Array} [targetPeerId] The target Peer ID to retrieve connection stats from.
  320. * - When provided as an Array, it will retrieve all connection stats from all the Peer IDs provided.
  321. * - When not provided, it will retrieve all connection stats from the currently connected Peers in the Room.
  322. * @param {Function} [callback] The callback function fired when request has completed.
  323. * <small>Function parameters signature is <code>function (error, success)</code></small>
  324. * <small>Function request completion is determined by the <a href="#event_getConnectionStatusStateChange">
  325. * <code>getConnectionStatusStateChange</code> event</a> triggering <code>state</code> parameter payload
  326. * value as <code>RETRIEVE_SUCCESS</code> for all Peers targeted for request success.</small>
  327. * [Rel: Skylink.GET_CONNECTION_STATUS_STATE]
  328. * @param {JSON} callback.error The error result in request.
  329. * <small>Defined as <code>null</code> when there are no errors in request</small>
  330. * @param {Array} callback.error.listOfPeers The list of Peer IDs targeted.
  331. * @param {JSON} callback.error.retrievalErrors The list of Peer connection stats retrieval errors.
  332. * @param {Error|String} callback.error.retrievalErrors.#peerId The Peer connection stats retrieval error associated
  333. * with the Peer ID defined in <code>#peerId</code> property.
  334. * <small>If <code>#peerId</code> value is <code>"self"</code>, it means that it is the error when there
  335. * are no Peer connections to refresh with.</small>
  336. * @param {JSON} callback.error.connectionStats The list of Peer connection stats.
  337. * <small>These are the Peer connection stats that has been managed to be successfully retrieved.</small>
  338. * @param {JSON} callback.error.connectionStats.#peerId The Peer connection stats associated with
  339. * the Peer ID defined in <code>#peerId</code> property.
  340. * <small>Object signature matches the <code>stats</code> parameter payload received in the
  341. * <a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>.</small>
  342. * @param {JSON} callback.success The success result in request.
  343. * <small>Defined as <code>null</code> when there are errors in request</small>
  344. * @param {Array} callback.success.listOfPeers The list of Peer IDs targeted.
  345. * @param {JSON} callback.success.connectionStats The list of Peer connection stats.
  346. * @param {JSON} callback.success.connectionStats.#peerId The Peer connection stats associated with
  347. * the Peer ID defined in <code>#peerId</code> property.
  348. * <small>Object signature matches the <code>stats</code> parameter payload received in the
  349. * <a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>.</small>
  350. * @trigger <ol class="desc-seq">
  351. * <li>Retrieves Peer connection stats for all targeted Peers. <ol>
  352. * <li>If Peer connection has closed or does not exists: <small>This can be checked with
  353. * <a href="#event_peerConnectionState"><code>peerConnectionState</code> event</a>
  354. * triggering parameter payload <code>state</code> as <code>CLOSED</code> for Peer.</small> <ol>
  355. * <li><a href="#event_getConnectionStatusStateChange"> <code>getConnectionStatusStateChange</code> event</a>
  356. * triggers parameter payload <code>state</code> as <code>RETRIEVE_ERROR</code>.</li>
  357. * <li><b>ABORT</b> and return error.</li></ol></li>
  358. * <li><a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>
  359. * triggers parameter payload <code>state</code> as <code>RETRIEVING</code>.</li>
  360. * <li>Received response from retrieval. <ol>
  361. * <li>If retrieval was successful: <ol>
  362. * <li><a href="#event_getConnectionStatusStateChange"><code>getConnectionStatusStateChange</code> event</a>
  363. * triggers parameter payload <code>state</code> as <code>RETRIEVE_SUCCESS</code>.</li></ol></li>
  364. * <li>Else: <ol>
  365. * <li><a href="#event_getConnectionStatusStateChange"> <code>getConnectionStatusStateChange</code> event</a>
  366. * triggers parameter payload <code>state</code> as <code>RETRIEVE_ERROR</code>.</li>
  367. * </ol></li></ol></li></ol></li></ol>
  368. * @example
  369. * // Example 1: Retrieve a Peer connection stats
  370. * function startBWStatsInterval (peerId) {
  371. * setInterval(function () {
  372. * skylinkDemo.getConnectionStatus(peerId, function (error, success) {
  373. * if (error) return;
  374. * var sendVideoBytes = success.connectionStats[peerId].video.sending.bytes;
  375. * var sendAudioBytes = success.connectionStats[peerId].audio.sending.bytes;
  376. * var recvVideoBytes = success.connectionStats[peerId].video.receiving.bytes;
  377. * var recvAudioBytes = success.connectionStats[peerId].audio.receiving.bytes;
  378. * var localCandidate = success.connectionStats[peerId].selectedCandidate.local;
  379. * var remoteCandidate = success.connectionStats[peerId].selectedCandidate.remote;
  380. * console.log("Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  381. * console.log("Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  382. * console.log("Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  383. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  384. * console.log("Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  385. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  386. * });
  387. * }, 1000);
  388. * }
  389. *
  390. * // Example 2: Retrieve a list of Peer connection stats
  391. * function printConnStats (peerId, data) {
  392. * if (!data.connectionStats[peerId]) return;
  393. * var sendVideoBytes = data.connectionStats[peerId].video.sending.bytes;
  394. * var sendAudioBytes = data.connectionStats[peerId].audio.sending.bytes;
  395. * var recvVideoBytes = data.connectionStats[peerId].video.receiving.bytes;
  396. * var recvAudioBytes = data.connectionStats[peerId].audio.receiving.bytes;
  397. * var localCandidate = data.connectionStats[peerId].selectedCandidate.local;
  398. * var remoteCandidate = data.connectionStats[peerId].selectedCandidate.remote;
  399. * console.log(peerId + " - Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  400. * console.log(peerId + " - Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  401. * console.log(peerId + " - Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  402. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  403. * console.log(peerId + " - Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  404. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  405. * }
  406. *
  407. * function startBWStatsInterval (peerIdA, peerIdB) {
  408. * setInterval(function () {
  409. * skylinkDemo.getConnectionStatus([peerIdA, peerIdB], function (error, success) {
  410. * if (error) {
  411. * printConnStats(peerIdA, error.connectionStats);
  412. * printConnStats(peerIdB, error.connectionStats);
  413. * } else {
  414. * printConnStats(peerIdA, success.connectionStats);
  415. * printConnStats(peerIdB, success.connectionStats);
  416. * }
  417. * });
  418. * }, 1000);
  419. * }
  420. *
  421. * // Example 3: Retrieve all Peer connection stats
  422. * function printConnStats (listOfPeers, data) {
  423. * listOfPeers.forEach(function (peerId) {
  424. * if (!data.connectionStats[peerId]) return;
  425. * var sendVideoBytes = data.connectionStats[peerId].video.sending.bytes;
  426. * var sendAudioBytes = data.connectionStats[peerId].audio.sending.bytes;
  427. * var recvVideoBytes = data.connectionStats[peerId].video.receiving.bytes;
  428. * var recvAudioBytes = data.connectionStats[peerId].audio.receiving.bytes;
  429. * var localCandidate = data.connectionStats[peerId].selectedCandidate.local;
  430. * var remoteCandidate = data.connectionStats[peerId].selectedCandidate.remote;
  431. * console.log(peerId + " - Sending audio (" + sendAudioBytes + "bps) video (" + sendVideoBytes + ")");
  432. * console.log(peerId + " - Receiving audio (" + recvAudioBytes + "bps) video (" + recvVideoBytes + ")");
  433. * console.log(peerId + " - Local candidate: " + localCandidate.ipAddress + ":" + localCandidate.portNumber +
  434. * "?transport=" + localCandidate.transport + " (type: " + localCandidate.candidateType + ")");
  435. * console.log(peerId + " - Remote candidate: " + remoteCandidate.ipAddress + ":" + remoteCandidate.portNumber +
  436. * "?transport=" + remoteCandidate.transport + " (type: " + remoteCandidate.candidateType + ")");
  437. * });
  438. * }
  439. *
  440. * function startBWStatsInterval (peerIdA, peerIdB) {
  441. * setInterval(function () {
  442. * skylinkDemo.getConnectionStatus(function (error, success) {
  443. * if (error) {
  444. * printConnStats(error.listOfPeers, error.connectionStats);
  445. * } else {
  446. * printConnStats(success.listOfPeers, success.connectionStats);
  447. * }
  448. * });
  449. * }, 1000);
  450. * }
  451. * @for Skylink
  452. * @since 0.6.14
  453. */
  454. Skylink.prototype.getConnectionStatus = function (targetPeerId, callback) {
  455. var self = this;
  456. var listOfPeers = Object.keys(self._peerConnections);
  457. var listOfPeerStats = {};
  458. var listOfPeerErrors = {};
  459.  
  460. // getConnectionStatus([])
  461. if (Array.isArray(targetPeerId)) {
  462. listOfPeers = targetPeerId;
  463.  
  464. // getConnectionStatus('...')
  465. } else if (typeof targetPeerId === 'string' && !!targetPeerId) {
  466. listOfPeers = [targetPeerId];
  467.  
  468. // getConnectionStatus(function () {})
  469. } else if (typeof targetPeerId === 'function') {
  470. callback = targetPeerId;
  471. targetPeerId = undefined;
  472. }
  473.  
  474. // Check if Peers list is empty, in which we throw an Error if there isn't any
  475. if (listOfPeers.length === 0) {
  476. listOfPeerErrors.self = new Error('There is currently no peer connections to retrieve connection status');
  477.  
  478. log.error([null, 'RTCStatsReport', null, 'Retrieving request failure ->'], listOfPeerErrors.self);
  479.  
  480. if (typeof callback === 'function') {
  481. callback({
  482. listOfPeers: listOfPeers,
  483. retrievalErrors: listOfPeerErrors,
  484. connectionStats: listOfPeerStats
  485. }, null);
  486. }
  487. return;
  488. }
  489.  
  490. if (AdapterJS.webrtcDetectedBrowser === 'edge') {
  491. log.warn('Edge browser does not have well support for stats.');
  492. }
  493.  
  494. var completedTaskCounter = [];
  495.  
  496. var checkCompletedFn = function (peerId) {
  497. if (completedTaskCounter.indexOf(peerId) === -1) {
  498. completedTaskCounter.push(peerId);
  499. }
  500.  
  501. if (completedTaskCounter.length === listOfPeers.length) {
  502. if (typeof callback === 'function') {
  503. if (Object.keys(listOfPeerErrors).length > 0) {
  504. callback({
  505. listOfPeers: listOfPeers,
  506. retrievalErrors: listOfPeerErrors,
  507. connectionStats: listOfPeerStats
  508. }, null);
  509.  
  510. } else {
  511. callback(null, {
  512. listOfPeers: listOfPeers,
  513. connectionStats: listOfPeerStats
  514. });
  515. }
  516. }
  517. }
  518. };
  519.  
  520. var statsFn = function (peerId) {
  521. var retrieveFn = function (firstRetrieval, nextCb) {
  522. return function (err, result) {
  523. if (err) {
  524. log.error([peerId, 'RTCStatsReport', null, 'Retrieval failure ->'], error);
  525. listOfPeerErrors[peerId] = error;
  526. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_ERROR,
  527. peerId, null, error);
  528. checkCompletedFn(peerId);
  529. if (firstRetrieval) {
  530. delete self._peerStats[peerId];
  531. }
  532. return;
  533. }
  534.  
  535. if (firstRetrieval) {
  536. nextCb();
  537. } else {
  538. listOfPeerStats[peerId] = result;
  539. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_SUCCESS,
  540. peerId, listOfPeerStats[peerId], null);
  541. checkCompletedFn(peerId);
  542. }
  543. };
  544. };
  545.  
  546. if (!self._peerStats[peerId]) {
  547. self._peerStats[peerId] = {};
  548.  
  549. log.debug([peerId, 'RTCStatsReport', null, 'Retrieving first report to tabulate results']);
  550.  
  551. self._retrieveStats(peerId, retrieveFn(true, function () {
  552. self._retrieveStats(peerId, retrieveFn());
  553. }), true);
  554. return;
  555. }
  556.  
  557. self._retrieveStats(peerId, retrieveFn());
  558. };
  559.  
  560. // Loop through all the list of Peers selected to retrieve connection status
  561. for (var i = 0; i < listOfPeers.length; i++) {
  562. var peerId = listOfPeers[i];
  563.  
  564. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVING,
  565. peerId, null, null);
  566.  
  567. // Check if the Peer connection exists first
  568. if (self._peerConnections.hasOwnProperty(peerId) && self._peerConnections[peerId]) {
  569. statsFn(peerId);
  570.  
  571. } else {
  572. listOfPeerErrors[peerId] = new Error('The peer connection object does not exists');
  573.  
  574. log.error([peerId, 'RTCStatsReport', null, 'Retrieval failure ->'], listOfPeerErrors[peerId]);
  575.  
  576. self._trigger('getConnectionStatusStateChange', self.GET_CONNECTION_STATUS_STATE.RETRIEVE_ERROR,
  577. peerId, null, listOfPeerErrors[peerId]);
  578.  
  579. checkCompletedFn(peerId);
  580. }
  581. }
  582. };
  583.  
  584. /**
  585. * Function that retrieves Peer connection stats.
  586. * @method _retrieveStats
  587. * @private
  588. * @for Skylink
  589. * @since 0.6.18
  590. */
  591. Skylink.prototype._retrieveStats = function (peerId, callback, beSilentOnLogs, isAutoBwStats) {
  592. var self = this;
  593. var pc = self._peerConnections[peerId];
  594. var output = {
  595. raw: {},
  596. connection: {},
  597. audio: {
  598. sending: {},
  599. receiving: {} },
  600. video: {
  601. sending: {},
  602. receiving: {}
  603. },
  604. selectedCandidate: {
  605. local: {},
  606. remote: {},
  607. consentResponses: {},
  608. consentRequests: {},
  609. responses: {},
  610. requests: {}
  611. },
  612. certificate: {}
  613. };
  614.  
  615. // Peer stats has to be retrieved once first before the second time.
  616. if (!self._peerStats[peerId] && !isAutoBwStats) {
  617. return callback(new Error('No stats initiated yet.'));
  618. } else if (!pc) {
  619. return callback(new Error('Peer connection is not initialised'));
  620. }
  621.  
  622. // Warn due to Edge not giving complete stats and returning as 0 sometimes..
  623. if (AdapterJS.webrtcDetectedBrowser === 'edge' || AdapterJS.webrtcDetectedType === 'AppleWebKit') {
  624. log.warn('Current connection stats may not be complete as it is in beta');
  625. }
  626.  
  627. // Parse RTCPeerConnection details
  628. output.connection.iceConnectionState = pc.iceConnectionState;
  629. output.connection.iceGatheringState = pc.iceGatheringState;
  630. output.connection.signalingState = pc.signalingState;
  631. output.connection.remoteDescription = {
  632. type: (pc.remoteDescription && pc.remoteDescription.type) || '',
  633. sdp : (pc.remoteDescription && pc.remoteDescription.sdp) || ''
  634. };
  635. output.connection.localDescription = {
  636. type: (pc.localDescription && pc.localDescription.type) || '',
  637. sdp : (pc.localDescription && pc.localDescription.sdp) || ''
  638. };
  639. output.connection.candidates = {
  640. sending: self._getSDPICECandidates(peerId, pc.localDescription, beSilentOnLogs),
  641. receiving: self._getSDPICECandidates(peerId, pc.remoteDescription, beSilentOnLogs)
  642. };
  643. output.connection.dataChannels = {};
  644. output.connection.constraints = self._peerConnStatus[peerId] ? self._peerConnStatus[peerId].constraints : null;
  645. output.connection.optional = self._peerConnStatus[peerId] ? self._peerConnStatus[peerId].optional : null;
  646. output.connection.sdpConstraints = self._peerConnStatus[peerId] ? self._peerConnStatus[peerId].sdpConstraints : null;
  647.  
  648. // Parse workaround possible codecs details
  649. output.audio.sending.codec = self._getSDPSelectedCodec(peerId, pc.remoteDescription, 'audio', beSilentOnLogs);
  650. output.video.sending.codec = self._getSDPSelectedCodec(peerId, pc.remoteDescription, 'video', beSilentOnLogs);
  651. output.audio.receiving.codec = self._getSDPSelectedCodec(peerId, pc.localDescription, 'audio', beSilentOnLogs);
  652. output.video.receiving.codec = self._getSDPSelectedCodec(peerId, pc.localDescription, 'video', beSilentOnLogs);
  653.  
  654. // Parse workaround possible certificate details
  655. output.certificate.local = self._getSDPFingerprint(peerId, pc.localDescription, beSilentOnLogs);
  656. output.certificate.remote = self._getSDPFingerprint(peerId, pc.remoteDescription, beSilentOnLogs);
  657.  
  658. // Parse workaround possible SSRC details to prevent receiving 0 from Safari 11
  659. var inboundSSRCs = self._getSDPMediaSSRC(peerId, pc.remoteDescription, beSilentOnLogs);
  660. output.audio.receiving.ssrc = inboundSSRCs.audio;
  661. output.video.receiving.ssrc = inboundSSRCs.video;
  662. var outboundSSRCs = self._getSDPMediaSSRC(peerId, pc.localDescription, beSilentOnLogs);
  663. output.audio.sending.ssrc = outboundSSRCs.audio;
  664. output.video.sending.ssrc = outboundSSRCs.video;
  665.  
  666. // Parse RTCDataChannel details (not stats)
  667. Object.keys(self._dataChannels[peerId] || {}).forEach(function (prop) {
  668. var channel = self._dataChannels[peerId][prop];
  669. output.connection.dataChannels[channel.channel.label] = {
  670. label: channel.channel.label,
  671. readyState: channel.channel.readyState,
  672. channelType: self.DATA_CHANNEL_TYPE[prop === 'main' ? 'MESSAGING' : 'DATA'],
  673. currentTransferId: channel.transferId || null,
  674. currentStreamId: channel.streamId || null
  675. };
  676. });
  677.  
  678. // Format DTLS certificates and ciphers used
  679. var certificateFn = function (item, prop) {
  680. // Safari 11
  681. if (prop.indexOf('RTCCertificate_') === 0) {
  682. // Map the certificate data basing off the fingerprint algorithm
  683. if (item.fingerprint === output.certificate.local.fingerprint) {
  684. output.certificate.local.derBase64 = item.base64Certificate;
  685. output.certificate.local.fingerprintAlgorithm = item.fingerprintAlgorithm;
  686.  
  687. } else if (item.fingerprint === output.certificate.remote.fingerprint) {
  688. output.certificate.remote.derBase64 = item.base64Certificate;
  689. output.certificate.remote.fingerprintAlgorithm = item.fingerprintAlgorithm;
  690. }
  691.  
  692. // Chrome / Plugin
  693. } else if (prop.indexOf('ssrc_') === 0 && item.transportId) {
  694. var pairItem = output.raw[item.transportId] || {};
  695. output.certificate.srtpCipher = pairItem.srtpCipher;
  696. output.certificate.dtlsCipher = pairItem.dtlsCipher;
  697.  
  698. var localCertItem = output.raw[pairItem.localCertificateId || ''] || {};
  699. output.certificate.local.fingerprint = localCertItem.googFingerprint;
  700. output.certificate.local.fingerprintAlgorithm = localCertItem.googFingerprintAlgorithm;
  701. output.certificate.local.derBase64 = localCertItem.googDerBase64;
  702.  
  703. var remoteCertItem = output.raw[pairItem.remoteCertificateId || ''] || {};
  704. output.certificate.remote.fingerprint = remoteCertItem.googFingerprint;
  705. output.certificate.remote.fingerprintAlgorithm = remoteCertItem.googFingerprintAlgorithm;
  706. output.certificate.remote.derBase64 = remoteCertItem.googDerBase64;
  707. }
  708. };
  709.  
  710. // Format selected candidate pair
  711. var candidatePairFn = function (item, prop) {
  712. // Safari 11
  713. if (prop.indexOf('RTCIceCandidatePair_') === 0) {
  714. // Use the nominated pair, else use the one that has succeeded but not yet nominated.
  715. // This is to handle the case where none of the ICE candidates appear nominated.
  716. if (item.state !== 'succeeded' || (output.selectedCandidate.nominated ? true :
  717. (item.prioirty < (output.selectedCandidate.priority || 0)))) {
  718. return;
  719. }
  720.  
  721. var prevStats = isAutoBwStats ? self._peerBandwidth[peerId][prop] : self._peerStats[peerId][prop];
  722.  
  723. // Map the selected ICE candidate pair based on computed prioirty
  724. var sending = (pc.localDescription && pc.localDescription.sdp && pc.localDescription.sdp.match(/a=candidate:.*\r\n/gi)) || [];
  725. var receiving = (pc.remoteDescription && pc.remoteDescription && pc.remoteDescription.sdp.match(/a=candidate:.*\r\n/gi)) || [];
  726.  
  727. // Compute the priority
  728. var computePrioirtyFn = function (controller, controlled) {
  729. return (Math.pow(2, 32) * Math.min(controller, controlled)) + (2 * Math.max(controller, controlled)) + (controller > controlled ? 1 : 0);
  730. };
  731.  
  732. // Format the candidate type
  733. var computeCanTypeFn = function (type) {
  734. if (type === 'relay') {
  735. return 'relayed';
  736. } else if (type === 'host') {
  737. return 'local';
  738. } else if (type === 'srflx') {
  739. return 'serverreflexive';
  740. }
  741. return type;
  742. };
  743.  
  744. for (var s = 0; s < sending.length; s++) {
  745. var sendCanParts = sending[s].split(' ');
  746.  
  747. for (var r = 0; r < receiving.length; r++) {
  748. var recvCanParts = receiving[r].split(' ');
  749. var priority = null;
  750.  
  751. if (item.writable) {
  752. // Compute the priority since we are the controller
  753. priority = computePrioirtyFn(parseInt(sendCanParts[3], 10), parseInt(recvCanParts[3], 10));
  754. } else {
  755. // Compute the priority since we are the controlled
  756. priority = computePrioirtyFn(parseInt(recvCanParts[3], 10), parseInt(sendCanParts[3], 10));
  757. }
  758.  
  759. if (priority === item.priority) {
  760. output.selectedCandidate.local.ipAddress = sendCanParts[4];
  761. output.selectedCandidate.local.candidateType = sendCanParts[7];
  762. output.selectedCandidate.local.portNumber = parseInt(sendCanParts[5], 10);
  763. output.selectedCandidate.local.transport = sendCanParts[2];
  764. output.selectedCandidate.local.priority = parseInt(sendCanParts[3], 10);
  765. output.selectedCandidate.local.candidateType = computeCanTypeFn(sendCanParts[7]);
  766.  
  767. output.selectedCandidate.remote.ipAddress = recvCanParts[4];
  768. output.selectedCandidate.remote.candidateType = recvCanParts[7];
  769. output.selectedCandidate.remote.portNumber = parseInt(recvCanParts[5], 10);
  770. output.selectedCandidate.remote.transport = recvCanParts[2];
  771. output.selectedCandidate.remote.priority = parseInt(recvCanParts[3], 10);
  772. output.selectedCandidate.remote.candidateType = computeCanTypeFn(recvCanParts[7]);
  773. break;
  774. }
  775. }
  776. }
  777.  
  778. output.selectedCandidate.writable = item.writable;
  779. output.selectedCandidate.readable = item.readable;
  780. output.selectedCandidate.priority = item.priority;
  781. output.selectedCandidate.nominated = item.nominated;
  782.  
  783. var rtt = parseInt(item.rtt || '0', 10);
  784. output.selectedCandidate.totalRtt = rtt;
  785. output.selectedCandidate.rtt = self._parseConnectionStats(prevStats, item, 'rtt');
  786.  
  787. var consentResponsesReceived = parseInt(item.consentResponsesReceived || '0', 10);
  788. output.selectedCandidate.consentResponses.totalReceived = consentResponsesReceived;
  789. output.selectedCandidate.consentResponses.received = self._parseConnectionStats(prevStats, item, 'consentResponsesReceived');
  790.  
  791. var consentResponsesSent = parseInt(item.consentResponsesSent || '0', 10);
  792. output.selectedCandidate.consentResponses.totalSent = consentResponsesSent;
  793. output.selectedCandidate.consentResponses.sent = self._parseConnectionStats(prevStats, item, 'consentResponsesSent');
  794.  
  795. var responsesReceived = parseInt(item.responsesReceived || '0', 10);
  796. output.selectedCandidate.responses.totalReceived = responsesReceived;
  797. output.selectedCandidate.responses.received = self._parseConnectionStats(prevStats, item, 'responsesReceived');
  798.  
  799. var responsesSent = parseInt(item.responsesSent || '0', 10);
  800. output.selectedCandidate.responses.totalSent = responsesSent;
  801. output.selectedCandidate.responses.sent = self._parseConnectionStats(prevStats, item, 'responsesSent');
  802.  
  803. // Chrome / Plugin
  804. } else if (item.type === 'googCandidatePair') {
  805. var prevStats = isAutoBwStats ? self._peerBandwidth[peerId][prop] : self._peerStats[peerId][prop];
  806.  
  807. output.selectedCandidate.writable = item.googWritable === 'true';
  808. output.selectedCandidate.readable = item.googReadable === 'true';
  809.  
  810. var rtt = parseInt(item.googRtt || '0', 10);
  811. output.selectedCandidate.totalRtt = rtt;
  812. output.selectedCandidate.rtt = self._parseConnectionStats(prevStats, item, 'rtt');
  813.  
  814. if (item.consentResponsesReceived) {
  815. var consentResponsesReceived = parseInt(item.consentResponsesReceived || '0', 10);
  816. output.selectedCandidate.consentResponses.totalReceived = consentResponsesReceived;
  817. output.selectedCandidate.consentResponses.received = self._parseConnectionStats(prevStats, item, 'consentResponsesReceived');
  818. }
  819.  
  820. if (item.consentResponsesSent) {
  821. var consentResponsesSent = parseInt(item.consentResponsesSent || '0', 10);
  822. output.selectedCandidate.consentResponses.totalSent = consentResponsesSent;
  823. output.selectedCandidate.consentResponses.sent = self._parseConnectionStats(prevStats, item, 'consentResponsesSent');
  824. }
  825.  
  826. if (item.responsesReceived) {
  827. var responsesReceived = parseInt(item.responsesReceived || '0', 10);
  828. output.selectedCandidate.responses.totalReceived = responsesReceived;
  829. output.selectedCandidate.responses.received = self._parseConnectionStats(prevStats, item, 'responsesReceived');
  830. }
  831.  
  832. if (item.responsesSent) {
  833. var responsesSent = parseInt(item.responsesSent || '0', 10);
  834. output.selectedCandidate.responses.totalSent = responsesSent;
  835. output.selectedCandidate.responses.sent = self._parseConnectionStats(prevStats, item, 'responsesSent');
  836. }
  837.  
  838. var localCanItem = output.raw[item.localCandidateId || ''] || {};
  839. output.selectedCandidate.local.ipAddress = localCanItem.ipAddress;
  840. output.selectedCandidate.local.portNumber = parseInt(localCanItem.portNumber, 10);
  841. output.selectedCandidate.local.priority = parseInt(localCanItem.priority, 10);
  842. output.selectedCandidate.local.networkType = localCanItem.networkType;
  843. output.selectedCandidate.local.transport = localCanItem.transport;
  844. output.selectedCandidate.local.candidateType = localCanItem.candidateType;
  845.  
  846. var remoteCanItem = output.raw[item.remoteCandidateId || ''] || {};
  847. output.selectedCandidate.remote.ipAddress = remoteCanItem.ipAddress;
  848. output.selectedCandidate.remote.portNumber = parseInt(remoteCanItem.portNumber, 10);
  849. output.selectedCandidate.remote.priority = parseInt(remoteCanItem.priority, 10);
  850. output.selectedCandidate.remote.transport = remoteCanItem.transport;
  851. output.selectedCandidate.remote.candidateType = remoteCanItem.candidateType;
  852.  
  853. // Firefox
  854. } else if (item.type === 'candidatepair' && item.state === 'succeeded' && item.nominated) {
  855. output.selectedCandidate.writable = item.writable;
  856. output.selectedCandidate.readable = item.readable;
  857.  
  858. var localCanItem = output.raw[item.localCandidateId || ''];
  859. output.selectedCandidate.local.ipAddress = localCanItem.ipAddress;
  860. output.selectedCandidate.local.portNumber = localCanItem.portNumber;
  861. output.selectedCandidate.local.transport = localCanItem.transport;
  862. output.selectedCandidate.local.candidateType = localCanItem.candidateType;
  863. output.selectedCandidate.local.turnMediaTransport = localCanItem.mozLocalTransport;
  864.  
  865. var remoteCanItem = output.raw[item.remoteCandidateId || ''];
  866. output.selectedCandidate.remote.ipAddress = remoteCanItem.ipAddress;
  867. output.selectedCandidate.remote.portNumber = remoteCanItem.portNumber;
  868. output.selectedCandidate.remote.transport = remoteCanItem.transport;
  869. output.selectedCandidate.remote.candidateType = remoteCanItem.candidateType;
  870. }
  871. };
  872.  
  873. // Format selected codecs
  874. var codecsFn = function (item, prop) {
  875. // Chrome / Plugin
  876. if (prop.indexOf('ssrc_') === 0) {
  877. var direction = prop.indexOf('_send') > 0 ? 'sending' : 'receiving';
  878.  
  879. item.codecImplementationName = item.codecImplementationName === 'unknown' ? null : item.codecImplementationName;
  880. output[item.mediaType][direction].codec.implementation = item.codecImplementationName || null;
  881.  
  882. item.googCodecName = item.googCodecName === 'unknown' ? null : item.googCodecName;
  883. output[item.mediaType][direction].codec.name = item.googCodecName || output[item.mediaType][direction].codec.name;
  884. }
  885. };
  886.  
  887. // Format audio stats
  888. var audioStatsFn = function (item, prop) {
  889. var prevStats = isAutoBwStats ? self._peerBandwidth[peerId][prop] : self._peerStats[peerId][prop];
  890.  
  891. // Safari 11 (Inbound stats)
  892. if (prop.indexOf('RTCInboundRTPAudioStream') === 0) {
  893. output.audio.receiving.fractionLost = item.fractionLost;
  894. output.audio.receiving.jitter = item.jitter;
  895.  
  896. output.audio.receiving.totalBytes = item.bytesReceived;
  897. output.audio.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  898.  
  899. output.audio.receiving.totalPackets = item.packetsReceived;
  900. output.audio.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  901.  
  902. output.audio.receiving.totalPacketsDiscarded = item.packetsDiscarded;
  903. output.audio.receiving.packetsDiscarded = self._parseConnectionStats(prevStats, item, 'packetsDiscarded');
  904.  
  905. output.audio.receiving.totalPacketsLost = item.packetsLost;
  906. output.audio.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  907.  
  908. output.audio.receiving.totalNacks = item.nackCount;
  909. output.audio.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  910.  
  911. if (typeof pc.getReceivers !== 'function') {
  912. return;
  913. }
  914.  
  915. // Safari 11 (Inbound track stats)
  916. } else if (prop.indexOf('RTCMediaStreamTrack_remote_audio_') === 0) {
  917. output.audio.receiving.audioOutputLevel = item.audioLevel;
  918.  
  919. // Safari 11 (Outbound stats)
  920. } else if (prop.indexOf('RTCOutboundRTPAudioStream') === 0) {
  921. output.audio.sending.targetBitrate = item.targetBitrate || 0;
  922.  
  923. output.audio.sending.totalBytes = item.bytesSent;
  924. output.audio.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  925.  
  926. output.audio.sending.totalPackets = item.packetsSent;
  927. output.audio.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  928.  
  929. output.audio.sending.totalNacks = item.nackCount;
  930. output.audio.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  931.  
  932. // Edge (WebRTC not ORTC shim) (Inbound stats) - Stats may not be accurate as it returns 0.
  933. } else if (AdapterJS.webrtcDetectedBrowser === 'edge' && item.type === 'inboundrtp' && item.mediaType === 'audio' && item.isRemote) {
  934. output.audio.receiving.fractionLost = item.fractionLost;
  935. output.audio.receiving.jitter = item.jitter;
  936.  
  937. output.audio.receiving.totalBytes = item.bytesReceived;
  938. output.audio.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  939.  
  940. output.audio.receiving.totalPackets = item.packetsReceived;
  941. output.audio.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  942.  
  943. output.audio.receiving.totalPacketsLost = item.packetsLost;
  944. output.audio.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  945.  
  946. output.audio.receiving.totalNacks = item.nackCount;
  947. output.audio.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  948.  
  949. // Edge (WebRTC not ORTC shim) (Outbound stats) - Stats may not be accurate as it returns 0.
  950. } else if (AdapterJS.webrtcDetectedBrowser === 'edge' && item.type === 'outboundrtp' && item.mediaType === 'audio' && !item.isRemote) {
  951. output.audio.sending.targetBitrate = item.targetBitrate;
  952. output.audio.sending.rtt = item.roundTripTime;
  953.  
  954. output.audio.sending.totalBytes = item.bytesSent;
  955. output.audio.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  956.  
  957. output.audio.sending.totalPackets = item.packetsSent;
  958. output.audio.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  959.  
  960. output.audio.sending.totalNacks = item.nackCount;
  961. output.audio.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  962.  
  963. var trackItem = output.raw[item.mediaTrackId || ''] || {};
  964. output.audio.sending.audioInputLevel = trackItem.audioLevel;
  965. output.audio.sending.echoReturnLoss = trackItem.echoReturnLoss;
  966. output.audio.sending.echoReturnLossEnhancement = trackItem.echoReturnLossEnhancement;
  967.  
  968. // Chrome / Plugin
  969. } else if (prop.indexOf('ssrc_') === 0 && item.mediaType === 'audio') {
  970. // Chrome / Plugin (Inbound stats)
  971. if (prop.indexOf('_recv') > 0) {
  972. output.audio.receiving.jitter = parseInt(item.googJitterReceived || '0', 10);
  973. output.audio.receiving.jitterBufferMs = parseInt(item.googJitterBufferMs || '0', 10);
  974. output.audio.receiving.currentDelayMs = parseInt(item.googCurrentDelayMs || '0', 10);
  975. //output.audio.receiving.audioOutputLevel = parseInt(item.audioOutputLevel || '0', 10);
  976.  
  977. var bytesReceived = parseInt(item.bytesReceived || '0', 10);
  978. output.audio.receiving.totalBytes = bytesReceived;
  979. output.audio.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  980.  
  981. var packetsReceived = parseInt(item.packetsReceived || '0', 10);
  982. output.audio.receiving.totalPackets = packetsReceived;
  983. output.audio.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  984.  
  985. var packetsLost = parseInt(item.packetsLost || '0', 10);
  986. output.audio.receiving.totalPacketsLost = packetsLost;
  987. output.audio.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  988.  
  989. // Chrome / Plugin (Outbound stats)
  990. } else {
  991. output.audio.sending.rtt = parseInt(item.googRtt || '0', 10);
  992. output.audio.sending.audioInputLevel = parseInt(item.audioInputLevel || '0', 10);
  993. output.audio.sending.echoReturnLoss = parseInt(item.googEchoCancellationReturnLoss || '0', 10);
  994. output.audio.sending.echoReturnLossEnhancement = parseInt(item.googEchoCancellationReturnLossEnhancement || '0', 10);
  995.  
  996. var bytesSent = parseInt(item.bytesSent || '0', 10);
  997. output.audio.sending.totalBytes = bytesSent;
  998. output.audio.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  999.  
  1000. var packetsSent = parseInt(item.packetsSent || '0', 10);
  1001. output.audio.sending.totalPackets = packetsSent;
  1002. output.audio.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1003. }
  1004.  
  1005. // Firefox (Inbound stats)
  1006. } else if (prop.indexOf('inbound_rtp_audio') === 0) {
  1007. output.audio.receiving.jitter = item.jitter || 0;
  1008.  
  1009. output.audio.receiving.totalBytes = item.bytesReceived;
  1010. output.audio.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  1011.  
  1012. output.audio.receiving.totalPackets = item.packetsReceived;
  1013. output.audio.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  1014.  
  1015. output.audio.receiving.totalPacketsLost = item.packetsLost;
  1016. output.audio.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  1017.  
  1018. output.audio.receiving.totalNacks = item.nackCount;
  1019. output.audio.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1020.  
  1021. // Firefox (Outbound stats)
  1022. } else if (prop.indexOf('outbound_rtp_audio') === 0) {
  1023. output.audio.sending.totalBytes = item.bytesSent;
  1024. output.audio.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  1025.  
  1026. output.audio.sending.totalPackets = item.packetsSent;
  1027. output.audio.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1028.  
  1029. output.audio.sending.totalNacks = item.nackCount;
  1030. output.audio.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1031.  
  1032. var rtcpItem = output.raw[prop.replace(/_rtp_/g, '_rtcp_')] || {};
  1033. output.audio.sending.rtt = rtcpItem.roundTripTime || 0;
  1034. }
  1035. };
  1036.  
  1037. // Format video stats
  1038. var videoStatsFn = function (item, prop) {
  1039. var prevStats = isAutoBwStats ? self._peerBandwidth[peerId][prop] : self._peerStats[peerId][prop];
  1040.  
  1041. // Safari 11 (Inbound stats)
  1042. if (prop.indexOf('RTCInboundRTPVideoStream') === 0) {
  1043. output.video.receiving.fractionLost = item.fractionLost;
  1044. output.video.receiving.jitter = item.jitter;
  1045. output.video.receiving.framesDecoded = item.framesDecoded;
  1046. output.video.receiving.qpSum = item.qpSum;
  1047.  
  1048. output.video.receiving.totalBytes = item.bytesReceived;
  1049. output.video.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  1050.  
  1051. output.video.receiving.totalPackets = item.packetsReceived;
  1052. output.video.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  1053.  
  1054. output.video.receiving.totalPacketsDiscarded = item.packetsDiscarded;
  1055. output.video.receiving.packetsDiscarded = self._parseConnectionStats(prevStats, item, 'packetsDiscarded');
  1056.  
  1057. output.video.receiving.totalPacketsLost = item.packetsLost;
  1058. output.video.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  1059.  
  1060. output.video.receiving.totalNacks = item.nackCount;
  1061. output.video.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1062.  
  1063. output.video.receiving.totalFirs = item.firCount;
  1064. output.video.receiving.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1065.  
  1066. output.video.receiving.totalSlis = item.sliCount;
  1067. output.video.receiving.slis = self._parseConnectionStats(prevStats, item, 'sliCount');
  1068.  
  1069. // Safari 11 (Inbound track stats)
  1070. } else if (prop.indexOf('RTCMediaStreamTrack_remote_video_') === 0) {
  1071. output.video.receiving.frameHeight = item.frameHeight;
  1072. output.video.receiving.frameWidth = item.frameWidth;
  1073. output.video.receiving.framesCorrupted = item.framesCorrupted;
  1074. output.video.receiving.framesPerSecond = item.framesPerSecond;
  1075. output.video.receiving.framesDropped = item.framesDropped;
  1076.  
  1077. output.video.receiving.totalFrames = item.framesReceived;
  1078. output.video.receiving.frames = self._parseConnectionStats(prevStats, item, 'framesReceived');
  1079.  
  1080. // Safari 11 (Outbound stats)
  1081. } else if (prop.indexOf('RTCOutboundRTPVideoStream') === 0) {
  1082. output.video.sending.qpSum = item.qpSum;
  1083. output.video.sending.targetBitrate = item.targetBitrate || 0;
  1084. output.video.sending.framesEncoded = item.framesEncoded || 0;
  1085.  
  1086. output.video.sending.totalBytes = item.bytesSent;
  1087. output.video.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  1088.  
  1089. output.video.sending.totalPackets = item.packetsSent;
  1090. output.video.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1091.  
  1092. output.video.sending.totalNacks = item.nackCount;
  1093. output.video.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1094.  
  1095. output.video.receiving.totalFirs = item.firCount;
  1096. output.video.receiving.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1097.  
  1098. output.video.sending.totalSlis = item.sliCount;
  1099. output.video.sending.slis = self._parseConnectionStats(prevStats, item, 'sliCount');
  1100.  
  1101. // Edge (WebRTC not ORTC shim) (Inbound stats) - Stats may not be accurate as it returns 0.
  1102. } else if (AdapterJS.webrtcDetectedBrowser === 'edge' && item.type === 'inboundrtp' && item.mediaType === 'video' && item.isRemote) {
  1103. output.video.receiving.fractionLost = item.fractionLost;
  1104. output.video.receiving.jitter = item.jitter;
  1105.  
  1106. output.video.receiving.totalBytes = item.bytesReceived;
  1107. output.video.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  1108.  
  1109. output.video.receiving.totalPackets = item.packetsReceived;
  1110. output.video.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  1111.  
  1112. output.video.receiving.totalPacketsLost = item.packetsLost;
  1113. output.video.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  1114.  
  1115. output.video.receiving.totalNacks = item.nackCount;
  1116. output.video.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1117.  
  1118. output.video.receiving.totalPlis = item.pliCount;
  1119. output.video.receiving.plis = self._parseConnectionStats(prevStats, item, 'pliCount');
  1120.  
  1121. output.video.receiving.totalFirs = item.firCount;
  1122. output.video.receiving.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1123.  
  1124. output.video.receiving.totalSlis = item.sliCount;
  1125. output.video.receiving.slis = self._parseConnectionStats(prevStats, item, 'sliCount');
  1126.  
  1127. var trackItem = output.raw[item.mediaTrackId || ''] || {};
  1128. output.video.receiving.framesCorrupted = trackItem.framesCorrupted;
  1129. output.video.receiving.framesDropped = trackItem.framesDropped;
  1130. output.video.receiving.framesDecoded = trackItem.framesDecoded;
  1131.  
  1132. output.video.receiving.totalFrames = trackItem.framesReceived;
  1133. output.video.receiving.frames = self._parseConnectionStats(prevStats, trackItem, 'framesReceived');
  1134.  
  1135. // Edge (WebRTC not ORTC shim) (Outbound stats) - Stats may not be accurate as it returns 0.
  1136. } else if (AdapterJS.webrtcDetectedBrowser === 'edge' && item.type === 'outboundrtp' && item.mediaType === 'video' && !item.isRemote) {
  1137. output.video.sending.targetBitrate = item.targetBitrate || 0;
  1138. output.video.sending.roundTripTime = item.roundTripTime || 0;
  1139.  
  1140. output.video.sending.totalBytes = item.bytesSent;
  1141. output.video.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  1142.  
  1143. output.video.sending.totalPackets = item.packetsSent;
  1144. output.video.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1145.  
  1146. output.video.sending.totalNacks = item.nackCount;
  1147. output.video.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1148.  
  1149. output.video.sending.totalFirs = item.firCount;
  1150. output.video.sending.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1151.  
  1152. output.video.sending.totalPlis = item.pliCount;
  1153. output.video.sending.plis = self._parseConnectionStats(prevStats, item, 'pliCount');
  1154.  
  1155. output.video.sending.totalSlis = item.sliCount;
  1156. output.video.sending.slis = self._parseConnectionStats(prevStats, item, 'sliCount');
  1157.  
  1158. var trackItem = output.raw[item.mediaTrackId || ''] || {};
  1159. output.video.sending.frameHeight = trackItem.frameHeight;
  1160. output.video.sending.frameWidth = trackItem.frameWidth;
  1161. output.video.sending.framesPerSecond = trackItem.framesPerSecond;
  1162.  
  1163. output.video.sending.totalFrames = trackItem.framesSent;
  1164. output.video.sending.frames = self._parseConnectionStats(prevStats, trackItem, 'framesSent');
  1165.  
  1166. // Chrome / Plugin
  1167. } else if (prop.indexOf('ssrc_') === 0 && item.mediaType === 'video') {
  1168. // Chrome / Plugin (Inbound stats)
  1169. if (prop.indexOf('_recv') > 0) {
  1170. output.video.receiving.jitter = parseInt(item.googJitterReceived || '0', 10);
  1171. output.video.receiving.jitterBufferMs = parseInt(item.googJitterBufferMs || '0', 10);
  1172. output.video.receiving.currentDelayMs = parseInt(item.googCurrentDelayMs || '0', 10);
  1173. output.video.receiving.renderDelayMs = parseInt(item.googRenderDelayMs || '0', 10);
  1174. output.video.receiving.frameWidth = parseInt(item.googFrameWidthReceived || '0', 10);
  1175. output.video.receiving.frameHeight = parseInt(item.googFrameHeightReceived || '0', 10);
  1176. output.video.receiving.framesDecoded = parseInt(item.framesDecoded || '0', 10);
  1177. output.video.receiving.frameRateOutput = parseInt(item.googFrameRateOutput || '0', 10);
  1178. output.video.receiving.frameRateDecoded = parseInt(item.googFrameRateDecoded || '0', 10);
  1179. output.video.receiving.frameRateReceived = parseInt(item.googFrameRateReceived || '0', 10);
  1180. output.video.receiving.qpSum = parseInt(item.qpSum || '0', 10);
  1181.  
  1182. var bytesReceived = parseInt(item.bytesReceived || '0', 10);
  1183. output.video.receiving.totalBytes = bytesReceived;
  1184. output.video.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  1185.  
  1186. var packetsReceived = parseInt(item.packetsReceived || '0', 10);
  1187. output.video.receiving.totalPackets = packetsReceived;
  1188. output.video.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  1189.  
  1190. var packetsLost = parseInt(item.packetsLost || '0', 10);
  1191. output.video.receiving.totalPacketsLost = packetsLost;
  1192. output.video.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  1193.  
  1194. var nacksSent = parseInt(item.googNacksSent || '0', 10);
  1195. output.video.receiving.totalNacks = nacksSent;
  1196. output.video.receiving.nacks = self._parseConnectionStats(prevStats, item, 'googNacksSent');
  1197.  
  1198. var plisSent = parseInt(item.googPlisSent || '0', 10);
  1199. output.video.receiving.totalPlis = plisSent;
  1200. output.video.receiving.plis = self._parseConnectionStats(prevStats, item, 'googPlisSent');
  1201.  
  1202. var firsSent = parseInt(item.googFirsSent || '0', 10);
  1203. output.video.receiving.totalFirs = firsSent;
  1204. output.video.receiving.firs = self._parseConnectionStats(prevStats, item, 'googFirsSent');
  1205.  
  1206. // Chrome / Plugin (Outbound stats)
  1207. } else {
  1208. output.video.sending.rtt = parseInt(item.googRtt || '0', 10);
  1209. output.video.sending.frameWidth = parseInt(item.googFrameWidthSent || '0', 10);
  1210. output.video.sending.frameHeight = parseInt(item.googFrameHeightSent || '0', 10);
  1211. output.video.sending.framesEncoded = parseInt(item.framesEncoded || '0', 10);
  1212. output.video.sending.frameRateInput = parseInt(item.googFrameRateInput || '0', 10);
  1213. output.video.sending.frameRateEncoded = parseInt(item.googFrameRateEncoded || '0', 10);
  1214. output.video.sending.frameRateSent = parseInt(item.googFrameRateSent || '0', 10);
  1215. output.video.sending.cpuLimitedResolution = item.googCpuLimitedResolution === 'true';
  1216. output.video.sending.bandwidthLimitedResolution = item.googBandwidthLimitedResolution === 'true';
  1217.  
  1218. var bytesSent = parseInt(item.bytesSent || '0', 10);
  1219. output.video.sending.totalBytes = bytesSent;
  1220. output.video.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  1221.  
  1222. var packetsSent = parseInt(item.packetsSent || '0', 10);
  1223. output.video.sending.totalPackets = packetsSent;
  1224. output.video.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1225.  
  1226. var nacksReceived = parseInt(item.googNacksReceived || '0', 10);
  1227. output.video.sending.totalNacks = nacksReceived;
  1228. output.video.sending.nacks = self._parseConnectionStats(prevStats, item, 'googNacksReceived');
  1229.  
  1230. var plisReceived = parseInt(item.googPlisReceived || '0', 10);
  1231. output.video.sending.totalPlis = plisReceived;
  1232. output.video.sending.plis = self._parseConnectionStats(prevStats, item, 'googPlisReceived');
  1233.  
  1234. var firsReceived = parseInt(item.googFirsReceived || '0', 10);
  1235. output.video.sending.totalFirs = firsReceived;
  1236. output.video.sending.firs = self._parseConnectionStats(prevStats, item, 'googFirsReceived');
  1237. }
  1238.  
  1239. // Firefox (Inbound stats)
  1240. } else if (prop.indexOf('inbound_rtp_video') === 0) {
  1241. output.video.receiving.jitter = item.jitter || 0;
  1242. output.video.receiving.framesDecoded = item.framesDecoded || 0;
  1243. output.video.receiving.frameRateMean = item.framerateMean || 0;
  1244. output.video.receiving.frameRateStdDev = item.framerateStdDev || 0;
  1245.  
  1246. output.video.receiving.totalBytes = item.bytesReceived;
  1247. output.video.receiving.bytes = self._parseConnectionStats(prevStats, item, 'bytesReceived');
  1248.  
  1249. output.video.receiving.totalPackets = item.packetsReceived;
  1250. output.video.receiving.packets = self._parseConnectionStats(prevStats, item, 'packetsReceived');
  1251.  
  1252. output.video.receiving.totalPacketsLost = item.packetsLost;
  1253. output.video.receiving.packetsLost = self._parseConnectionStats(prevStats, item, 'packetsLost');
  1254.  
  1255. output.video.receiving.totalNacks = item.nackCount;
  1256. output.video.receiving.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1257.  
  1258. output.video.receiving.totalPlis = item.pliCount;
  1259. output.video.receiving.plis = self._parseConnectionStats(prevStats, item, 'pliCount');
  1260.  
  1261. output.video.receiving.totalFirs = item.firCount;
  1262. output.video.receiving.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1263.  
  1264. // Firefox (Outbound stats)
  1265. } else if (prop.indexOf('outbound_rtp_video') === 0) {
  1266. output.video.sending.framesEncoded = item.framesEncoded || 0;
  1267. output.video.sending.frameRateMean = item.framerateMean || 0;
  1268. output.video.sending.frameRateStdDev = item.framerateStdDev || 0;
  1269. output.video.sending.framesDropped = item.droppedFrames || 0;
  1270.  
  1271. output.video.sending.totalBytes = item.bytesSent;
  1272. output.video.sending.bytes = self._parseConnectionStats(prevStats, item, 'bytesSent');
  1273.  
  1274. output.video.sending.totalPackets = item.packetsSent;
  1275. output.video.sending.packets = self._parseConnectionStats(prevStats, item, 'packetsSent');
  1276.  
  1277. output.video.sending.totalNacks = item.nackCount;
  1278. output.video.sending.nacks = self._parseConnectionStats(prevStats, item, 'nackCount');
  1279.  
  1280. output.video.sending.totalPlis = item.pliCount;
  1281. output.video.sending.plis = self._parseConnectionStats(prevStats, item, 'pliCount');
  1282.  
  1283. output.video.sending.totalFirs = item.firCount;
  1284. output.video.sending.firs = self._parseConnectionStats(prevStats, item, 'firCount');
  1285.  
  1286. var rtcpItem = output.raw[prop.replace(/_rtp_/g, '_rtcp_')] || {};
  1287. output.video.sending.rtt = rtcpItem.roundTripTime || 0;
  1288. }
  1289. };
  1290.  
  1291. // Format video e2e delay stats
  1292. var videoE2EStatsFn = function (item, prop) {
  1293. // Chrome / Plugin (Inbound e2e stats)
  1294. if (prop.indexOf('ssrc_') === 0 && item.mediaType === 'video') {
  1295. var captureStartNtpTimeMs = parseInt(item.googCaptureStartNtpTimeMs || '0', 10);
  1296. var remoteStream = pc.getRemoteStreams()[0];
  1297.  
  1298. if (!(captureStartNtpTimeMs > 0 && prop.indexOf('_recv') > 0 && remoteStream &&
  1299. document && typeof document.getElementsByTagName === 'function')) {
  1300. return;
  1301. }
  1302.  
  1303. try {
  1304. var elements = document.getElementsByTagName(AdapterJS.webrtcDetectedType === 'plugin' ? 'object' : 'video');
  1305.  
  1306. if (AdapterJS.webrtcDetectedType !== 'plugin' && elements.length === 0) {
  1307. elements = document.getElementsByTagName('audio');
  1308. }
  1309.  
  1310. for (var e = 0; e < elements.length; e++) {
  1311. var videoStreamId = null;
  1312.  
  1313. // For Plugin case where they use the <object> element
  1314. if (AdapterJS.webrtcDetectedType === 'plugin') {
  1315. // Precautionary check to return if there is no children like <param>, which means something is wrong..
  1316. if (!(elements[e].children && typeof elements[e].children === 'object' &&
  1317. typeof elements[e].children.length === 'number' && elements[e].children.length > 0)) {
  1318. break;
  1319. }
  1320.  
  1321. // Retrieve the "streamId" parameter
  1322. for (var ec = 0; ec < elements[e].children.length; ec++) {
  1323. if (elements[e].children[ec].name === 'streamId') {
  1324. videoStreamId = elements[e].children[ec].value || null;
  1325. break;
  1326. }
  1327. }
  1328.  
  1329. // For Chrome case where the srcObject can be obtained and determine the streamId
  1330. } else {
  1331. videoStreamId = (elements[e].srcObject && (elements[e].srcObject.id || elements[e].srcObject.label)) || null;
  1332. }
  1333.  
  1334. if (videoStreamId && videoStreamId === (remoteStream.id || remoteStream.label)) {
  1335. output.video.receiving.e2eDelay = ((new Date()).getTime() + 2208988800000) - captureStartNtpTimeMs - elements[e].currentTime * 1000;
  1336. break;
  1337. }
  1338. }
  1339.  
  1340. } catch (error) {
  1341. if (!beSilentOnLogs) {
  1342. log.warn([peerId, 'RTCStatsReport', null, 'Failed retrieving e2e delay ->'], error);
  1343. }
  1344. }
  1345. }
  1346. };
  1347.  
  1348. var successCbFn = function (stats) {
  1349. if (typeof stats.forEach === 'function') {
  1350. stats.forEach(function (item, prop) {
  1351. output.raw[prop] = item;
  1352. });
  1353. } else {
  1354. output.raw = stats;
  1355. }
  1356.  
  1357. var edgeTracksKind = {
  1358. remote: {},
  1359. local: {}
  1360. };
  1361.  
  1362. if (AdapterJS.webrtcDetectedBrowser === 'edge') {
  1363. if (pc.remoteStream) {
  1364. pc.remoteStream.getTracks().forEach(function (track) {
  1365. edgeTracksKind.remote[track.id] = track.kind;
  1366. });
  1367. }
  1368.  
  1369. if (pc.localStream) {
  1370. pc.localStream.getTracks().forEach(function (track) {
  1371. edgeTracksKind.local[track.id] = track.kind;
  1372. });
  1373. }
  1374. }
  1375.  
  1376. Object.keys(output.raw).forEach(function (prop) {
  1377. // Polyfill for Plugin missing "mediaType" stats item
  1378. if (prop.indexOf('ssrc_') === 0 && !output.raw[prop].mediaType) {
  1379. output.raw[prop].mediaType = output.raw[prop].audioInputLevel || output.raw[prop].audioOutputLevel ? 'audio' : 'video';
  1380.  
  1381. // Polyfill for Edge 15.x missing "mediaType" stats item
  1382. } else if (AdapterJS.webrtcDetectedBrowser === 'edge' && !output.raw[prop].mediaType &&
  1383. ['inboundrtp', 'outboundrtp'].indexOf(output.raw[prop].type) > -1) {
  1384. var trackItem = output.raw[ output.raw[prop].mediaTrackId ] || {};
  1385. output.raw[prop].mediaType = edgeTracksKind[ output.raw[prop].isRemote ? 'remote' : 'local' ][ trackItem.trackIdentifier ] || '';
  1386. }
  1387.  
  1388. certificateFn(output.raw[prop], prop);
  1389. candidatePairFn(output.raw[prop], prop);
  1390. codecsFn(output.raw[prop], prop);
  1391. audioStatsFn(output.raw[prop], prop);
  1392. videoStatsFn(output.raw[prop], prop);
  1393. videoE2EStatsFn(output.raw[prop], prop);
  1394.  
  1395. // Parse for bandwidth statistics if not yet defined to not mix with the getConnectionStatus()
  1396. if (isAutoBwStats && !self._peerBandwidth[peerId][prop]) {
  1397. self._peerBandwidth[peerId][prop] = output.raw[prop];
  1398. } else if (!isAutoBwStats && !self._peerStats[peerId][prop]) {
  1399. self._peerStats[peerId][prop] = output.raw[prop];
  1400. }
  1401. });
  1402.  
  1403. // Prevent "0" in Edge 15.x and Safari 11 when SSRC stats is not available
  1404. output.audio.sending.bytes = output.audio.sending.bytes || 0;
  1405. output.audio.sending.packets = output.audio.sending.packets || 0;
  1406. output.audio.sending.totalBytes = output.audio.sending.totalBytes || 0;
  1407. output.audio.sending.totalPackets = output.audio.sending.totalPackets || 0;
  1408.  
  1409. output.video.sending.bytes = output.video.sending.bytes || 0;
  1410. output.video.sending.packets = output.video.sending.packets || 0;
  1411. output.video.sending.totalBytes = output.video.sending.totalBytes || 0;
  1412. output.video.sending.totalPackets = output.video.sending.totalPackets || 0;
  1413.  
  1414. output.audio.receiving.bytes = output.audio.receiving.bytes || 0;
  1415. output.audio.receiving.packets = output.audio.receiving.packets || 0;
  1416. output.audio.receiving.totalBytes = output.audio.receiving.totalBytes || 0;
  1417. output.audio.receiving.totalPackets = output.audio.receiving.totalPackets || 0;
  1418.  
  1419. output.video.receiving.bytes = output.video.receiving.bytes || 0;
  1420. output.video.receiving.packets = output.video.receiving.packets || 0;
  1421. output.video.receiving.totalBytes = output.video.receiving.totalBytes || 0;
  1422. output.video.receiving.totalPackets = output.video.receiving.totalPackets || 0;
  1423.  
  1424. callback(null, output);
  1425. };
  1426.  
  1427. var errorCbFn = function (error) {
  1428. if (!beSilentOnLogs) {
  1429. log.error([peerId, 'RTCStatsReport', null, 'Failed retrieving stats ->'], error);
  1430. }
  1431. callback(error, null);
  1432. };
  1433.  
  1434. if (typeof pc.getStats !== 'function') {
  1435. return errorCbFn(new Error('getStats() API is not available.'));
  1436. }
  1437.  
  1438. if (AdapterJS.webrtcDetectedType === 'plugin') {
  1439. pc.getStats(null, successCbFn, errorCbFn);
  1440. } else {
  1441. pc.getStats(null).then(successCbFn).catch(errorCbFn);
  1442. }
  1443. };
  1444.  
  1445. /**
  1446. * Function that starts the Peer connection session.
  1447. * Remember to remove previous method of reconnection (re-creating the Peer connection - destroy and create connection).
  1448. * @method _addPeer
  1449. * @private
  1450. * @for Skylink
  1451. * @since 0.5.4
  1452. */
  1453. Skylink.prototype._addPeer = function(targetMid, cert, peerBrowser, receiveOnly, isSS) {
  1454. var self = this;
  1455. if (self._peerConnections[targetMid]) {
  1456. log.error([targetMid, null, null, 'Connection to peer has already been made']);
  1457. return;
  1458. }
  1459.  
  1460. self._peerConnStatus[targetMid] = {
  1461. connected: false,
  1462. init: false
  1463. };
  1464.  
  1465. log.log([targetMid, null, null, 'Starting the connection to peer. Options provided:'], {
  1466. peerBrowser: peerBrowser,
  1467. receiveOnly: receiveOnly,
  1468. enableDataChannel: self._initOptions.enableDataChannel
  1469. });
  1470.  
  1471. log.info('Adding peer', isSS);
  1472.  
  1473. self._peerConnections[targetMid] = self._createPeerConnection(targetMid, !!isSS, cert);
  1474.  
  1475. if (!self._peerConnections[targetMid]) {
  1476. log.error([targetMid, null, null, 'Failed creating the connection to peer.']);
  1477. return;
  1478. }
  1479.  
  1480. self._peerConnStatus[targetMid].init = true;
  1481. self._peerConnections[targetMid].hasScreen = !!isSS;
  1482. };
  1483.  
  1484. /**
  1485. * Function that re-negotiates a Peer connection.
  1486. * Remember to remove previous method of reconnection (re-creating the Peer connection - destroy and create connection).
  1487. * @method _restartPeerConnection
  1488. * @private
  1489. * @for Skylink
  1490. * @since 0.5.8
  1491. */
  1492. Skylink.prototype._restartPeerConnection = function (peerId, doIceRestart, bwOptions, callback) {
  1493. var self = this;
  1494.  
  1495. if (!self._peerConnections[peerId]) {
  1496. log.error([peerId, null, null, 'Peer does not have an existing ' +
  1497. 'connection. Unable to restart']);
  1498. return;
  1499. }
  1500.  
  1501. var pc = self._peerConnections[peerId];
  1502. var agent = (self.getPeerInfo(peerId) || {}).agent || {};
  1503.  
  1504. // prevent restarts for other SDK clients
  1505. if (self._isLowerThanVersion(agent.SMProtocolVersion || '', '0.1.2')) {
  1506. var notSupportedError = new Error('Failed restarting with other agents connecting from other SDKs as ' +
  1507. 're-negotiation is not supported by other SDKs');
  1508.  
  1509. log.warn([peerId, 'RTCPeerConnection', null, 'Ignoring restart request as agent\'s SDK does not support it'],
  1510. notSupportedError);
  1511.  
  1512. if (typeof callback === 'function') {
  1513. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  1514. callback(notSupportedError);
  1515. }
  1516. return;
  1517. }
  1518.  
  1519. // This is when the state is stable and re-handshaking is possible
  1520. // This could be due to previous connection handshaking that is already done
  1521. if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE && self._peerConnections[peerId]) {
  1522. log.log([peerId, null, null, 'Sending restart message to signaling server ->'], {
  1523. iceRestart: doIceRestart,
  1524. options: bwOptions
  1525. });
  1526.  
  1527. self._peerCustomConfigs[peerId] = self._peerCustomConfigs[peerId] || {};
  1528. self._peerCustomConfigs[peerId].bandwidth = self._peerCustomConfigs[peerId].bandwidth || {};
  1529. self._peerCustomConfigs[peerId].googleXBandwidth = self._peerCustomConfigs[peerId].googleXBandwidth || {};
  1530.  
  1531. if (bwOptions.bandwidth && typeof bwOptions.bandwidth === 'object') {
  1532. if (typeof bwOptions.bandwidth.audio === 'number') {
  1533. self._peerCustomConfigs[peerId].bandwidth.audio = bwOptions.bandwidth.audio;
  1534. }
  1535. if (typeof bwOptions.bandwidth.video === 'number') {
  1536. self._peerCustomConfigs[peerId].bandwidth.video = bwOptions.bandwidth.video;
  1537. }
  1538. if (typeof bwOptions.bandwidth.data === 'number') {
  1539. self._peerCustomConfigs[peerId].bandwidth.data = bwOptions.bandwidth.data;
  1540. }
  1541. }
  1542.  
  1543. if (bwOptions.googleXBandwidth && typeof bwOptions.googleXBandwidth === 'object') {
  1544. if (typeof bwOptions.googleXBandwidth.min === 'number') {
  1545. self._peerCustomConfigs[peerId].googleXBandwidth.min = bwOptions.googleXBandwidth.min;
  1546. }
  1547. if (typeof bwOptions.googleXBandwidth.max === 'number') {
  1548. self._peerCustomConfigs[peerId].googleXBandwidth.max = bwOptions.googleXBandwidth.max;
  1549. }
  1550. }
  1551.  
  1552. var restartMsg = {
  1553. type: self._SIG_MESSAGE_TYPE.RESTART,
  1554. mid: self._user.sid,
  1555. rid: self._room.id,
  1556. agent: AdapterJS.webrtcDetectedBrowser,
  1557. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1558. os: window.navigator.platform,
  1559. userInfo: self._getUserInfo(peerId),
  1560. target: peerId,
  1561. weight: self._peerPriorityWeight,
  1562. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1563. enableIceTrickle: self._initOptions.enableIceTrickle,
  1564. enableDataChannel: self._initOptions.enableDataChannel,
  1565. enableIceRestart: self._enableIceRestart,
  1566. doIceRestart: doIceRestart === true && self._enableIceRestart && self._peerInformations[peerId] &&
  1567. self._peerInformations[peerId].config.enableIceRestart,
  1568. isRestartResend: false,
  1569. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1570. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1571. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  1572. };
  1573.  
  1574. if (self._publishOnly) {
  1575. restartMsg.publishOnly = {
  1576. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1577. };
  1578. }
  1579.  
  1580. if (self._parentId) {
  1581. restartMsg.parentId = self._parentId;
  1582. }
  1583.  
  1584. self._peerEndOfCandidatesCounter[peerId] = self._peerEndOfCandidatesCounter[peerId] || {};
  1585. self._peerEndOfCandidatesCounter[peerId].len = 0;
  1586. self._doOffer(peerId, doIceRestart, restartMsg);
  1587. //self._handleNegotiationStats('restart', peerId, restartMsg, false);
  1588. //self._trigger('peerRestart', peerId, self.getPeerInfo(peerId), true, doIceRestart === true);
  1589.  
  1590. if (typeof callback === 'function') {
  1591. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart callback']);
  1592. callback(null);
  1593. }
  1594.  
  1595. } else {
  1596. // Let's check if the signalingState is stable first.
  1597. // In another galaxy or universe, where the local description gets dropped..
  1598. // In the offerHandler or answerHandler, do the appropriate flags to ignore or drop "extra" descriptions
  1599. if (pc.signalingState === self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
  1600. // Checks if the local description is defined first
  1601. var hasLocalDescription = pc.localDescription && pc.localDescription.sdp;
  1602. // By then it should have at least the local description..
  1603. if (hasLocalDescription) {
  1604. self._sendChannelMessage({
  1605. type: pc.localDescription.type,
  1606. sdp: pc.localDescription.sdp,
  1607. mid: self._user.sid,
  1608. target: peerId,
  1609. rid: self._room.id,
  1610. restart: true
  1611. });
  1612. } else {
  1613. var noLocalDescriptionError = 'Failed re-sending localDescription as there is ' +
  1614. 'no localDescription set to connection. There could be a handshaking step error';
  1615. log.error([peerId, 'RTCPeerConnection', null, noLocalDescriptionError], {
  1616. localDescription: pc.localDescription,
  1617. remoteDescription: pc.remoteDescription
  1618. });
  1619. if (typeof callback === 'function') {
  1620. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  1621. callback(new Error(noLocalDescriptionError));
  1622. }
  1623. }
  1624. // It could have connection state closed
  1625. } else {
  1626. var unableToRestartError = 'Failed restarting as peer connection state is ' + pc.signalingState;
  1627. log.warn([peerId, 'RTCPeerConnection', null, unableToRestartError]);
  1628. if (typeof callback === 'function') {
  1629. log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
  1630. callback(new Error(unableToRestartError));
  1631. }
  1632. }
  1633. }
  1634. };
  1635.  
  1636. /**
  1637. * Function that ends the Peer connection session.
  1638. * @method _removePeer
  1639. * @private
  1640. * @for Skylink
  1641. * @since 0.5.5
  1642. */
  1643. Skylink.prototype._removePeer = function(peerId) {
  1644. if (!this._peerConnections[peerId] && !this._peerInformations[peerId] && !this._hasMCU) {
  1645. log.debug([peerId, 'RTCPeerConnection', null, 'Dropping the hangup from Peer as not connected to Peer at all.']);
  1646. return;
  1647. }
  1648.  
  1649. var peerInfo = null;
  1650.  
  1651. if (!this._hasMCU) {
  1652. peerInfo = clone(this.getPeerInfo(peerId)) || {
  1653. userData: '',
  1654. settings: { audio: false, video: false, data: false },
  1655. mediaStatus: { audioMuted: true, videoMuted: true },
  1656. agent: {
  1657. name: 'unknown',
  1658. version: 0,
  1659. os: '',
  1660. pluginVersion: null
  1661. },
  1662. config: {
  1663. enableDataChannel: true,
  1664. enableIceRestart: false,
  1665. enableIceTrickle: true,
  1666. priorityWeight: 0,
  1667. publishOnly: false,
  1668. receiveOnly: true
  1669. },
  1670. parentId: null,
  1671. room: clone(this._selectedRoom)
  1672. };
  1673. }
  1674.  
  1675.  
  1676. if (peerId !== 'MCU') {
  1677. this._trigger('peerLeft', peerId, peerInfo, false);
  1678. } else {
  1679. this._hasMCU = false;
  1680. log.log([peerId, null, null, 'MCU has stopped listening and left']);
  1681. this._trigger('serverPeerLeft', peerId, this.SERVER_PEER_TYPE.MCU);
  1682. }
  1683.  
  1684. // check if health timer exists
  1685. if (this._peerConnections[peerId]) {
  1686. if (this._peerConnections[peerId].signalingState !== this.PEER_CONNECTION_STATE.CLOSED) {
  1687. this._peerConnections[peerId].close();
  1688. // Polyfill for safari 11 "closed" event not triggered for "iceConnectionState" and "signalingState".
  1689. if (AdapterJS.webrtcDetectedType === 'AppleWebKit') {
  1690. if (!this._peerConnections[peerId].signalingStateClosed) {
  1691. this._peerConnections[peerId].signalingStateClosed = true;
  1692. this._trigger('peerConnectionState', this.PEER_CONNECTION_STATE.CLOSED, peerId);
  1693. }
  1694. if (!this._peerConnections[peerId].iceConnectionStateClosed) {
  1695. this._peerConnections[peerId].iceConnectionStateClosed = true;
  1696. this._handleIceConnectionStats(this.ICE_CONNECTION_STATE.CLOSED, peerId);
  1697. this._trigger('iceConnectionState', this.ICE_CONNECTION_STATE.CLOSED, peerId);
  1698. }
  1699. }
  1700. }
  1701. if (peerId !== 'MCU') {
  1702. this._handleEndedStreams(peerId);
  1703. }
  1704. delete this._peerConnections[peerId];
  1705. }
  1706. // remove peer informations session
  1707. if (this._peerInformations[peerId]) {
  1708. delete this._peerInformations[peerId];
  1709. }
  1710. // remove peer messages stamps session
  1711. if (this._peerMessagesStamps[peerId]) {
  1712. delete this._peerMessagesStamps[peerId];
  1713. }
  1714. // remove peer streams session
  1715. if (this._streamsSession[peerId]) {
  1716. delete this._streamsSession[peerId];
  1717. }
  1718. // remove peer streams session
  1719. if (this._peerEndOfCandidatesCounter[peerId]) {
  1720. delete this._peerEndOfCandidatesCounter[peerId];
  1721. }
  1722. // remove peer queued ICE candidates
  1723. if (this._peerCandidatesQueue[peerId]) {
  1724. delete this._peerCandidatesQueue[peerId];
  1725. }
  1726. // remove peer sdp session
  1727. if (this._sdpSessions[peerId]) {
  1728. delete this._sdpSessions[peerId];
  1729. }
  1730. // remove peer stats session
  1731. if (this._peerStats[peerId]) {
  1732. delete this._peerStats[peerId];
  1733. }
  1734. // remove peer bandwidth stats
  1735. if (this._peerBandwidth[peerId]) {
  1736. delete this._peerBandwidth[peerId];
  1737. }
  1738. // remove peer ICE candidates
  1739. if (this._gatheredCandidates[peerId]) {
  1740. delete this._gatheredCandidates[peerId];
  1741. }
  1742. // remove peer ICE candidates
  1743. if (this._peerCustomConfigs[peerId]) {
  1744. delete this._peerCustomConfigs[peerId];
  1745. }
  1746. // remove peer connection config
  1747. if (this._peerConnStatus[peerId]) {
  1748. delete this._peerConnStatus[peerId];
  1749. }
  1750. // close datachannel connection
  1751. if (this._dataChannels[peerId]) {
  1752. this._closeDataChannel(peerId);
  1753. }
  1754. log.log([peerId, 'RTCPeerConnection', null, 'Successfully removed peer']);
  1755. };
  1756.  
  1757. /**
  1758. * Function that creates the Peer connection.
  1759. * @method _createPeerConnection
  1760. * @private
  1761. * @for Skylink
  1762. * @since 0.5.1
  1763. */
  1764. Skylink.prototype._createPeerConnection = function(targetMid, isScreenSharing, cert) {
  1765. var pc, self = this;
  1766. if (!self._inRoom || !(self._room && self._room.connection &&
  1767. self._room.connection.peerConfig && Array.isArray(self._room.connection.peerConfig.iceServers))) {
  1768. return;
  1769. }
  1770.  
  1771. var constraints = {
  1772. iceServers: self._room.connection.peerConfig.iceServers,
  1773. iceTransportPolicy: self._initOptions.filterCandidatesType.host && self._initOptions.filterCandidatesType.srflx &&
  1774. !self._initOptions.filterCandidatesType.relay ? 'relay' : 'all',
  1775. bundlePolicy: self._peerConnectionConfig.bundlePolicy === self.BUNDLE_POLICY.NONE ?
  1776. self.BUNDLE_POLICY.BALANCED : self._peerConnectionConfig.bundlePolicy,
  1777. rtcpMuxPolicy: self._peerConnectionConfig.rtcpMuxPolicy,
  1778. iceCandidatePoolSize: self._peerConnectionConfig.iceCandidatePoolSize
  1779. };
  1780. var optional = {
  1781. optional: [
  1782. { DtlsSrtpKeyAgreement: true },
  1783. { googIPv6: true }
  1784. ]
  1785. };
  1786.  
  1787. if (cert) {
  1788. constraints.certificates = [cert];
  1789. }
  1790.  
  1791. if (self._peerConnStatus[targetMid]) {
  1792. self._peerConnStatus[targetMid].constraints = constraints;
  1793. self._peerConnStatus[targetMid].optional = optional;
  1794. }
  1795.  
  1796. // currently the AdapterJS 0.12.1-2 causes an issue to prevent firefox from
  1797. // using .urls feature
  1798. try {
  1799. log.debug([targetMid, 'RTCPeerConnection', null, 'Creating peer connection ->'], {
  1800. constraints: constraints,
  1801. optional: optional
  1802. });
  1803. pc = new (self._initOptions.useEdgeWebRTC && window.msRTCPeerConnection ? window.msRTCPeerConnection : RTCPeerConnection)(constraints, optional);
  1804. } catch (error) {
  1805. log.error([targetMid, null, null, 'Failed creating peer connection:'], error);
  1806. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1807. return null;
  1808. }
  1809. // attributes (added on by Temasys)
  1810. pc.setOffer = '';
  1811. pc.setAnswer = '';
  1812. pc.hasStream = false;
  1813. pc.hasScreen = !!isScreenSharing;
  1814. pc.hasMainChannel = false;
  1815. pc.firefoxStreamId = '';
  1816. pc.processingLocalSDP = false;
  1817. pc.processingRemoteSDP = false;
  1818. pc.gathered = false;
  1819. pc.gathering = false;
  1820. pc.localStream = null;
  1821. pc.localStreamId = null;
  1822. pc.remoteStream = null;
  1823. pc.remoteStreamId = null;
  1824. // Used for safari 11
  1825. pc.iceConnectionStateClosed = false;
  1826. pc.signalingStateClosed = false;
  1827.  
  1828. // candidates
  1829. self._gatheredCandidates[targetMid] = {
  1830. sending: { host: [], srflx: [], relay: [] },
  1831. receiving: { host: [], srflx: [], relay: [] }
  1832. };
  1833.  
  1834. self._streamsSession[targetMid] = self._streamsSession[targetMid] || {};
  1835. self._peerEndOfCandidatesCounter[targetMid] = self._peerEndOfCandidatesCounter[targetMid] || {};
  1836. self._sdpSessions[targetMid] = { local: {}, remote: {} };
  1837. self._peerBandwidth[targetMid] = {};
  1838. var bandwidth = null;
  1839.  
  1840. // callbacks
  1841. // standard not implemented: onnegotiationneeded,
  1842. pc.ondatachannel = function(event) {
  1843. var dc = event.channel || event;
  1844. log.debug([targetMid, 'RTCDataChannel', dc.label, 'Received datachannel ->'], dc);
  1845. if (self._initOptions.enableDataChannel && self._peerInformations[targetMid] &&
  1846. self._peerInformations[targetMid].config.enableDataChannel) {
  1847. var channelType = self.DATA_CHANNEL_TYPE.DATA;
  1848. var channelKey = dc.label;
  1849.  
  1850. // if peer does not have main channel, the first item is main
  1851. if (!pc.hasMainChannel) {
  1852. channelType = self.DATA_CHANNEL_TYPE.MESSAGING;
  1853. channelKey = 'main';
  1854. pc.hasMainChannel = true;
  1855. }
  1856.  
  1857. self._createDataChannel(targetMid, dc);
  1858.  
  1859. } else {
  1860. log.warn([targetMid, 'RTCDataChannel', dc.label, 'Not adding datachannel as enable datachannel ' +
  1861. 'is set to false']);
  1862. }
  1863. };
  1864. self.videoRenderers = self.videoRenderers || {};
  1865. pc.ontrack = function (rtcTrackEvent) {
  1866. // TargetMid goes all the way back to Skylink.prototype._enterHandler
  1867.  
  1868. if (!self._peerConnections[targetMid]) {
  1869. return;
  1870. }
  1871.  
  1872. var stream = rtcTrackEvent.streams[0];
  1873. var transceiverMid = rtcTrackEvent.transceiver.mid;
  1874.  
  1875. pc.remoteStream = stream;
  1876. pc.remoteStreamId = pc.remoteStreamId || stream.id || stream.label;
  1877.  
  1878. var peerSettings = clone(self.getPeerInfo(targetMid).settings);
  1879.  
  1880. self._streamsSession[targetMid][pc.remoteStreamId] = peerSettings;
  1881.  
  1882. if (stream.getAudioTracks().length === 0) {
  1883. self._streamsSession[targetMid][pc.remoteStreamId].audio = false;
  1884. }
  1885.  
  1886. if (stream.getVideoTracks().length === 0) {
  1887. self._streamsSession[targetMid][pc.remoteStreamId].video = false;
  1888. }
  1889.  
  1890. pc.hasStream = true;
  1891. pc.hasScreen = peerSettings.video && typeof peerSettings.video === 'object' && peerSettings.video.screenshare;
  1892.  
  1893. self._onRemoteStreamAdded(self._hasMCU ? self._transceiverIdPeerIdMap[transceiverMid] : targetMid, stream, !!pc.hasScreen);
  1894. };
  1895.  
  1896. pc.onremovestream = function(evt) {
  1897. var stream = evt.stream || evt;
  1898. };
  1899.  
  1900. pc.onicecandidate = function(event) {
  1901. self._onIceCandidate(targetMid, event.candidate || event);
  1902. };
  1903.  
  1904. var statsInterval = null;
  1905. pc.oniceconnectionstatechange = function(evt) {
  1906. var iceConnectionState = pc.iceConnectionState;
  1907.  
  1908. log.debug([targetMid, 'RTCIceConnectionState', null, 'Ice connection state changed ->'], iceConnectionState);
  1909.  
  1910. if (AdapterJS.webrtcDetectedBrowser === 'edge') {
  1911. if (iceConnectionState === 'connecting') {
  1912. iceConnectionState = self.ICE_CONNECTION_STATE.CHECKING;
  1913. } else if (iceConnectionState === 'new') {
  1914. iceConnectionState = self.ICE_CONNECTION_STATE.FAILED;
  1915. }
  1916. }
  1917.  
  1918. if (AdapterJS.webrtcDetectedType === 'AppleWebKit' && iceConnectionState === self.ICE_CONNECTION_STATE.CLOSED) {
  1919. setTimeout(function () {
  1920. if (!pc.iceConnectionStateClosed) {
  1921. self._handleIceConnectionStats(self.ICE_CONNECTION_STATE.CLOSED, targetMid);
  1922. self._trigger('iceConnectionState', self.ICE_CONNECTION_STATE.CLOSED, targetMid);
  1923. }
  1924. }, 10);
  1925. return;
  1926. }
  1927.  
  1928. self._handleIceConnectionStats(pc.iceConnectionState, targetMid);
  1929. self._trigger('iceConnectionState', iceConnectionState, targetMid);
  1930.  
  1931. if (iceConnectionState === self.ICE_CONNECTION_STATE.FAILED && self._initOptions.enableIceTrickle) {
  1932. self._trigger('iceConnectionState', self.ICE_CONNECTION_STATE.TRICKLE_FAILED, targetMid);
  1933. }
  1934.  
  1935. if (self._peerConnStatus[targetMid]) {
  1936. self._peerConnStatus[targetMid].connected = [self.ICE_CONNECTION_STATE.COMPLETED,
  1937. self.ICE_CONNECTION_STATE.CONNECTED].indexOf(iceConnectionState) > -1;
  1938. }
  1939.  
  1940. if (!statsInterval && [self.ICE_CONNECTION_STATE.CONNECTED, self.ICE_CONNECTION_STATE.COMPLETED].indexOf(iceConnectionState) > -1) {
  1941. statsInterval = true;
  1942.  
  1943. // Do an initial getConnectionStatus() to backfill the first retrieval in order to do (currentTotalStats - lastTotalStats).
  1944. self.getConnectionStatus(targetMid, function () {
  1945. statsInterval = setInterval(function () {
  1946. if (!(self._peerConnections[targetMid] && self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED)) {
  1947. clearInterval(statsInterval);
  1948. return;
  1949. }
  1950. self._handleBandwidthStats(targetMid);
  1951. }, 20000);
  1952. });
  1953. }
  1954.  
  1955. if (!self._hasMCU && [self.ICE_CONNECTION_STATE.CONNECTED, self.ICE_CONNECTION_STATE.COMPLETED].indexOf(
  1956. iceConnectionState) > -1 && !!self._bandwidthAdjuster && !bandwidth && AdapterJS.webrtcDetectedBrowser !== 'edge' &&
  1957. (((self._peerInformations[targetMid] || {}).agent || {}).name || 'edge') !== 'edge') {
  1958. var currentBlock = 0;
  1959. var formatTotalFn = function (arr) {
  1960. var total = 0;
  1961. for (var i = 0; i < arr.length; i++) {
  1962. total += arr[i];
  1963. }
  1964. return total / arr.length;
  1965. };
  1966. bandwidth = {
  1967. audio: { send: [], recv: [] },
  1968. video: { send: [], recv: [] }
  1969. };
  1970. var pullInterval = setInterval(function () {
  1971. if (!(self._peerConnections[targetMid] && self._peerConnections[targetMid].signalingState !==
  1972. self.PEER_CONNECTION_STATE.CLOSED) || !self._bandwidthAdjuster || !self._peerBandwidth[targetMid]) {
  1973. clearInterval(pullInterval);
  1974. return;
  1975. }
  1976. self._retrieveStats(targetMid, function (err, stats) {
  1977. if (!(self._peerConnections[targetMid] && self._peerConnections[targetMid].signalingState !==
  1978. self.PEER_CONNECTION_STATE.CLOSED) || !self._bandwidthAdjuster) {
  1979. clearInterval(pullInterval);
  1980. return;
  1981. }
  1982. if (err) {
  1983. bandwidth.audio.send.push(0);
  1984. bandwidth.audio.recv.push(0);
  1985. bandwidth.video.send.push(0);
  1986. bandwidth.video.recv.push(0);
  1987. } else {
  1988. bandwidth.audio.send.push(stats.audio.sending.bytes * 8);
  1989. bandwidth.audio.recv.push(stats.audio.receiving.bytes * 8);
  1990. bandwidth.video.send.push(stats.video.sending.bytes * 8);
  1991. bandwidth.video.recv.push(stats.video.receiving.bytes * 8);
  1992. }
  1993. currentBlock++;
  1994. if (currentBlock === self._bandwidthAdjuster.interval) {
  1995. currentBlock = 0;
  1996. var totalAudioBw = formatTotalFn(bandwidth.audio.send);
  1997. var totalVideoBw = formatTotalFn(bandwidth.video.send);
  1998. if (!self._bandwidthAdjuster.useUploadBwOnly) {
  1999. totalAudioBw += formatTotalFn(bandwidth.audio.recv);
  2000. totalVideoBw += formatTotalFn(bandwidth.video.recv);
  2001. totalAudioBw = totalAudioBw / 2;
  2002. totalVideoBw = totalVideoBw / 2;
  2003. }
  2004. totalAudioBw = parseInt((totalAudioBw * (self._bandwidthAdjuster.limitAtPercentage / 100)) / 1000, 10);
  2005. totalVideoBw = parseInt((totalVideoBw * (self._bandwidthAdjuster.limitAtPercentage / 100)) / 1000, 10);
  2006. bandwidth = {
  2007. audio: { send: [], recv: [] },
  2008. video: { send: [], recv: [] }
  2009. };
  2010. self.refreshConnection(targetMid, {
  2011. bandwidth: { audio: totalAudioBw, video: totalVideoBw }
  2012. });
  2013. }
  2014. }, true, true);
  2015. }, 1000);
  2016. }
  2017. };
  2018.  
  2019. pc.onsignalingstatechange = function() {
  2020. log.debug([targetMid, 'RTCSignalingState', null, 'Peer connection state changed ->'], pc.signalingState);
  2021.  
  2022. if (AdapterJS.webrtcDetectedType === 'AppleWebKit' && pc.signalingState === self.PEER_CONNECTION_STATE.CLOSED) {
  2023. setTimeout(function () {
  2024. if (!pc.signalingStateClosed) {
  2025. self._trigger('peerConnectionState', self.PEER_CONNECTION_STATE.CLOSED, targetMid);
  2026. }
  2027. }, 10);
  2028. return;
  2029. }
  2030.  
  2031. self._trigger('peerConnectionState', pc.signalingState, targetMid);
  2032. };
  2033. pc.onicegatheringstatechange = function() {
  2034. log.log([targetMid, 'RTCIceGatheringState', null, 'Ice gathering state changed ->'], pc.iceGatheringState);
  2035. self._trigger('candidateGenerationState', pc.iceGatheringState, targetMid);
  2036. };
  2037.  
  2038. if (AdapterJS.webrtcDetectedBrowser === 'firefox') {
  2039. pc.removeStream = function (stream) {
  2040. var senders = pc.getSenders();
  2041. for (var s = 0; s < senders.length; s++) {
  2042. var tracks = stream.getTracks();
  2043. for (var t = 0; t < tracks.length; t++) {
  2044. if (tracks[t] === senders[s].track) {
  2045. pc.removeTrack(senders[s]);
  2046. }
  2047. }
  2048. }
  2049. };
  2050. }
  2051.  
  2052. self._handleIceConnectionStats(pc.iceConnectionState, targetMid);
  2053. self._handleIceGatheringStats('new', targetMid, false);
  2054. return pc;
  2055. };
  2056.  
  2057. /**
  2058. * Function that handles the <code>_restartPeerConnection</code> scenario
  2059. * for MCU enabled Peer connections.
  2060. * This is implemented currently by making the user leave and join the Room again.
  2061. * The Peer ID will not stay the same though.
  2062. * @method _restartMCUConnection
  2063. * @private
  2064. * @for Skylink
  2065. * @since 0.6.1
  2066. */
  2067. Skylink.prototype._restartMCUConnection = function(callback, doIceRestart, bwOptions) {
  2068. var self = this;
  2069. var listOfPeers = Object.keys(self._peerConnections);
  2070. var listOfPeerRestartErrors = {};
  2071. var sendRestartMsgFn = function (peerId) {
  2072. var restartMsg = {
  2073. type: self._SIG_MESSAGE_TYPE.RESTART,
  2074. mid: self._user.sid,
  2075. rid: self._room.id,
  2076. agent: AdapterJS.webrtcDetectedBrowser,
  2077. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  2078. os: window.navigator.platform,
  2079. userInfo: self._getUserInfo(peerId),
  2080. target: peerId,
  2081. weight: self._peerPriorityWeight,
  2082. receiveOnly: self.getPeerInfo().config.receiveOnly,
  2083. enableIceTrickle: self._initOptions.enableIceTrickle,
  2084. enableDataChannel: self._initOptions.enableDataChannel,
  2085. enableIceRestart: self._enableIceRestart,
  2086. doIceRestart: self._initOptions.mcuUseRenegoRestart && doIceRestart === true &&
  2087. self._enableIceRestart && self._peerInformations[peerId] &&
  2088. self._peerInformations[peerId].config.enableIceRestart,
  2089. isRestartResend: false,
  2090. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  2091. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  2092. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  2093. };
  2094.  
  2095. if (self._publishOnly) {
  2096. restartMsg.publishOnly = {
  2097. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  2098. };
  2099. }
  2100.  
  2101. if (self._parentId) {
  2102. restartMsg.parentId = self._parentId;
  2103. }
  2104.  
  2105. // log.log([peerId, 'RTCPeerConnection', null, 'Sending restart message to signaling server ->'], restartMsg);
  2106.  
  2107. self._doOffer('MCU', doIceRestart, restartMsg);
  2108. //self._handleNegotiationStats('restart', peerId, restartMsg, false);
  2109. };
  2110.  
  2111. // Toggle the main bandwidth options.
  2112. if (bwOptions.bandwidth && typeof bwOptions.bandwidth === 'object') {
  2113. if (typeof bwOptions.bandwidth.audio === 'number') {
  2114. self._streamsBandwidthSettings.bAS.audio = bwOptions.bandwidth.audio;
  2115. }
  2116. if (typeof bwOptions.bandwidth.video === 'number') {
  2117. self._streamsBandwidthSettings.bAS.video = bwOptions.bandwidth.video;
  2118. }
  2119. if (typeof bwOptions.bandwidth.data === 'number') {
  2120. self._streamsBandwidthSettings.bAS.data = bwOptions.bandwidth.data;
  2121. }
  2122. }
  2123.  
  2124. if (bwOptions.googleXBandwidth && typeof bwOptions.googleXBandwidth === 'object') {
  2125. if (typeof bwOptions.googleXBandwidth.min === 'number') {
  2126. self._streamsBandwidthSettings.googleX.min = bwOptions.googleXBandwidth.min;
  2127. }
  2128. if (typeof bwOptions.googleXBandwidth.max === 'number') {
  2129. self._streamsBandwidthSettings.googleX.max = bwOptions.googleXBandwidth.max;
  2130. }
  2131. }
  2132.  
  2133.  
  2134. // Below commented since with new MCU only peer connected is MCU
  2135.  
  2136. // for (var i = 0; i < listOfPeers.length; i++) {
  2137. // if (!self._peerConnections[listOfPeers[i]]) {
  2138. // var error = 'Peer connection with peer does not exists. Unable to restart';
  2139. // log.error([listOfPeers[i], 'PeerConnection', null, error]);
  2140. // listOfPeerRestartErrors[listOfPeers[i]] = new Error(error);
  2141. // continue;
  2142. // }
  2143. //
  2144. // if (listOfPeers[i] !== 'MCU') {
  2145. // self._trigger('peerRestart', listOfPeers[i], self.getPeerInfo(listOfPeers[i]), true, false);
  2146. //
  2147. // if (!self._initOptions.mcuUseRenegoRestart) {
  2148. // sendRestartMsgFn(listOfPeers[i]);
  2149. // }
  2150. // }
  2151. // }
  2152.  
  2153. // self._trigger('serverPeerRestart', 'MCU', self.SERVER_PEER_TYPE.MCU);
  2154.  
  2155. if (self._initOptions.mcuUseRenegoRestart) {
  2156. self._peerEndOfCandidatesCounter.MCU = self._peerEndOfCandidatesCounter.MCU || {};
  2157. self._peerEndOfCandidatesCounter.MCU.len = 0;
  2158. sendRestartMsgFn('MCU');
  2159. } else {
  2160. // Restart with MCU = peer leaves then rejoins room
  2161. var peerJoinedFn = function (peerId, peerInfo, isSelf) {
  2162. log.log([null, 'PeerConnection', null, 'Invoked all peers to restart with MCU. Firing callback']);
  2163.  
  2164. if (typeof callback === 'function') {
  2165. if (Object.keys(listOfPeerRestartErrors).length > 0) {
  2166. callback({
  2167. refreshErrors: listOfPeerRestartErrors,
  2168. listOfPeers: listOfPeers
  2169. }, null);
  2170. } else {
  2171. callback(null, {
  2172. listOfPeers: listOfPeers
  2173. });
  2174. }
  2175. }
  2176. };
  2177.  
  2178. self.once('peerJoined', peerJoinedFn, function (peerId, peerInfo, isSelf) {
  2179. return isSelf;
  2180. });
  2181.  
  2182. self.leaveRoom(false, function (error, success) {
  2183. if (error) {
  2184. if (typeof callback === 'function') {
  2185. for (var i = 0; i < listOfPeers.length; i++) {
  2186. listOfPeerRestartErrors[listOfPeers[i]] = error;
  2187. }
  2188. callback({
  2189. refreshErrors: listOfPeerRestartErrors,
  2190. listOfPeers: listOfPeers
  2191. }, null);
  2192. }
  2193. } else {
  2194. //self._trigger('serverPeerLeft', 'MCU', self.SERVER_PEER_TYPE.MCU);
  2195. self.joinRoom(self._selectedRoom, {
  2196. bandwidth: bwOptions.bandwidth || {},
  2197. googleXBandwidth: bwOptions.googleXBandwidth || {},
  2198. sdpSettings: clone(self._sdpSettings),
  2199. voiceActivityDetection: self._voiceActivityDetection,
  2200. publishOnly: !!self._publishOnly,
  2201. parentId: self._parentId || null,
  2202. autoBandwidthAdjustment: self._bandwidthAdjuster
  2203. });
  2204. }
  2205. });
  2206. }
  2207. };
  2208.  
  2209. /**
  2210. * Function that handles the stats tabulation.
  2211. * @method _parseConnectionStats
  2212. * @private
  2213. * @for Skylink
  2214. * @since 0.6.16
  2215. */
  2216. Skylink.prototype._parseConnectionStats = function(prevStats, stats, prop) {
  2217. var nTime = stats.timestamp;
  2218. var oTime = prevStats ? prevStats.timestamp || 0 : 0;
  2219. var nVal = parseFloat(stats[prop] || '0', 10);
  2220. var oVal = parseFloat(prevStats ? prevStats[prop] || '0' : '0', 10);
  2221.  
  2222. if ((new Date(nTime).getTime()) === (new Date(oTime).getTime())) {
  2223. return nVal;
  2224. }
  2225.  
  2226. return parseFloat(((nVal - oVal) / (nTime - oTime) * 1000).toFixed(3) || '0', 10);
  2227. };
  2228.  
  2229. /**
  2230. * Function that signals the end-of-candidates flag.
  2231. * @method _signalingEndOfCandidates
  2232. * @private
  2233. * @for Skylink
  2234. * @since 0.6.16
  2235. */
  2236. Skylink.prototype._signalingEndOfCandidates = function(targetMid) {
  2237. var self = this;
  2238.  
  2239. if (!self._peerEndOfCandidatesCounter[targetMid]) {
  2240. return;
  2241. }
  2242.  
  2243. if (
  2244. // If peer connection exists first and state is not closed.
  2245. self._peerConnections[targetMid] && self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED &&
  2246. // If remote description is set
  2247. self._peerConnections[targetMid].remoteDescription && self._peerConnections[targetMid].remoteDescription.sdp &&
  2248. // If end-of-candidates signal is received
  2249. typeof self._peerEndOfCandidatesCounter[targetMid].expectedLen === 'number' &&
  2250. // If all ICE candidates are received
  2251. self._peerEndOfCandidatesCounter[targetMid].len >= self._peerEndOfCandidatesCounter[targetMid].expectedLen &&
  2252. // If there is no ICE candidates queue
  2253. (self._peerCandidatesQueue[targetMid] ? self._peerCandidatesQueue[targetMid].length === 0 : true) &&
  2254. // If it has not been set yet
  2255. !self._peerEndOfCandidatesCounter[targetMid].hasSet) {
  2256. log.debug([targetMid, 'RTCPeerConnection', null, 'Signaling of end-of-candidates remote ICE gathering.']);
  2257.  
  2258. self._peerEndOfCandidatesCounter[targetMid].hasSet = true;
  2259.  
  2260. try {
  2261. if (AdapterJS.webrtcDetectedBrowser === 'edge') {
  2262. var mLineCounter = -1;
  2263. var addedMids = [];
  2264. var sdpLines = self._peerConnections[targetMid].remoteDescription.sdp.split('\r\n');
  2265. var rejected = false;
  2266.  
  2267. for (var i = 0; i < sdpLines.length; i++) {
  2268. if (sdpLines[i].indexOf('m=') === 0) {
  2269. rejected = sdpLines[i].split(' ')[1] === '0';
  2270. mLineCounter++;
  2271. } else if (sdpLines[i].indexOf('a=mid:') === 0 && !rejected) {
  2272. var mid = sdpLines[i].split('a=mid:')[1] || '';
  2273. if (mid && addedMids.indexOf(mid) === -1) {
  2274. addedMids.push(mid);
  2275. self._addIceCandidate(targetMid, 'endofcan-' + (new Date()).getTime(), new RTCIceCandidate({
  2276. sdpMid: mid,
  2277. sdpMLineIndex: mLineCounter,
  2278. candidate: 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'
  2279. }));
  2280. // Start breaking after the first add because of max-bundle option
  2281. if (self._peerConnectionConfig.bundlePolicy === self.BUNDLE_POLICY.MAX_BUNDLE) {
  2282. break;
  2283. }
  2284. }
  2285. }
  2286. }
  2287.  
  2288. } else if (AdapterJS && !self._isLowerThanVersion(AdapterJS.VERSION, '0.14.0')) {
  2289. self._peerConnections[targetMid].addIceCandidate(null);
  2290. }
  2291.  
  2292. if (self._gatheredCandidates[targetMid]) {
  2293. self._trigger('candidatesGathered', targetMid, {
  2294. expected: self._peerEndOfCandidatesCounter[targetMid].expectedLen || 0,
  2295. received: self._peerEndOfCandidatesCounter[targetMid].len || 0,
  2296. processed: self._gatheredCandidates[targetMid].receiving.srflx.length +
  2297. self._gatheredCandidates[targetMid].receiving.relay.length +
  2298. self._gatheredCandidates[targetMid].receiving.host.length
  2299. });
  2300. }
  2301.  
  2302. } catch (error) {
  2303. log.error([targetMid, 'RTCPeerConnection', null, 'Failed signaling end-of-candidates ->'], error);
  2304. }
  2305. }
  2306. };
  2307.  
  2308. /**
  2309. * Function that compares trackCount sent by MCU and decides wether to add new Transceivers based on count differences
  2310. * @method _compareTrackCounts
  2311. * @param {String} targetMid
  2312. * @private
  2313. */
  2314. Skylink.prototype._compareTrackCounts = function (targetMid) {
  2315. var self = this;
  2316. var pc = self._peerConnections[targetMid];
  2317.  
  2318. if (pc && typeof pc.getTransceivers === 'function') {
  2319. var transceivers = pc.getTransceivers();
  2320. var transceiverTypeCount = {
  2321. audio: 0,
  2322. video: 0
  2323. };
  2324.  
  2325. var checkDiffAndAddTransceivers = function (kind, requestedKindCount, actualKindCount, peerConnection) {
  2326. if (requestedKindCount > actualKindCount) {
  2327. var diff = requestedKindCount - actualKindCount;
  2328. while (diff) {
  2329. peerConnection.addTransceiver(kind);
  2330. diff = diff - 1;
  2331. }
  2332. }
  2333. };
  2334.  
  2335. if (transceivers && transceivers.length) {
  2336. for (var i = 0; i < transceivers.length; i++) {
  2337. if (transceivers[i] && transceivers[i].receiver && transceivers[i].receiver.track) {
  2338. var kind = transceivers[i].receiver.track.kind;
  2339. transceiverTypeCount[kind] += 1;
  2340. }
  2341. }
  2342. }
  2343.  
  2344. checkDiffAndAddTransceivers('video', self._currentRequestedTracks['video'], transceiverTypeCount.video, pc);
  2345. checkDiffAndAddTransceivers('audio', self._currentRequestedTracks['audio'], transceiverTypeCount.audio, pc);
  2346. }
  2347. };
  2348.  
  2349. /**
  2350. * Function that iterates the Peer Connections and replaces track using the RTCRTPSender.replaceTrack
  2351. * @method _replaceTrack
  2352. * @param {String} trackIDToCompare - ID of the track whose RTCRTPSenders needs to be used
  2353. * @param {MediaStreamTrack} trackToReplace - The new track which will replace the old track
  2354. * @private
  2355. */
  2356. Skylink.prototype._replaceTrack = function (trackIDToCompare, trackToReplace) {
  2357. var self = this;
  2358. if (Object.keys(self._peerConnections).length > 0) {
  2359. var peerIds = Object.keys(self._peerConnections);
  2360. for (var i = 0; i < peerIds.length; i++) {
  2361. var pc = self._peerConnections[peerIds[i]];
  2362. var senders = pc.getSenders();
  2363.  
  2364. for (var y = 0; y < senders.length; y++) {
  2365. var sender = senders[y];
  2366. if (sender.track && sender.track.id === trackIDToCompare) {
  2367. sender.replaceTrack(trackToReplace);
  2368. }
  2369. }
  2370. }
  2371. }
  2372. };
  2373.  
  2374.  
  2375.