/**
* Function that handles the Peer connection gathered ICE candidate to be sent.
* @method _onIceCandidate
* @private
* @for Skylink
* @since 0.1.0
*/
Skylink.prototype._onIceCandidate = function(targetMid, candidate) {
var self = this;
var pc = self._peerConnections[targetMid];
if (!pc) {
log.warn([targetMid, 'RTCIceCandidate', null, 'Ignoring of ICE candidate event as ' +
'Peer connection does not exists ->'], candidate);
return;
}
if (candidate.candidate) {
if (!pc.gathering) {
log.log([targetMid, 'RTCIceCandidate', null, 'ICE gathering has started.']);
pc.gathering = true;
pc.gathered = false;
self._handleIceGatheringStats('gathering', targetMid, false);
self._trigger('candidateGenerationState', self.CANDIDATE_GENERATION_STATE.GATHERING, targetMid);
}
var candidateType = candidate.candidate.split(' ')[7];
log.debug([targetMid, 'RTCIceCandidate', candidateType, 'Generated ICE candidate ->'], candidate);
if (candidateType === 'endOfCandidates' || !(self._peerConnections[targetMid] &&
self._peerConnections[targetMid].localDescription && self._peerConnections[targetMid].localDescription.sdp &&
self._peerConnections[targetMid].localDescription.sdp.indexOf('\r\na=mid:' + candidate.sdpMid + '\r\n') > -1)) {
log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate ' +
'end-of-candidates signal or unused ICE candidates to prevent errors ->'], candidate);
self._handleIceCandidateStats('dropped', targetMid, null, candidate);
return;
}
if (self._initOptions.filterCandidatesType[candidateType]) {
if (!(self._hasMCU && self._initOptions.forceTURN)) {
log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate as ' +
'it matches ICE candidate filtering flag ->'], candidate);
self._handleIceCandidateStats('dropped', targetMid, null, candidate);
return;
}
log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Not dropping of sending ICE candidate as ' +
'TURN connections are enforced as MCU is present (and act as a TURN itself) so filtering of ICE candidate ' +
'flags are not honoured ->'], candidate);
}
if (!self._gatheredCandidates[targetMid]) {
self._gatheredCandidates[targetMid] = {
sending: { host: [], srflx: [], relay: [] },
receiving: { host: [], srflx: [], relay: [] }
};
}
self._gatheredCandidates[targetMid].sending[candidateType].push({
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex,
candidate: candidate.candidate
});
if (!self._initOptions.enableIceTrickle) {
log.warn([targetMid, 'RTCIceCandidate', candidateType, 'Dropping of sending ICE candidate as ' +
'trickle ICE is disabled ->'], candidate);
self._handleIceCandidateStats('non_trickle', targetMid, null, candidate);
return;
}
log.debug([targetMid, 'RTCIceCandidate', candidateType, 'Sending ICE candidate ->'], candidate);
self._sendChannelMessage({
type: self._SIG_MESSAGE_TYPE.CANDIDATE,
label: candidate.sdpMLineIndex,
id: candidate.sdpMid,
candidate: candidate.candidate,
mid: self._user.sid,
target: targetMid,
rid: self._room.id
});
self._handleIceCandidateStats('received', targetMid, null, candidate);
} else {
log.log([targetMid, 'RTCIceCandidate', null, 'ICE gathering has completed.']);
if (pc.gathered) {
return;
}
pc.gathering = false;
pc.gathered = true;
self._handleIceGatheringStats('complete', targetMid, false);
self._trigger('candidateGenerationState', self.CANDIDATE_GENERATION_STATE.COMPLETED, targetMid);
// Disable Ice trickle option
if (!self._initOptions.enableIceTrickle) {
var sessionDescription = self._peerConnections[targetMid].localDescription;
if (!(sessionDescription && sessionDescription.type && sessionDescription.sdp)) {
log.warn([targetMid, 'RTCSessionDescription', null, 'Not sending any session description after ' +
'ICE gathering completed as it is not present.']);
return;
}
// a=end-of-candidates should present in non-trickle ICE connections so no need to send endOfCandidates message
self._sendChannelMessage({
type: sessionDescription.type,
sdp: self._renderSDPOutput(targetMid, sessionDescription),
mid: self._user.sid,
userInfo: self._getUserInfo(targetMid),
target: targetMid,
rid: self._room.id
});
} else if (self._gatheredCandidates[targetMid]) {
var sendEndOfCandidates = function() {
self._sendChannelMessage({
type: self._SIG_MESSAGE_TYPE.END_OF_CANDIDATES,
noOfExpectedCandidates: self._gatheredCandidates[targetMid].sending.srflx.length +
self._gatheredCandidates[targetMid].sending.host.length +
self._gatheredCandidates[targetMid].sending.relay.length,
mid: self._user.sid,
target: targetMid,
rid: self._room.id
});
};
setTimeout(sendEndOfCandidates, 6000);
}
}
};
/**
* Function that buffers the Peer connection ICE candidate when received
* before remote session description is received and set.
* @method _addIceCandidateToQueue
* @private
* @for Skylink
* @since 0.5.2
*/
Skylink.prototype._addIceCandidateToQueue = function(targetMid, canId, candidate) {
var candidateType = candidate.candidate.split(' ')[7];
log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Buffering ICE candidate.']);
this._handleIceCandidateStats('buffered', targetMid, canId, candidate);
this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.BUFFERED,
targetMid, canId, candidateType, {
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, null);
this._peerCandidatesQueue[targetMid] = this._peerCandidatesQueue[targetMid] || [];
this._peerCandidatesQueue[targetMid].push([canId, candidate]);
};
/**
* Function that adds all the Peer connection buffered ICE candidates received.
* This should be called only after the remote session description is received and set.
* @method _addIceCandidateFromQueue
* @private
* @for Skylink
* @since 0.5.2
*/
Skylink.prototype._addIceCandidateFromQueue = function(targetMid) {
this._peerCandidatesQueue[targetMid] = this._peerCandidatesQueue[targetMid] || [];
for (var i = 0; i < this._peerCandidatesQueue[targetMid].length; i++) {
var canArray = this._peerCandidatesQueue[targetMid][i];
if (canArray) {
var candidateType = canArray[1].candidate.split(' ')[7];
log.debug([targetMid, 'RTCIceCandidate', canArray[0] + ':' + candidateType, 'Adding buffered ICE candidate.']);
this._addIceCandidate(targetMid, canArray[0], canArray[1]);
} else if (this._peerConnections[targetMid] &&
this._peerConnections[targetMid].signalingState !== this.PEER_CONNECTION_STATE.CLOSED &&
AdapterJS && !this._isLowerThanVersion(AdapterJS.VERSION, '0.14.0')) {
try {
this._peerConnections[targetMid].addIceCandidate(null);
log.debug([targetMid, 'RTCPeerConnection', null, 'Signaling of end-of-candidates remote ICE gathering.']);
} catch (error) {
log.error([targetMid, 'RTCPeerConnection', null, 'Failed signaling of end-of-candidates remote ICE gathering.']);
}
}
}
delete this._peerCandidatesQueue[targetMid];
this._signalingEndOfCandidates(targetMid);
};
/**
* Function that adds the ICE candidate to Peer connection.
* @method _addIceCandidate
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._addIceCandidate = function (targetMid, canId, candidate) {
var self = this;
var candidateType = candidate.candidate.split(' ')[7];
var onSuccessCbFn = function () {
log.log([targetMid, 'RTCIceCandidate', canId + ':' + candidateType,
'Added ICE candidate successfully.']);
self._handleIceCandidateStats('process_success', targetMid, canId, candidate);
self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESS_SUCCESS,
targetMid, canId, candidateType, {
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, null);
};
var onErrorCbFn = function (error) {
log.error([targetMid, 'RTCIceCandidate', canId + ':' + candidateType,
'Failed adding ICE candidate ->'], error);
self._handleIceCandidateStats('process_failed', targetMid, canId, candidate, error);
self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESS_ERROR,
targetMid, canId, candidateType, {
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, error);
};
log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Adding ICE candidate.']);
self._handleIceCandidateStats('processing', targetMid, canId, candidate);
self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.PROCESSING,
targetMid, canId, candidateType, {
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, null);
if (!(self._peerConnections[targetMid] &&
self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED &&
self._peerConnections[targetMid].remoteDescription &&
self._peerConnections[targetMid].remoteDescription.sdp &&
self._peerConnections[targetMid].remoteDescription.sdp.indexOf('\r\na=mid:' + candidate.sdpMid + '\r\n') > -1)) {
log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping ICE candidate ' +
'as Peer connection does not exists or is closed']);
self._handleIceCandidateStats('process_failed', targetMid, canId, candidate, 'Peer connection does not exist');
self._trigger('candidateProcessingState', self.CANDIDATE_PROCESSING_STATE.DROPPED,
targetMid, canId, candidateType, {
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, new Error('Failed processing ICE candidate as Peer connection does not exists or is closed.'));
return;
}
try {
self._peerConnections[targetMid].addIceCandidate(candidate, onSuccessCbFn, onErrorCbFn);
} catch (error) {
onErrorCbFn(error);
}
};