File: source/ice-candidate.js

  1. /**
  2. * Function that handles the Peer connection gathered ICE candidate to be sent.
  3. * @method _onIceCandidate
  4. * @private
  5. * @for Skylink
  6. * @since 0.1.0
  7. */
  8. Skylink.prototype._onIceCandidate = function(targetMid, candidate) {
  9. var self = this;
  10. var pc = self._peerConnections[targetMid];
  11.  
  12. if (!pc) {
  13. log.warn([targetMid, 'RTCIceCandidate', null, 'Ignoring of ICE candidate event as ' +
  14. 'Peer connection does not exists ->'], candidate);
  15. return;
  16. }
  17.  
  18. if (candidate.candidate) {
  19. if (!pc.gathering) {
  20. log.log([targetMid, 'RTCIceCandidate', null, 'ICE gathering has started.']);
  21.  
  22. pc.gathering = true;
  23. pc.gathered = false;
  24.  
  25. self._handleIceGatheringStats('gathering', targetMid, false);
  26. self._trigger('candidateGenerationState', self.CANDIDATE_GENERATION_STATE.GATHERING, targetMid);
  27. }
  28.  
  29. var candidateType = candidate.candidate.split(' ')[7];
  30.  
  31. log.debug([targetMid, 'RTCIceCandidate', candidateType, 'Generated ICE candidate ->'], candidate);
  32.  
  33. if (candidateType === 'endOfCandidates' || !(self._peerConnections[targetMid] &&
  34. self._peerConnections[targetMid].localDescription && self._peerConnections[targetMid].localDescription.sdp &&
  35. self._peerConnections[targetMid].localDescription.sdp.indexOf('\r\na=mid:' + candidate.sdpMid + '\r\n') > -1)) {
  36. log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate ' +
  37. 'end-of-candidates signal or unused ICE candidates to prevent errors ->'], candidate);
  38. self._handleIceCandidateStats('dropped', targetMid, null, candidate);
  39. return;
  40. }
  41.  
  42. if (self._initOptions.filterCandidatesType[candidateType]) {
  43. if (!(self._hasMCU && self._initOptions.forceTURN)) {
  44. log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate as ' +
  45. 'it matches ICE candidate filtering flag ->'], candidate);
  46. self._handleIceCandidateStats('dropped', targetMid, null, candidate);
  47. return;
  48. }
  49.  
  50. log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Not dropping of sending ICE candidate as ' +
  51. 'TURN connections are enforced as MCU is present (and act as a TURN itself) so filtering of ICE candidate ' +
  52. 'flags are not honoured ->'], candidate);
  53. }
  54.  
  55. if (!self._gatheredCandidates[targetMid]) {
  56. self._gatheredCandidates[targetMid] = {
  57. sending: { host: [], srflx: [], relay: [] },
  58. receiving: { host: [], srflx: [], relay: [] }
  59. };
  60. }
  61.  
  62. self._gatheredCandidates[targetMid].sending[candidateType].push({
  63. sdpMid: candidate.sdpMid,
  64. sdpMLineIndex: candidate.sdpMLineIndex,
  65. candidate: candidate.candidate
  66. });
  67.  
  68. if (!self._initOptions.enableIceTrickle) {
  69. log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate as ' +
  70. 'trickle ICE is disabled ->'], candidate);
  71. self._handleIceCandidateStats('non_trickle', targetMid, null, candidate);
  72. return;
  73. }
  74.  
  75. log.debug([targetMid, 'RTCIceCandidate', candidateType, 'Sending ICE candidate ->'], candidate);
  76.  
  77. self._sendChannelMessage({
  78. type: self._SIG_MESSAGE_TYPE.CANDIDATE,
  79. label: candidate.sdpMLineIndex,
  80. id: candidate.sdpMid,
  81. candidate: candidate.candidate,
  82. mid: self._user.sid,
  83. target: targetMid,
  84. rid: self._room.id
  85. });
  86. self._handleIceCandidateStats('received', targetMid, null, candidate);
  87.  
  88. } else {
  89. log.log([targetMid, 'RTCIceCandidate', null, 'ICE gathering has completed.']);
  90.  
  91. if (pc.gathered) {
  92. return;
  93. }
  94.  
  95. pc.gathering = false;
  96. pc.gathered = true;
  97.  
  98. self._handleIceGatheringStats('complete', targetMid, false);
  99. self._trigger('candidateGenerationState', self.CANDIDATE_GENERATION_STATE.COMPLETED, targetMid);
  100.  
  101. // Disable Ice trickle option
  102. if (!self._initOptions.enableIceTrickle) {
  103. var sessionDescription = self._peerConnections[targetMid].localDescription;
  104.  
  105. if (!(sessionDescription && sessionDescription.type && sessionDescription.sdp)) {
  106. log.warn([targetMid, 'RTCSessionDescription', null, 'Not sending any session description after ' +
  107. 'ICE gathering completed as it is not present.']);
  108. return;
  109. }
  110.  
  111. // a=end-of-candidates should present in non-trickle ICE connections so no need to send endOfCandidates message
  112. self._sendChannelMessage({
  113. type: sessionDescription.type,
  114. sdp: self._renderSDPOutput(targetMid, sessionDescription),
  115. mid: self._user.sid,
  116. userInfo: self._getUserInfo(targetMid),
  117. target: targetMid,
  118. rid: self._room.id
  119. });
  120. } else if (self._gatheredCandidates[targetMid]) {
  121. var sendEndOfCandidates = function() {
  122. if (self._gatheredCandidates[targetMid] && self._gatheredCandidates[targetMid].sending) {
  123. self._sendChannelMessage({
  124. type: self._SIG_MESSAGE_TYPE.END_OF_CANDIDATES,
  125. noOfExpectedCandidates: self._gatheredCandidates[targetMid].sending.srflx.length +
  126. self._gatheredCandidates[targetMid].sending.host.length +
  127. self._gatheredCandidates[targetMid].sending.relay.length,
  128. mid: self._user.sid,
  129. target: targetMid,
  130. rid: self._room.id
  131. });
  132. }
  133. };
  134. setTimeout(sendEndOfCandidates, 6000);
  135. }
  136. }
  137. };
  138.  
  139. /**
  140. * Function that buffers the Peer connection ICE candidate when received
  141. * before remote session description is received and set.
  142. * @method _addIceCandidateToQueue
  143. * @private
  144. * @for Skylink
  145. * @since 0.5.2
  146. */
  147. Skylink.prototype._addIceCandidateToQueue = function(targetMid, canId, candidate) {
  148. var candidateType = candidate.candidate.split(' ')[7];
  149.  
  150. log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Buffering ICE candidate.']);
  151.  
  152. this._handleIceCandidateStats('buffered', targetMid, canId, candidate);
  153. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.BUFFERED,
  154. targetMid, canId, candidateType, {
  155. candidate: candidate.candidate,
  156. sdpMid: candidate.sdpMid,
  157. sdpMLineIndex: candidate.sdpMLineIndex
  158. }, null);
  159.  
  160. this._peerCandidatesQueue[targetMid] = this._peerCandidatesQueue[targetMid] || [];
  161. this._peerCandidatesQueue[targetMid].push([canId, candidate]);
  162. };
  163.  
  164. /**
  165. * Function that adds all the Peer connection buffered ICE candidates received.
  166. * This should be called only after the remote session description is received and set.
  167. * @method _addIceCandidateFromQueue
  168. * @private
  169. * @for Skylink
  170. * @since 0.5.2
  171. */
  172. Skylink.prototype._addIceCandidateFromQueue = function(targetMid) {
  173. this._peerCandidatesQueue[targetMid] = this._peerCandidatesQueue[targetMid] || [];
  174.  
  175. for (var i = 0; i < this._peerCandidatesQueue[targetMid].length; i++) {
  176. var canArray = this._peerCandidatesQueue[targetMid][i];
  177.  
  178. if (canArray) {
  179. var candidateType = canArray[1].candidate.split(' ')[7];
  180.  
  181. log.debug([targetMid, 'RTCIceCandidate', canArray[0] + ':' + candidateType, 'Adding buffered ICE candidate.']);
  182.  
  183. this._addIceCandidate(targetMid, canArray[0], canArray[1]);
  184. } else if (this._peerConnections[targetMid] &&
  185. this._peerConnections[targetMid].signalingState !== this.PEER_CONNECTION_STATE.CLOSED &&
  186. AdapterJS && !this._isLowerThanVersion(AdapterJS.VERSION, '0.14.0')) {
  187. try {
  188. this._peerConnections[targetMid].addIceCandidate(null);
  189. log.debug([targetMid, 'RTCPeerConnection', null, 'Signaling of end-of-candidates remote ICE gathering.']);
  190. } catch (error) {
  191. log.error([targetMid, 'RTCPeerConnection', null, 'Failed signaling of end-of-candidates remote ICE gathering.']);
  192. }
  193. }
  194. }
  195.  
  196. delete this._peerCandidatesQueue[targetMid];
  197.  
  198. this._signalingEndOfCandidates(targetMid);
  199. };
  200.  
  201. /**
  202. * Function that adds the ICE candidate to Peer connection.
  203. * @method _addIceCandidate
  204. * @private
  205. * @for Skylink
  206. * @since 0.6.16
  207. */
  208. Skylink.prototype._addIceCandidate = function (targetMid, canId, candidate) {
  209. var self = this;
  210. var candidateType = candidate.candidate.split(' ')[7];
  211.  
  212. var onSuccessCbFn = function () {
  213. log.log([targetMid, 'RTCIceCandidate', canId + ':' + candidateType,
  214. 'Added ICE candidate successfully.']);
  215. self._handleIceCandidateStats('process_success', targetMid, canId, candidate);
  216. self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESS_SUCCESS,
  217. targetMid, canId, candidateType, {
  218. candidate: candidate.candidate,
  219. sdpMid: candidate.sdpMid,
  220. sdpMLineIndex: candidate.sdpMLineIndex
  221. }, null);
  222. };
  223.  
  224. var onErrorCbFn = function (error) {
  225. log.error([targetMid, 'RTCIceCandidate', canId + ':' + candidateType,
  226. 'Failed adding ICE candidate ->'], error);
  227. self._handleIceCandidateStats('process_failed', targetMid, canId, candidate, error);
  228. self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESS_ERROR,
  229. targetMid, canId, candidateType, {
  230. candidate: candidate.candidate,
  231. sdpMid: candidate.sdpMid,
  232. sdpMLineIndex: candidate.sdpMLineIndex
  233. }, error);
  234. };
  235.  
  236. log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Adding ICE candidate.']);
  237.  
  238. self._handleIceCandidateStats('processing', targetMid, canId, candidate);
  239. self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESSING,
  240. targetMid, canId, candidateType, {
  241. candidate: candidate.candidate,
  242. sdpMid: candidate.sdpMid,
  243. sdpMLineIndex: candidate.sdpMLineIndex
  244. }, null);
  245.  
  246. if (!(self._peerConnections[targetMid] &&
  247. self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED &&
  248. self._peerConnections[targetMid].remoteDescription &&
  249. self._peerConnections[targetMid].remoteDescription.sdp &&
  250. self._peerConnections[targetMid].remoteDescription.sdp.indexOf('\r\na=mid:' + candidate.sdpMid + '\r\n') > -1)) {
  251. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping ICE candidate ' +
  252. 'as Peer connection does not exists or is closed']);
  253. self._handleIceCandidateStats('process_failed', targetMid, canId, candidate, 'Peer connection does not exist');
  254. self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.DROPPED,
  255. targetMid, canId, candidateType, {
  256. candidate: candidate.candidate,
  257. sdpMid: candidate.sdpMid,
  258. sdpMLineIndex: candidate.sdpMLineIndex
  259. }, new Error('Failed processing ICE candidate as Peer connection does not exists or is closed.'));
  260. return;
  261. }
  262.  
  263. try {
  264. self._peerConnections[targetMid].addIceCandidate(candidate, onSuccessCbFn, onErrorCbFn);
  265. } catch (error) {
  266. onErrorCbFn(error);
  267. }
  268. };