/**
* These are the list of Peer connection signaling states that Skylink would trigger.
* - Some of the state references the [w3c WebRTC Specification Draft](http://www.w3.org/TR/webrtc/#idl-def-RTCSignalingState).
* @attribute PEER_CONNECTION_STATE
* @type JSON
* @param {String} STABLE <small>Value <code>"stable"</code></small>
* The state when there is no handshaking in progress and when
* handshaking has just started or close.<br>
* This state occurs when Peer connection has just been initialised and after
* <code>HAVE_LOCAL_OFFER</code> or <code>HAVE_REMOTE_OFFER</code>.
* @param {String} HAVE_LOCAL_OFFER <small>Value <code>"have-local-offer"</code></small>
* The state when the local session description <code>"offer"</code> is generated and to be sent.<br>
* This state occurs after <code>STABLE</code> state.
* @param {String} HAVE_REMOTE_OFFER <small>Value <code>"have-remote-offer"</code></small>
* The state when the remote session description <code>"offer"</code> is received.<br>
* At this stage, this indicates that the Peer connection signaling handshaking has been completed, and
* likely would go back to <code>STABLE</code> after local <code>"answer"</code> is received by Peer.
* @param {String} CLOSED <small>Value <code>"closed"</code></small>
* The state when the Peer connection is closed.<br>
* This state occurs when connection with Peer has been closed, usually when Peer leaves the room.
* @readOnly
* @component Peer
* @for Skylink
* @since 0.5.0
*/
Skylink.prototype.PEER_CONNECTION_STATE = {
STABLE: 'stable',
HAVE_LOCAL_OFFER: 'have-local-offer',
HAVE_REMOTE_OFFER: 'have-remote-offer',
CLOSED: 'closed'
};
/**
* These are the types of server Peers that Skylink would connect with.
* - Different server Peers that serves different functionalities.
* - The server Peers functionalities are only available depending on the
* Application Key configuration.
* - Eventually, this list will be populated as there are more server Peer
* functionalities provided by the Skylink platform.
* @attribute SERVER_PEER_TYPE
* @param {String} MCU <small>Value <code>"mcu"</code></small>
* This server Peer is a MCU server connection.
* @type JSON
* @readOnly
* @component Peer
* @for Skylink
* @since 0.6.1
*/
Skylink.prototype.SERVER_PEER_TYPE = {
MCU: 'mcu'
//SIP: 'sip'
};
/**
* Stores the timestamp of the moment when the last Peers connection
* restarts has happened. Used for the restart Peers connection functionality.
* @attribute _lastRestart
* @type Object
* @required
* @private
* @component Peer
* @for Skylink
* @since 0.5.9
*/
Skylink.prototype._lastRestart = null;
/**
* Stores the counter of the number of consecutive
* Peers connection restarts retries.
* @attribute _retryCount
* @type Number
* @required
* @private
* @component Peer
* @for Skylink
* @since 0.5.10
*/
Skylink.prototype._retryCount = 0;
/**
* Stores the list of Peers connection.
* @attribute _peerConnections
* @param {Object} (#peerId) The Peer ID associated to the RTCPeerConnection object.
* @type JSON
* @required
* @private
* @component Peer
* @for Skylink
* @since 0.1.0
*/
Skylink.prototype._peerConnections = {};
/**
* Connects to the Peer.
* @method _addPeer
* @param {String} targetMid The Peer ID to connect to.
* @param {JSON} peerBrowser The Peer platform agent information.
* @param {String} peerBrowser.agent The Peer platform browser or agent name.
* @param {Number} peerBrowser.version The Peer platform browser or agent version.
* @param {Number} peerBrowser.os The Peer platform name.
* @param {Boolean} [toOffer=false] The flag that indicates if the Peer connection
* should be start connection as an offerer or as an answerer.
* @param {Boolean} [restartConn=false] The flag that indicates if the Peer
* connection is part of restart functionality use-case.
* @param {Boolean} [receiveOnly=false] The flag that indicates if the Peer
* connection would send Stream or not (receive only).
* @param {Boolean} [isSS=false] The flag that indicates if the Peer
* connection Stream sent is a screensharing stream or not.
* @private
* @component Peer
* @for Skylink
* @since 0.5.4
*/
Skylink.prototype._addPeer = function(targetMid, peerBrowser, toOffer, restartConn, receiveOnly, isSS) {
var self = this;
if (self._peerConnections[targetMid] && !restartConn) {
log.error([targetMid, null, null, 'Connection to peer has already been made']);
return;
}
log.log([targetMid, null, null, 'Starting the connection to peer. Options provided:'], {
peerBrowser: peerBrowser,
toOffer: toOffer,
receiveOnly: receiveOnly,
enableDataChannel: self._enableDataChannel
});
log.info('Adding peer', isSS);
if (!restartConn) {
self._peerConnections[targetMid] = self._createPeerConnection(targetMid, !!isSS);
}
if (!self._peerConnections[targetMid]) {
log.error([targetMid, null, null, 'Failed creating the connection to peer']);
return;
}
self._peerConnections[targetMid].receiveOnly = !!receiveOnly;
self._peerConnections[targetMid].hasScreen = !!isSS;
if (!receiveOnly) {
self._addLocalMediaStreams(targetMid);
}
// I'm the callee I need to make an offer
if (toOffer) {
if (self._enableDataChannel) {
if (typeof self._dataChannels[targetMid] !== 'object') {
log.error([targetMid, 'RTCDataChannel', null, 'Create offer error as unable to create datachannel ' +
'as datachannels array is undefined'], self._dataChannels[targetMid]);
return;
}
self._dataChannels[targetMid].main =
self._createDataChannel(targetMid, self.DATA_CHANNEL_TYPE.MESSAGING, null, targetMid);
self._peerConnections[targetMid].hasMainChannel = true;
}
self._doOffer(targetMid, peerBrowser);
}
// do a peer connection health check
// let MCU handle this case
if (!self._hasMCU) {
this._startPeerConnectionHealthCheck(targetMid, toOffer);
} else {
log.warn([targetMid, 'PeerConnectionHealth', null, 'Not setting health timer for MCU connection']);
return;
}
};
/**
* Recreates a peer connection.
* This is the fallback restart mechanism for other platforms.
* @method _restartPeerConnection
* @param {String} peerId The Peer ID to recreate the connection with.
* @private
* @component Peer
* @for Skylink
* @since 0.6.6
*/
Skylink.prototype._recreatePeerConnection = function (peerId) {
var self = this;
if (!self._peerConnections[peerId]) {
log.error([peerId, null, null, 'Peer does not have an existing ' +
'connection. Unable to recreate connection']);
return;
}
// get the value of receiveOnly
log.log([peerId, null, null, 'Recreating a peer connection']);
// get the value of receiveOnly
var receiveOnly = self._peerConnections[peerId] ?
!!self._peerConnections[peerId].receiveOnly : false;
var hasScreenSharing = self._peerConnections[peerId] ?
!!self._peerConnections[peerId].hasScreen : false;
// close the peer connection and remove the reference
var iceConnectionStateClosed = false;
var peerConnectionStateClosed = false;
var dataChannelStateClosed = !self._enableDataChannel;
delete self._peerConnectionHealth[peerId];
self._stopPeerConnectionHealthCheck(peerId);
if (self._peerConnections[peerId].signalingState !== 'closed') {
self._peerConnections[peerId].close();
}
if (self._peerConnections[peerId].hasStream) {
self._trigger('streamEnded', peerId, self.getPeerInfo(peerId), false);
}
self._peerConnections[peerId].dataChannelClosed = true;
delete self._peerConnections[peerId];
log.log([peerId, null, null, 'Re-creating peer connection']);
self._peerConnections[peerId] = self._createPeerConnection(peerId, !!hasScreenSharing);
if (self._peerConnections[peerId]){
self._peerConnections[peerId].receiveOnly = receiveOnly;
self._peerConnections[peerId].hasScreen = hasScreenSharing;
}
return self._peerConnections[peerId];
};
/**
* Restarts a Peer connection in a P2P environment.
* This is usually done for replacing the previous Stream attached and restarting
* the connection with a new one, or when the ICE connection has issues
* streaming video/audio stream in the remote Stream which requires
* a refresh in the ICE connection.
* @method _restartPeerConnection
* @param {String} peerId The Peer ID to restart the connection with.
* @param {Boolean} isSelfInitiatedRestart The flag that indicates if the restart action
* was caused by self.
* @param {Boolean} isConnectionRestart The flag that indicates whether the restarting action
* is caused by ICE connection or handshake connection failure. Currently, this feature works the same as
* <code>explict</code> parameter.
* @param {Function} callback The callback fired after the Peer connection has
* been succesfully initiated with a restart. Set this value to <code>null</code> if you
* do not want to pass in any callbacks.
* @param {Boolean} [explict=false] The flag that indicates whether the restart functionality
* is invoked by the application or by Skylink when the ICE connection fails to establish
* a "healthy" connection state. Currently, this feature works the same as
* <code>isConnectionRestart</code> parameter.
* @private
* @component Peer
* @for Skylink
* @since 0.5.8
*/
Skylink.prototype._restartPeerConnection = function (peerId, isSelfInitiatedRestart, isConnectionRestart, callback, explicit) {
var self = this;
if (!self._peerConnections[peerId]) {
log.error([peerId, null, null, 'Peer does not have an existing ' +
'connection. Unable to restart']);
return;
}
delete self._peerConnectionHealth[peerId];
self._stopPeerConnectionHealthCheck(peerId);
var pc = self._peerConnections[peerId];
var agent = (self.getPeerInfo(peerId) || {}).agent || {};
// fallback to older versions for mobile users
if (['Android', 'iOS'].indexOf(agent.name) > -1) {
pc = self._recreatePeerConnection(peerId);
if (!pc) {
var noConnObjError = 'Failed restarting (fallback) with mobile SDKs as peer connection object is not defined';
log.error([peerId, 'RTCPeerConnection', null, noConnObjError], {
localDescription: pc.localDescription,
remoteDescription: pc.remoteDescription
});
if (typeof callback === 'function') {
log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
callback(null, new Error(noConnObjError));
}
return;
}
}
// This is when the state is stable and re-handshaking is possible
// This could be due to previous connection handshaking that is already done
if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
if (self._peerConnections[peerId] && !self._peerConnections[peerId].receiveOnly) {
self._addLocalMediaStreams(peerId);
}
if (isSelfInitiatedRestart){
log.log([peerId, null, null, 'Sending restart message to signaling server']);
var lastRestart = Date.now() || function() { return +new Date(); };
self._sendChannelMessage({
type: self._SIG_MESSAGE_TYPE.RESTART,
mid: self._user.sid,
rid: self._room.id,
agent: window.webrtcDetectedBrowser,
version: window.webrtcDetectedVersion,
os: window.navigator.platform,
userInfo: self.getPeerInfo(),
target: peerId,
isConnectionRestart: !!isConnectionRestart,
lastRestart: lastRestart,
// This will not be used based off the logic in _restartHandler
weight: self._peerPriorityWeight,
receiveOnly: self._peerConnections[peerId].receiveOnly,
enableIceTrickle: self._enableIceTrickle,
enableDataChannel: self._enableDataChannel,
sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
explicit: !!explicit
});
self._trigger('peerRestart', peerId, self.getPeerInfo(peerId), false);
if (typeof callback === 'function') {
log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart callback']);
callback(null, null);
}
} else {
if (typeof callback === 'function') {
log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart callback (receiving peer)']);
callback(null, null);
}
}
// following the previous logic to do checker always
self._startPeerConnectionHealthCheck(peerId, false);
} else {
// Let's check if the signalingState is stable first.
// In another galaxy or universe, where the local description gets dropped..
// In the offerHandler or answerHandler, do the appropriate flags to ignore or drop "extra" descriptions
if (pc.signalingState === self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER ||
pc.signalingState === self.PEER_CONNECTION_STATE.HAVE_REMOTE_OFFER) {
// Checks if the local description is defined first
var hasLocalDescription = pc.localDescription && pc.localDescription.sdp;
// By then it should have at least the local description..
if (hasLocalDescription) {
self._sendChannelMessage({
type: pc.localDescription.type,
sdp: pc.localDescription.sdp,
mid: self._user.sid,
target: peerId,
rid: self._room.id,
restart: true
});
} else {
var noLocalDescriptionError = 'Failed re-sending localDescription as there is ' +
'no localDescription set to connection. There could be a handshaking step error';
log.error([peerId, 'RTCPeerConnection', null, noLocalDescriptionError], {
localDescription: pc.localDescription,
remoteDescription: pc.remoteDescription
});
if (typeof callback === 'function') {
log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
callback(null, new Error(noLocalDescriptionError));
}
}
// It could have connection state closed
} else {
var unableToRestartError = 'Failed restarting as peer connection state is ' + pc.signalingState;
log.warn([peerId, 'RTCPeerConnection', null, unableToRestartError]);
if (typeof callback === 'function') {
log.debug([peerId, 'RTCPeerConnection', null, 'Firing restart failure callback']);
callback(null, new Error(unableToRestartError));
}
}
}
};
/**
* Disconnects the Peer connection and remove object references associated.
* @method _removePeer
* @param {String} peerId The Peer ID to disconnect the connection with.
* @trigger peerLeft
* @private
* @component Peer
* @for Skylink
* @since 0.5.5
*/
Skylink.prototype._removePeer = function(peerId) {
var peerInfo = clone(this.getPeerInfo(peerId)) || {
userData: '',
settings: {},
mediaStatus: {},
agent: {},
room: clone(this._selectedRoom)
};
if (peerId !== 'MCU') {
this._trigger('peerLeft', peerId, peerInfo, false);
} else {
this._hasMCU = false;
log.log([peerId, null, null, 'MCU has stopped listening and left']);
this._trigger('serverPeerLeft', peerId, this.SERVER_PEER_TYPE.MCU);
}
// stop any existing peer health timer
this._stopPeerConnectionHealthCheck(peerId);
// check if health timer exists
if (typeof this._peerConnections[peerId] !== 'undefined') {
// new flag to check if datachannels are all closed
this._peerConnections[peerId].dataChannelClosed = true;
if (this._peerConnections[peerId].signalingState !== 'closed') {
this._peerConnections[peerId].close();
}
if (this._peerConnections[peerId].hasStream) {
this._trigger('streamEnded', peerId, this.getPeerInfo(peerId), false);
}
delete this._peerConnections[peerId];
}
// remove peer informations session
if (typeof this._peerInformations[peerId] !== 'undefined') {
delete this._peerInformations[peerId];
}
if (typeof this._peerConnectionHealth[peerId] !== 'undefined') {
delete this._peerConnectionHealth[peerId];
}
// close datachannel connection
if (this._enableDataChannel) {
this._closeDataChannel(peerId);
}
log.log([peerId, null, null, 'Successfully removed peer']);
};
/**
* Creates a Peer connection. This does not start the handshake connection
* but creates the Peer connection object ready for connection.
* @method _createPeerConnection
* @param {String} targetMid The Peer ID to create the connection object
* with.
* @param {Boolean} [isScreenSharing=false] The flag that indicates if the Peer
* connection Stream sent is a screensharing stream or not.
* @return {Object} The Peer connection object associated with
* the provided ID.
* @private
* @component Peer
* @for Skylink
* @since 0.5.1
*/
Skylink.prototype._createPeerConnection = function(targetMid, isScreenSharing) {
var pc, self = this;
// currently the AdapterJS 0.12.1-2 causes an issue to prevent firefox from
// using .urls feature
var newRTCPeerConnection = window.webkitRTCPeerConnection || window.mozRTCPeerConnection ||
window.RTCPeerConnection;
try {
pc = new newRTCPeerConnection(
self._room.connection.peerConfig,
self._room.connection.peerConstraints);
log.info([targetMid, null, null, 'Created peer connection']);
log.debug([targetMid, null, null, 'Peer connection config:'],
self._room.connection.peerConfig);
log.debug([targetMid, null, null, 'Peer connection constraints:'],
self._room.connection.peerConstraints);
} catch (error) {
log.error([targetMid, null, null, 'Failed creating peer connection:'], error);
return null;
}
// attributes (added on by Temasys)
pc.setOffer = '';
pc.setAnswer = '';
pc.hasStream = false;
pc.hasScreen = !!isScreenSharing;
pc.hasMainChannel = false;
pc.firefoxStreamId = '';
// datachannels
self._dataChannels[targetMid] = {};
// candidates
self._addedCandidates[targetMid] = {
relay: [],
host: [],
srflx: []
};
// callbacks
// standard not implemented: onnegotiationneeded,
pc.ondatachannel = function(event) {
var dc = event.channel || event;
log.debug([targetMid, 'RTCDataChannel', dc.label, 'Received datachannel ->'], dc);
if (self._enableDataChannel) {
var channelType = self.DATA_CHANNEL_TYPE.DATA;
var channelKey = dc.label;
// if peer does not have main channel, the first item is main
if (!pc.hasMainChannel) {
channelType = self.DATA_CHANNEL_TYPE.MESSAGING;
channelKey = 'main';
pc.hasMainChannel = true;
}
self._dataChannels[targetMid][channelKey] =
self._createDataChannel(targetMid, channelType, dc, dc.label);
} else {
log.warn([targetMid, 'RTCDataChannel', dc.label, 'Not adding datachannel as enable datachannel ' +
'is set to false']);
}
};
pc.onaddstream = function(event) {
var stream = event.stream || event;
pc.hasStream = true;
var agent = (self.getPeerInfo(targetMid) || {}).agent || {};
var timeout = 0;
// NOTE: Add timeouts to the firefox stream received because it seems to have some sort of black stream rendering at first
// This may not be advisable but that it seems to work after 1500s. (tried with ICE established but it does not work and getStats)
if (agent.name === 'firefox' && window.webrtcDetectedBrowser !== 'firefox') {
timeout = 1500;
}
setTimeout(function () {
self._onRemoteStreamAdded(targetMid, stream, !!pc.hasScreen);
}, timeout);
};
pc.onicecandidate = function(event) {
log.debug([targetMid, 'RTCIceCandidate', null, 'Ice candidate generated ->'],
event.candidate);
self._onIceCandidate(targetMid, event);
};
pc.oniceconnectionstatechange = function(evt) {
checkIceConnectionState(targetMid, pc.iceConnectionState,
function(iceConnectionState) {
log.debug([targetMid, 'RTCIceConnectionState', null,
'Ice connection state changed ->'], iceConnectionState);
self._trigger('iceConnectionState', iceConnectionState, targetMid);
// clear all peer connection health check
// peer connection is stable. now if there is a waiting check on it
if (iceConnectionState === self.ICE_CONNECTION_STATE.COMPLETED &&
pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
log.debug([targetMid, 'PeerConnectionHealth', null,
'Peer connection with user is stable']);
self._peerConnectionHealth[targetMid] = true;
self._stopPeerConnectionHealthCheck(targetMid);
self._retryCount = 0;
}
if (typeof self._ICEConnectionFailures[targetMid] === 'undefined') {
self._ICEConnectionFailures[targetMid] = 0;
}
if (self._ICEConnectionFailures[targetMid] > 2) {
self._peerIceTrickleDisabled[targetMid] = true;
}
if (iceConnectionState === self.ICE_CONNECTION_STATE.FAILED) {
self._ICEConnectionFailures[targetMid] += 1;
if (self._enableIceTrickle && !self._peerIceTrickleDisabled[targetMid]) {
self._trigger('iceConnectionState',
self.ICE_CONNECTION_STATE.TRICKLE_FAILED, targetMid);
}
// refresh when failed. ignore for MCU case since restart is handled by MCU in this case
if (!self._hasMCU) {
self._restartPeerConnection(targetMid, true, true, null, false);
}
}
/**** SJS-53: Revert of commit ******
// resend if failed
if (iceConnectionState === self.ICE_CONNECTION_STATE.FAILED) {
log.debug([targetMid, 'RTCIceConnectionState', null,
'Ice connection state failed. Re-negotiating connection']);
self._removePeer(targetMid);
self._sendChannelMessage({
type: self._SIG_MESSAGE_TYPE.WELCOME,
mid: self._user.sid,
rid: self._room.id,
agent: window.webrtcDetectedBrowser,
version: window.webrtcDetectedVersion,
userInfo: self.getPeerInfo(),
target: targetMid,
restartNego: true,
hsPriority: -1
});
} *****/
});
};
// pc.onremovestream = function () {
// self._onRemoteStreamRemoved(targetMid);
// };
pc.onsignalingstatechange = function() {
log.debug([targetMid, 'RTCSignalingState', null,
'Peer connection state changed ->'], pc.signalingState);
self._trigger('peerConnectionState', pc.signalingState, targetMid);
// clear all peer connection health check
// peer connection is stable. now if there is a waiting check on it
if ((pc.iceConnectionState === self.ICE_CONNECTION_STATE.COMPLETED ||
pc.iceConnectionState === self.ICE_CONNECTION_STATE.CONNECTED) &&
pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
log.debug([targetMid, 'PeerConnectionHealth', null,
'Peer connection with user is stable']);
self._peerConnectionHealth[targetMid] = true;
self._stopPeerConnectionHealthCheck(targetMid);
self._retryCount = 0;
}
};
pc.onicegatheringstatechange = function() {
log.log([targetMid, 'RTCIceGatheringState', null,
'Ice gathering state changed ->'], pc.iceGatheringState);
self._trigger('candidateGenerationState', pc.iceGatheringState, targetMid);
};
if (window.webrtcDetectedBrowser === 'firefox') {
pc.removeStream = function (stream) {
var senders = pc.getSenders();
for (var s = 0; s < senders.length; s++) {
var tracks = stream.getTracks();
for (var t = 0; t < tracks.length; t++) {
if (tracks[t] === senders[s].track) {
pc.removeTrack(senders[s]);
}
}
}
};
}
return pc;
};
/**
* Refreshes a Peer connection.
* - This feature can be used to refresh a Peer connection when the
* remote Stream received does not stream any audio/video stream.
* - If there are more than 1 refresh during 5 seconds
* or refresh is less than 3 seconds since the last refresh
* initiated by the other peer, it will be aborted.
* - As for MCU connection, the restart mechanism makes the self user
* leave and join the currently connected room again.
* @method refreshConnection
* @param {String|Array} [targetPeerId] The array of targeted Peers connection to refresh
* the connection with.
* @param {Function} [callback] The callback fired after all targeted Peers connection has
* been initiated with refresh or have met with an exception.
* The callback signature is <code>function (error, success)</code>.
* @param {JSON} callback.error The error object received in the callback.
* If received as <code>null</code>, it means that there is no errors.
* @param {Array} callback.error.listOfPeers The list of Peers that the
* refresh connection had been initiated with.
* @param {JSON} callback.error.refreshErrors The list of errors occurred
* based on per Peer basis.
* @param {Object|String} callback.error.refreshErrors.(#peerId) The Peer ID that
* is associated with the error that occurred when refreshing the connection.
* @param {JSON} callback.success The success object received in the callback.
* If received as <code>null</code>, it means that there are errors.
* @param {Array} callback.success.listOfPeers The list of Peers that the
* refresh connection had been initiated with.
* @example
* SkylinkDemo.on("iceConnectionState", function (state, peerId)) {
* if (iceConnectionState === SkylinkDemo.ICE_CONNECTION_STATE.FAILED) {
* // Do a refresh
* SkylinkDemo.refreshConnection(peerId);
* }
* });
* @trigger peerRestart, serverPeerRestart, peerJoined, peerLeft, serverPeerJoined, serverPeerLeft
* @component Peer
* @for Skylink
* @since 0.5.5
*/
Skylink.prototype.refreshConnection = function(targetPeerId, callback) {
var self = this;
var listOfPeers = Object.keys(self._peerConnections);
var listOfPeerRestarts = [];
var error = '';
var listOfPeerRestartErrors = {};
if(Array.isArray(targetPeerId)) {
listOfPeers = targetPeerId;
} else if (typeof targetPeerId === 'string') {
listOfPeers = [targetPeerId];
} else if (typeof targetPeerId === 'function') {
callback = targetPeerId;
}
if (listOfPeers.length === 0) {
error = 'There is currently no peer connections to restart';
log.warn([null, 'PeerConnection', null, error]);
listOfPeerRestartErrors.self = new Error(error);
if (typeof callback === 'function') {
callback({
refreshErrors: listOfPeerRestartErrors,
listOfPeers: listOfPeers
}, null);
}
return;
}
// To fix jshint dont put functions within a loop
var refreshSinglePeerCallback = function (peerId) {
return function (error, success) {
if (listOfPeerRestarts.indexOf(peerId) === -1) {
if (error) {
log.error([peerId, 'RTCPeerConnection', null, 'Failed restarting for peer'], error);
listOfPeerRestartErrors[peerId] = error;
}
listOfPeerRestarts.push(peerId);
}
if (listOfPeerRestarts.length === listOfPeers.length) {
if (typeof callback === 'function') {
log.log([null, 'PeerConnection', null, 'Invoked all peers to restart. Firing callback']);
if (Object.keys(listOfPeerRestartErrors).length > 0) {
callback({
refreshErrors: listOfPeerRestartErrors,
listOfPeers: listOfPeers
}, null);
} else {
callback(null, {
listOfPeers: listOfPeers
});
}
}
}
};
};
var refreshSinglePeer = function(peerId, peerCallback){
if (!self._peerConnections[peerId]) {
error = 'There is currently no existing peer connection made ' +
'with the peer. Unable to restart connection';
log.error([peerId, null, null, error]);
listOfPeerRestartErrors[peerId] = new Error(error);
return;
}
var now = Date.now() || function() { return +new Date(); };
if (now - self.lastRestart < 3000) {
error = 'Last restart was so tight. Aborting.';
log.error([peerId, null, null, error]);
listOfPeerRestartErrors[peerId] = new Error(error);
return;
}
log.log([peerId, 'PeerConnection', null, 'Restarting peer connection']);
// do a hard reset on variable object
self._restartPeerConnection(peerId, true, false, peerCallback, true);
};
var toRefresh = function() {
if(!self._hasMCU) {
var i;
for (i = 0; i < listOfPeers.length; i++) {
var peerId = listOfPeers[i];
if (Object.keys(self._peerConnections).indexOf(peerId) > -1) {
refreshSinglePeer(peerId, refreshSinglePeerCallback(peerId));
} else {
error = 'Peer connection with peer does not exists. Unable to restart';
log.error([peerId, 'PeerConnection', null, error]);
listOfPeerRestartErrors[peerId] = new Error(error);
}
// there's an error to trigger for
if (i === listOfPeers.length - 1 && Object.keys(listOfPeerRestartErrors).length > 0) {
if (typeof callback === 'function') {
callback({
refreshErrors: listOfPeerRestartErrors,
listOfPeers: listOfPeers
}, null);
}
}
}
} else {
self._restartMCUConnection(callback);
}
};
self._throttle(toRefresh,5000)();
};
/**
* Restarts all Peers connection in a MCU connection environment.
* This would require the current user to leave the room and restart all
* current existing Peers connection.
* @method _restartMCUConnection
* @param {Function} [callback] The callback fired after all targeted Peers connection has
* been initiated with refresh or have met with an exception.
* The callback signature is <code>function (error, success)</code>.
* @param {JSON} callback.error The error object received in the callback.
* If received as <code>null</code>, it means that there is no errors.
* @param {Array} callback.error.listOfPeers The list of Peers that the
* refresh connection had been initiated with.
* @param {JSON} callback.error.refreshErrors The list of errors occurred
* based on per Peer basis.
* @param {Object|String} callback.error.refreshErrors.(#peerId) The Peer ID associated
* with the error that occurred when refreshing the connection.
* @param {JSON} callback.success The success object received in the callback.
* If received as <code>null</code>, it means that there are errors.
* @param {Array} callback.success.listOfPeers The list of Peers that the
* refresh connection had been initiated with.
* @private
* @trigger peerRestart, serverPeerRestart, peerJoined, peerLeft, serverPeerJoined
* @component Peer
* @for Skylink
* @since 0.6.1
*/
Skylink.prototype._restartMCUConnection = function(callback) {
var self = this;
log.info([self._user.sid, null, null, 'Restarting with MCU enabled']);
// Save room name
/*var roomName = (self._room.id).substring((self._room.id)
.indexOf('_api_') + 5, (self._room.id).length);*/
var listOfPeers = Object.keys(self._peerConnections);
var listOfPeerRestartErrors = {};
var peerId; // j shint is whinning
var receiveOnly = false;
// for MCU case, these dont matter at all
var lastRestart = Date.now() || function() { return +new Date(); };
var weight = (new Date()).valueOf();
self._trigger('serverPeerRestart', 'MCU', self.SERVER_PEER_TYPE.MCU);
for (var i = 0; i < listOfPeers.length; i++) {
peerId = listOfPeers[i];
if (!self._peerConnections[peerId]) {
var error = 'Peer connection with peer does not exists. Unable to restart';
log.error([peerId, 'PeerConnection', null, error]);
listOfPeerRestartErrors[peerId] = new Error(error);
continue;
}
if (peerId === 'MCU') {
receiveOnly = !!self._peerConnections[peerId].receiveOnly;
}
if (peerId !== 'MCU') {
self._trigger('peerRestart', peerId, self.getPeerInfo(peerId), true);
log.log([peerId, null, null, 'Sending restart message to signaling server']);
self._sendChannelMessage({
type: self._SIG_MESSAGE_TYPE.RESTART,
mid: self._user.sid,
rid: self._room.id,
agent: window.webrtcDetectedBrowser,
version: window.webrtcDetectedVersion,
os: window.navigator.platform,
userInfo: self.getPeerInfo(),
target: peerId, //'MCU',
isConnectionRestart: false,
lastRestart: lastRestart,
weight: self._peerPriorityWeight,
receiveOnly: receiveOnly,
enableIceTrickle: self._enableIceTrickle,
enableDataChannel: self._enableDataChannel,
sessionType: !!self._mediaScreen ? 'screensharing' : 'stream',
explicit: true
});
}
}
// Restart with MCU = peer leaves then rejoins room
var peerJoinedFn = function (peerId, peerInfo, isSelf) {
log.log([null, 'PeerConnection', null, 'Invoked all peers to restart with MCU. Firing callback']);
if (typeof callback === 'function') {
if (Object.keys(listOfPeerRestartErrors).length > 0) {
callback({
refreshErrors: listOfPeerRestartErrors,
listOfPeers: listOfPeers
}, null);
} else {
callback(null, {
listOfPeers: listOfPeers
});
}
}
};
self.once('peerJoined', peerJoinedFn, function (peerId, peerInfo, isSelf) {
return isSelf;
});
self.leaveRoom(false, function (error, success) {
if (error) {
if (typeof callback === 'function') {
for (var i = 0; i < listOfPeers.length; i++) {
listOfPeerRestartErrors[listOfPeers[i]] = error;
}
callback({
refreshErrors: listOfPeerRestartErrors,
listOfPeers: listOfPeers
}, null);
}
} else {
//self._trigger('serverPeerLeft', 'MCU', self.SERVER_PEER_TYPE.MCU);
self.joinRoom(self._selectedRoom);
}
});
};