rtc-multi-connection
Version:
WebRTC JavaScript library for peer-to-peer applications (screen sharing, audio/video conferencing, file sharing, media streaming etc.)
1,307 lines (1,071 loc) • 211 kB
JavaScript
'use strict';
// Last time updated: 2017-04-29 7:08:10 AM UTC
// _________________________
// RTCMultiConnection v3.4.4 (patched for NPM.)
// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
(function (mod) {
if (typeof module === 'object' && typeof module.exports === 'object') {
// CommonJS.
// Won't work on Node.js as it calls `navigator` object.
module.exports = mod;
} else {
// Traditional frontend style
window.RTCMultiConnection = mod;
}
}) (function(roomid, forceOptions) {
function SocketConnection(connection, connectCallback) {
var parameters = '';
parameters += '?userid=' + connection.userid;
parameters += '&sessionid=' + connection.sessionid;
parameters += '&msgEvent=' + connection.socketMessageEvent;
parameters += '&socketCustomEvent=' + connection.socketCustomEvent;
parameters += '&autoCloseEntireSession=' + !!connection.autoCloseEntireSession;
parameters += '&maxParticipantsAllowed=' + connection.maxParticipantsAllowed;
if (connection.enableScalableBroadcast) {
parameters += '&enableScalableBroadcast=true';
parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2);
}
if (connection.socketCustomParameters) {
parameters += connection.socketCustomParameters;
}
try {
io.sockets = {};
} catch (e) {};
if (!connection.socketURL) {
connection.socketURL = '/';
}
if (connection.socketURL.substr(connection.socketURL.length - 1, 1) != '/') {
// connection.socketURL = 'https://domain.com:9001/';
throw '"socketURL" MUST end with a slash.';
}
if (connection.enableLogs) {
if (connection.socketURL == '/') {
console.info('socket.io is connected at: ', location.origin + '/');
} else {
console.info('socket.io is connected at: ', connection.socketURL);
}
}
try {
connection.socket = io(connection.socketURL + parameters);
} catch (e) {
connection.socket = io.connect(connection.socketURL + parameters, connection.socketOptions);
}
// detect signaling medium
connection.socket.isIO = true;
var mPeer = connection.multiPeersHandler;
connection.socket.on('extra-data-updated', function(remoteUserId, extra) {
if (!connection.peers[remoteUserId]) return;
connection.peers[remoteUserId].extra = extra;
connection.onExtraDataUpdated({
userid: remoteUserId,
extra: extra
});
if (!connection.peersBackup[remoteUserId]) {
connection.peersBackup[remoteUserId] = {
userid: remoteUserId,
extra: {}
};
}
connection.peersBackup[remoteUserId].extra = extra;
});
connection.socket.on(connection.socketMessageEvent, function(message) {
if (message.remoteUserId != connection.userid) return;
if (connection.peers[message.sender] && connection.peers[message.sender].extra != message.message.extra) {
connection.peers[message.sender].extra = message.extra;
connection.onExtraDataUpdated({
userid: message.sender,
extra: message.extra
});
}
if (message.message.streamSyncNeeded && connection.peers[message.sender]) {
var stream = connection.streamEvents[message.message.streamid];
if (!stream || !stream.stream) {
return;
}
var action = message.message.action;
if (action === 'ended' || action === 'inactive' || action === 'stream-removed') {
if (connection.peersBackup[stream.userid]) {
stream.extra = connection.peersBackup[stream.userid].extra;
}
connection.onstreamended(stream);
return;
}
var type = message.message.type != 'both' ? message.message.type : null;
if (typeof stream.stream[action] == 'function') {
stream.stream[action](type);
}
return;
}
if (message.message === 'connectWithAllParticipants') {
if (connection.broadcasters.indexOf(message.sender) === -1) {
connection.broadcasters.push(message.sender);
}
mPeer.onNegotiationNeeded({
allParticipants: connection.getAllParticipants(message.sender)
}, message.sender);
return;
}
if (message.message === 'removeFromBroadcastersList') {
if (connection.broadcasters.indexOf(message.sender) !== -1) {
delete connection.broadcasters[connection.broadcasters.indexOf(message.sender)];
connection.broadcasters = removeNullEntries(connection.broadcasters);
}
return;
}
if (message.message === 'dropPeerConnection') {
connection.deletePeer(message.sender);
return;
}
if (message.message.allParticipants) {
if (message.message.allParticipants.indexOf(message.sender) === -1) {
message.message.allParticipants.push(message.sender);
}
message.message.allParticipants.forEach(function(participant) {
mPeer[!connection.peers[participant] ? 'createNewPeer' : 'renegotiatePeer'](participant, {
localPeerSdpConstraints: {
OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
remotePeerSdpConstraints: {
OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
isOneWay: !!connection.session.oneway || connection.direction === 'one-way',
isDataOnly: isData(connection.session)
});
});
return;
}
if (message.message.newParticipant) {
if (message.message.newParticipant == connection.userid) return;
if (!!connection.peers[message.message.newParticipant]) return;
mPeer.createNewPeer(message.message.newParticipant, message.message.userPreferences || {
localPeerSdpConstraints: {
OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
remotePeerSdpConstraints: {
OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
isOneWay: !!connection.session.oneway || connection.direction === 'one-way',
isDataOnly: isData(connection.session)
});
return;
}
if (message.message.readyForOffer || message.message.addMeAsBroadcaster) {
connection.addNewBroadcaster(message.sender);
}
if (message.message.newParticipationRequest && message.sender !== connection.userid) {
if (connection.peers[message.sender]) {
connection.deletePeer(message.sender);
}
var userPreferences = {
extra: message.extra || {},
localPeerSdpConstraints: message.message.remotePeerSdpConstraints || {
OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
remotePeerSdpConstraints: message.message.localPeerSdpConstraints || {
OfferToReceiveAudio: connection.session.oneway ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.session.oneway ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo
},
isOneWay: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way',
isDataOnly: typeof message.message.isDataOnly !== 'undefined' ? message.message.isDataOnly : isData(connection.session),
dontGetRemoteStream: typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way',
dontAttachLocalStream: !!message.message.dontGetRemoteStream,
connectionDescription: message,
successCallback: function() {
// if its oneway----- todo: THIS SEEMS NOT IMPORTANT.
if (typeof message.message.isOneWay !== 'undefined' ? message.message.isOneWay : !!connection.session.oneway || connection.direction === 'one-way') {
connection.addNewBroadcaster(message.sender, userPreferences);
}
if (!!connection.session.oneway || connection.direction === 'one-way' || isData(connection.session)) {
connection.addNewBroadcaster(message.sender, userPreferences);
}
}
};
connection.onNewParticipant(message.sender, userPreferences);
return;
}
if (message.message.shiftedModerationControl) {
connection.onShiftedModerationControl(message.sender, message.message.broadcasters);
return;
}
if (message.message.changedUUID) {
if (connection.peers[message.message.oldUUID]) {
connection.peers[message.message.newUUID] = connection.peers[message.message.oldUUID];
delete connection.peers[message.message.oldUUID];
}
}
if (message.message.userLeft) {
mPeer.onUserLeft(message.sender);
if (!!message.message.autoCloseEntireSession) {
connection.leave();
}
return;
}
mPeer.addNegotiatedMessage(message.message, message.sender);
});
connection.socket.on('user-left', function(userid) {
onUserLeft(userid);
connection.onUserStatusChanged({
userid: userid,
status: 'offline',
extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {}
});
var eventObject = {
userid: userid,
extra: {}
};
if (connection.peersBackup[eventObject.userid]) {
eventObject.extra = connection.peersBackup[eventObject.userid].extra;
}
connection.onleave(eventObject);
});
var alreadyConnected = false;
connection.socket.resetProps = function() {
alreadyConnected = false;
};
connection.socket.on('connect', function() {
if (alreadyConnected) {
return;
}
alreadyConnected = true;
if (connection.enableLogs) {
console.info('socket.io connection is opened.');
}
setTimeout(function() {
connection.socket.emit('extra-data-updated', connection.extra);
if (connectCallback) {
connectCallback(connection.socket);
}
}, 1000);
});
connection.socket.on('disconnect', function() {
if (connection.enableLogs) {
console.warn('socket.io connection is closed');
}
});
connection.socket.on('join-with-password', function(remoteUserId) {
connection.onJoinWithPassword(remoteUserId);
});
connection.socket.on('invalid-password', function(remoteUserId, oldPassword) {
connection.onInvalidPassword(remoteUserId, oldPassword);
});
connection.socket.on('password-max-tries-over', function(remoteUserId) {
connection.onPasswordMaxTriesOver(remoteUserId);
});
connection.socket.on('user-disconnected', function(remoteUserId) {
if (remoteUserId === connection.userid) {
return;
}
connection.onUserStatusChanged({
userid: remoteUserId,
status: 'offline',
extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra || {} : {}
});
connection.deletePeer(remoteUserId);
});
connection.socket.on('user-connected', function(userid) {
if (userid === connection.userid) {
return;
}
connection.onUserStatusChanged({
userid: userid,
status: 'online',
extra: connection.peers[userid] ? connection.peers[userid].extra || {} : {}
});
});
connection.socket.on('closed-entire-session', function(sessionid, extra) {
connection.leave();
connection.onEntireSessionClosed({
sessionid: sessionid,
userid: sessionid,
extra: extra
});
});
connection.socket.on('userid-already-taken', function(useridAlreadyTaken, yourNewUserId) {
connection.isInitiator = false;
connection.userid = yourNewUserId;
connection.onUserIdAlreadyTaken(useridAlreadyTaken, yourNewUserId);
})
connection.socket.on('logs', function(log) {
if (!connection.enableLogs) return;
console.debug('server-logs', log);
});
connection.socket.on('number-of-broadcast-viewers-updated', function(data) {
connection.onNumberOfBroadcastViewersUpdated(data);
});
connection.socket.on('room-full', function(roomid) {
connection.onRoomFull(roomid);
});
connection.socket.on('become-next-modrator', function(sessionid) {
if (sessionid != connection.sessionid) return;
setTimeout(function() {
connection.open(sessionid);
connection.socket.emit('shift-moderator-control-on-disconnect');
}, 1000);
});
}
function MultiPeers(connection) {
var self = this;
var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach'];
connection.peersBackup = {};
connection.peers = {
getLength: function() {
var numberOfPeers = 0;
for (var peer in this) {
if (skipPeers.indexOf(peer) == -1) {
numberOfPeers++;
}
}
return numberOfPeers;
},
selectFirst: function() {
var firstPeer;
for (var peer in this) {
if (skipPeers.indexOf(peer) == -1) {
firstPeer = this[peer];
}
}
return firstPeer;
},
getAllParticipants: function(sender) {
var allPeers = [];
for (var peer in this) {
if (skipPeers.indexOf(peer) == -1 && peer != sender) {
allPeers.push(peer);
}
}
return allPeers;
},
forEach: function(callbcak) {
this.getAllParticipants().forEach(function(participant) {
callbcak(connection.peers[participant]);
});
},
send: function(data, remoteUserId) {
var that = this;
if (!isNull(data.size) && !isNull(data.type)) {
self.shareFile(data, remoteUserId);
return;
}
if (data.type !== 'text' && !(data instanceof ArrayBuffer) && !(data instanceof DataView)) {
TextSender.send({
text: data,
channel: this,
connection: connection,
remoteUserId: remoteUserId
});
return;
}
if (data.type === 'text') {
data = JSON.stringify(data);
}
if (remoteUserId) {
var remoteUser = connection.peers[remoteUserId];
if (remoteUser) {
if (!remoteUser.channels.length) {
connection.peers[remoteUserId].createDataChannel();
connection.renegotiate(remoteUserId);
setTimeout(function() {
that.send(data, remoteUserId);
}, 3000);
return;
}
remoteUser.channels.forEach(function(channel) {
channel.send(data);
});
return;
}
}
this.getAllParticipants().forEach(function(participant) {
if (!that[participant].channels.length) {
connection.peers[participant].createDataChannel();
connection.renegotiate(participant);
setTimeout(function() {
that[participant].channels.forEach(function(channel) {
channel.send(data);
});
}, 3000);
return;
}
that[participant].channels.forEach(function(channel) {
channel.send(data);
});
});
}
};
this.uuid = connection.userid;
this.getLocalConfig = function(remoteSdp, remoteUserId, userPreferences) {
if (!userPreferences) {
userPreferences = {};
}
return {
streamsToShare: userPreferences.streamsToShare || {},
rtcMultiConnection: connection,
connectionDescription: userPreferences.connectionDescription,
userid: remoteUserId,
localPeerSdpConstraints: userPreferences.localPeerSdpConstraints,
remotePeerSdpConstraints: userPreferences.remotePeerSdpConstraints,
dontGetRemoteStream: !!userPreferences.dontGetRemoteStream,
dontAttachLocalStream: !!userPreferences.dontAttachLocalStream,
renegotiatingPeer: !!userPreferences.renegotiatingPeer,
peerRef: userPreferences.peerRef,
channels: userPreferences.channels || [],
onLocalSdp: function(localSdp) {
self.onNegotiationNeeded(localSdp, remoteUserId);
},
onLocalCandidate: function(localCandidate) {
localCandidate = OnIceCandidateHandler.processCandidates(connection, localCandidate)
if (localCandidate) {
self.onNegotiationNeeded(localCandidate, remoteUserId);
}
},
remoteSdp: remoteSdp,
onDataChannelMessage: function(message) {
if (!connection.fbr && connection.enableFileSharing) initFileBufferReader();
if (typeof message == 'string' || !connection.enableFileSharing) {
self.onDataChannelMessage(message, remoteUserId);
return;
}
var that = this;
if (message instanceof ArrayBuffer || message instanceof DataView) {
connection.fbr.convertToObject(message, function(object) {
that.onDataChannelMessage(object);
});
return;
}
if (message.readyForNextChunk) {
connection.fbr.getNextChunk(message, function(nextChunk, isLastChunk) {
connection.peers[remoteUserId].channels.forEach(function(channel) {
channel.send(nextChunk);
});
}, remoteUserId);
return;
}
if (message.chunkMissing) {
connection.fbr.chunkMissing(message);
return;
}
connection.fbr.addChunk(message, function(promptNextChunk) {
connection.peers[remoteUserId].peer.channel.send(promptNextChunk);
});
},
onDataChannelError: function(error) {
self.onDataChannelError(error, remoteUserId);
},
onDataChannelOpened: function(channel) {
self.onDataChannelOpened(channel, remoteUserId);
},
onDataChannelClosed: function(event) {
self.onDataChannelClosed(event, remoteUserId);
},
onRemoteStream: function(stream) {
if (connection.peers[remoteUserId]) {
connection.peers[remoteUserId].streams.push(stream);
}
if (isPluginRTC && window.PluginRTC) {
var mediaElement = document.createElement('video');
var body = connection.videosContainer;
body.insertBefore(mediaElement, body.firstChild);
setTimeout(function() {
window.PluginRTC.attachMediaStream(mediaElement, stream);
}, 3000);
return;
}
self.onGettingRemoteMedia(stream, remoteUserId);
},
onRemoteStreamRemoved: function(stream) {
self.onRemovingRemoteMedia(stream, remoteUserId);
},
onPeerStateChanged: function(states) {
self.onPeerStateChanged(states);
if (states.iceConnectionState === 'new') {
self.onNegotiationStarted(remoteUserId, states);
}
if (states.iceConnectionState === 'connected') {
self.onNegotiationCompleted(remoteUserId, states);
}
if (states.iceConnectionState.search(/closed|failed/gi) !== -1) {
self.onUserLeft(remoteUserId);
self.disconnectWith(remoteUserId);
}
}
};
};
this.createNewPeer = function(remoteUserId, userPreferences) {
if (connection.maxParticipantsAllowed <= connection.getAllParticipants().length) {
return;
}
userPreferences = userPreferences || {};
if (connection.isInitiator && !!connection.session.audio && connection.session.audio === 'two-way' && !userPreferences.streamsToShare) {
userPreferences.isOneWay = false;
userPreferences.isDataOnly = false;
userPreferences.session = connection.session;
}
if (!userPreferences.isOneWay && !userPreferences.isDataOnly) {
userPreferences.isOneWay = true;
this.onNegotiationNeeded({
enableMedia: true,
userPreferences: userPreferences
}, remoteUserId);
return;
}
userPreferences = connection.setUserPreferences(userPreferences, remoteUserId);
var localConfig = this.getLocalConfig(null, remoteUserId, userPreferences);
connection.peers[remoteUserId] = new PeerInitiator(localConfig);
};
this.createAnsweringPeer = function(remoteSdp, remoteUserId, userPreferences) {
userPreferences = connection.setUserPreferences(userPreferences || {}, remoteUserId);
var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences);
connection.peers[remoteUserId] = new PeerInitiator(localConfig);
};
this.renegotiatePeer = function(remoteUserId, userPreferences, remoteSdp) {
if (!connection.peers[remoteUserId]) {
if (connection.enableLogs) {
console.error('This peer (' + remoteUserId + ') does not exist. Renegotiation skipped.');
}
return;
}
if (!userPreferences) {
userPreferences = {};
}
userPreferences.renegotiatingPeer = true;
userPreferences.peerRef = connection.peers[remoteUserId].peer;
userPreferences.channels = connection.peers[remoteUserId].channels;
var localConfig = this.getLocalConfig(remoteSdp, remoteUserId, userPreferences);
connection.peers[remoteUserId] = new PeerInitiator(localConfig);
};
this.replaceTrack = function(track, remoteUserId, isVideoTrack) {
if (!connection.peers[remoteUserId]) {
throw 'This peer (' + remoteUserId + ') does not exist.';
}
var peer = connection.peers[remoteUserId].peer;
if (!!peer.getSenders && typeof peer.getSenders === 'function' && peer.getSenders().length) {
peer.getSenders().forEach(function(rtpSender) {
if (isVideoTrack && rtpSender.track instanceof VideoStreamTrack) {
connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track;
rtpSender.replaceTrack(track);
}
if (!isVideoTrack && rtpSender.track instanceof AudioStreamTrack) {
connection.peers[remoteUserId].peer.lastAudioTrack = rtpSender.track;
rtpSender.replaceTrack(track);
}
});
return;
}
console.warn('RTPSender.replaceTrack is NOT supported.');
this.renegotiatePeer(remoteUserId);
};
this.onNegotiationNeeded = function(message, remoteUserId) {};
this.addNegotiatedMessage = function(message, remoteUserId) {
if (message.type && message.sdp) {
if (message.type == 'answer') {
if (connection.peers[remoteUserId]) {
connection.peers[remoteUserId].addRemoteSdp(message);
}
}
if (message.type == 'offer') {
if (message.renegotiatingPeer) {
this.renegotiatePeer(remoteUserId, null, message);
} else {
this.createAnsweringPeer(message, remoteUserId);
}
}
if (connection.enableLogs) {
console.log('Remote peer\'s sdp:', message.sdp);
}
return;
}
if (message.candidate) {
if (connection.peers[remoteUserId]) {
connection.peers[remoteUserId].addRemoteCandidate(message);
}
if (connection.enableLogs) {
console.log('Remote peer\'s candidate pairs:', message.candidate);
}
return;
}
if (message.enableMedia) {
connection.session = message.userPreferences.session || connection.session;
if (connection.session.oneway && connection.attachStreams.length) {
connection.attachStreams = [];
}
if (message.userPreferences.isDataOnly && connection.attachStreams.length) {
connection.attachStreams.length = [];
}
var streamsToShare = {};
connection.attachStreams.forEach(function(stream) {
streamsToShare[stream.streamid] = {
isAudio: !!stream.isAudio,
isVideo: !!stream.isVideo,
isScreen: !!stream.isScreen
};
});
message.userPreferences.streamsToShare = streamsToShare;
self.onNegotiationNeeded({
readyForOffer: true,
userPreferences: message.userPreferences
}, remoteUserId);
}
if (message.readyForOffer) {
connection.onReadyForOffer(remoteUserId, message.userPreferences);
}
function cb(stream) {
gumCallback(stream, message, remoteUserId);
}
};
function gumCallback(stream, message, remoteUserId) {
var streamsToShare = {};
connection.attachStreams.forEach(function(stream) {
streamsToShare[stream.streamid] = {
isAudio: !!stream.isAudio,
isVideo: !!stream.isVideo,
isScreen: !!stream.isScreen
};
});
message.userPreferences.streamsToShare = streamsToShare;
self.onNegotiationNeeded({
readyForOffer: true,
userPreferences: message.userPreferences
}, remoteUserId);
}
this.connectNewParticipantWithAllBroadcasters = function(newParticipantId, userPreferences, broadcastersList) {
if (connection.socket.isIO) {
return;
}
broadcastersList = (broadcastersList || '').split('|-,-|');
if (!broadcastersList.length) {
return;
}
var firstBroadcaster;
var remainingBroadcasters = [];
broadcastersList.forEach(function(list) {
list = (list || '').replace(/ /g, '');
if (list.length) {
if (!firstBroadcaster) {
firstBroadcaster = list;
} else {
remainingBroadcasters.push(list);
}
}
});
if (!firstBroadcaster) {
return;
}
self.onNegotiationNeeded({
newParticipant: newParticipantId,
userPreferences: userPreferences || false
}, firstBroadcaster);
if (!remainingBroadcasters.length) {
return;
}
setTimeout(function() {
self.connectNewParticipantWithAllBroadcasters(newParticipantId, userPreferences, remainingBroadcasters.join('|-,-|'));
}, 3 * 1000);
};
this.onGettingRemoteMedia = function(stream, remoteUserId) {};
this.onRemovingRemoteMedia = function(stream, remoteUserId) {};
this.onGettingLocalMedia = function(localStream) {};
this.onLocalMediaError = function(error, constraints) {
connection.onMediaError(error, constraints);
};
function initFileBufferReader() {
connection.fbr = new FileBufferReader();
connection.fbr.onProgress = function(chunk) {
connection.onFileProgress(chunk);
};
connection.fbr.onBegin = function(file) {
connection.onFileStart(file);
};
connection.fbr.onEnd = function(file) {
connection.onFileEnd(file);
};
}
this.shareFile = function(file, remoteUserId) {
if (!connection.enableFileSharing) {
throw '"connection.enableFileSharing" is false.';
}
initFileBufferReader();
connection.fbr.readAsArrayBuffer(file, function(uuid) {
var arrayOfUsers = connection.getAllParticipants();
if (remoteUserId) {
arrayOfUsers = [remoteUserId];
}
arrayOfUsers.forEach(function(participant) {
connection.fbr.getNextChunk(uuid, function(nextChunk) {
connection.peers[participant].channels.forEach(function(channel) {
channel.send(nextChunk);
});
}, participant);
});
}, {
userid: connection.userid,
// extra: connection.extra,
chunkSize: isFirefox ? 15 * 1000 : connection.chunkSize || 0
});
};
if (typeof 'TextReceiver' !== 'undefined') {
var textReceiver = new TextReceiver(connection);
}
this.onDataChannelMessage = function(message, remoteUserId) {
textReceiver.receive(JSON.parse(message), remoteUserId, connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {});
};
this.onDataChannelClosed = function(event, remoteUserId) {
event.userid = remoteUserId;
event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {};
connection.onclose(event);
};
this.onDataChannelError = function(error, remoteUserId) {
error.userid = remoteUserId;
event.extra = connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {};
connection.onerror(error);
};
this.onDataChannelOpened = function(channel, remoteUserId) {
// keep last channel only; we are not expecting parallel/channels channels
if (connection.peers[remoteUserId].channels.length) {
connection.peers[remoteUserId].channels = [channel];
return;
}
connection.peers[remoteUserId].channels.push(channel);
connection.onopen({
userid: remoteUserId,
extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {},
channel: channel
});
};
this.onPeerStateChanged = function(state) {
connection.onPeerStateChanged(state);
};
this.onNegotiationStarted = function(remoteUserId, states) {};
this.onNegotiationCompleted = function(remoteUserId, states) {};
this.getRemoteStreams = function(remoteUserId) {
remoteUserId = remoteUserId || connection.peers.getAllParticipants()[0];
return connection.peers[remoteUserId] ? connection.peers[remoteUserId].streams : [];
};
this.isPluginRTC = connection.isPluginRTC = isPluginRTC;
}
// globals.js
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
var isChrome = !!window.chrome && !isOpera;
var isIE = !!document.documentMode;
var isMobileDevice = !!navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i);
if (typeof cordova !== 'undefined') {
isMobileDevice = true;
isChrome = true;
}
if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) {
isMobileDevice = true;
isChrome = true;
}
var isPluginRTC = !isMobileDevice && (isSafari || isIE);
if (isPluginRTC && typeof URL !== 'undefined') {
URL.createObjectURL = function() {};
}
// detect node-webkit
var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.process.versions['node-webkit']);
var chromeVersion = 50;
var matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (isChrome && matchArray && matchArray[2]) {
chromeVersion = parseInt(matchArray[2], 10);
}
var firefoxVersion = 50;
matchArray = navigator.userAgent.match(/Firefox\/(.*)/);
if (isFirefox && matchArray && matchArray[1]) {
firefoxVersion = parseInt(matchArray[1], 10);
}
function fireEvent(obj, eventName, args) {
if (typeof CustomEvent === 'undefined') {
return;
}
var eventDetail = {
arguments: args,
__exposedProps__: args
};
var event = new CustomEvent(eventName, eventDetail);
obj.dispatchEvent(event);
}
function setHarkEvents(connection, streamEvent) {
if (!connection || !streamEvent) {
throw 'Both arguments are required.';
}
if (!connection.onspeaking || !connection.onsilence) {
return;
}
if (typeof hark === 'undefined') {
throw 'hark.js not found.';
}
hark(streamEvent.stream, {
onspeaking: function() {
connection.onspeaking(streamEvent);
},
onsilence: function() {
connection.onsilence(streamEvent);
},
onvolumechange: function(volume, threshold) {
if (!connection.onvolumechange) {
return;
}
connection.onvolumechange(merge({
volume: volume,
threshold: threshold
}, streamEvent));
}
});
}
function setMuteHandlers(connection, streamEvent) {
if (!streamEvent.stream || !streamEvent.stream || !streamEvent.stream.addEventListener) return;
streamEvent.stream.addEventListener('mute', function(event) {
event = connection.streamEvents[streamEvent.streamid];
event.session = {
audio: event.muteType === 'audio',
video: event.muteType === 'video'
};
connection.onmute(event);
}, false);
streamEvent.stream.addEventListener('unmute', function(event) {
event = connection.streamEvents[streamEvent.streamid];
event.session = {
audio: event.unmuteType === 'audio',
video: event.unmuteType === 'video'
};
connection.onunmute(event);
}, false);
}
function getRandomString() {
if (window.crypto && window.crypto.getRandomValues && navigator.userAgent.indexOf('Safari') === -1) {
var a = window.crypto.getRandomValues(new Uint32Array(3)),
token = '';
for (var i = 0, l = a.length; i < l; i++) {
token += a[i].toString(36);
}
return token;
} else {
return (Math.random() * new Date().getTime()).toString(36).replace(/\./g, '');
}
}
// Get HTMLAudioElement/HTMLVideoElement accordingly
function getRMCMediaElement(stream, callback, connection) {
var isAudioOnly = false;
if (!!stream.getVideoTracks && !stream.getVideoTracks().length) {
isAudioOnly = true;
}
var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video');
if (isPluginRTC && window.PluginRTC) {
connection.videosContainer.insertBefore(mediaElement, connection.videosContainer.firstChild);
setTimeout(function() {
window.PluginRTC.attachMediaStream(mediaElement, stream);
callback(mediaElement);
}, 1000);
return;
}
// "mozSrcObject" is always preferred over "src"!!
mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.URL.createObjectURL(stream);
mediaElement.controls = true;
// http://goo.gl/WZ5nFl
// Firefox don't yet support onended for any stream (remote/local)
if (isFirefox) {
var streamEndedEvent = 'ended';
if ('oninactive' in mediaElement) {
streamEndedEvent = 'inactive';
}
mediaElement.addEventListener(streamEndedEvent, function() {
// fireEvent(stream, streamEndedEvent, stream);
currentUserMediaRequest.remove(stream.idInstance);
if (stream.type === 'local') {
streamEndedEvent = 'ended';
if ('oninactive' in stream) {
streamEndedEvent = 'inactive';
}
StreamsHandler.onSyncNeeded(stream.streamid, streamEndedEvent);
connection.attachStreams.forEach(function(aStream, idx) {
if (stream.streamid === aStream.streamid) {
delete connection.attachStreams[idx];
}
});
var newStreamsArray = [];
connection.attachStreams.forEach(function(aStream) {
if (aStream) {
newStreamsArray.push(aStream);
}
});
connection.attachStreams = newStreamsArray;
var streamEvent = connection.streamEvents[stream.streamid];
if (streamEvent) {
connection.onstreamended(streamEvent);
return;
}
if (this.parentNode) {
this.parentNode.removeChild(this);
}
}
}, false);
}
mediaElement.play();
callback(mediaElement);
}
// if IE
if (!window.addEventListener) {
window.addEventListener = function(el, eventName, eventHandler) {
if (!el.attachEvent) {
return;
}
el.attachEvent('on' + eventName, eventHandler);
};
}
function listenEventHandler(eventName, eventHandler) {
window.removeEventListener(eventName, eventHandler);
window.addEventListener(eventName, eventHandler, false);
}
window.attachEventListener = function(video, type, listener, useCapture) {
video.addEventListener(type, listener, useCapture);
};
function removeNullEntries(array) {
var newArray = [];
array.forEach(function(item) {
if (item) {
newArray.push(item);
}
});
return newArray;
}
function isData(session) {
return !session.audio && !session.video && !session.screen && session.data;
}
function isNull(obj) {
return typeof obj === 'undefined';
}
function isString(obj) {
return typeof obj === 'string';
}
var MediaStream = window.MediaStream;
if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
MediaStream = webkitMediaStream;
}
/*global MediaStream:true */
if (typeof MediaStream !== 'undefined') {
if (!('getVideoTracks' in MediaStream.prototype)) {
MediaStream.prototype.getVideoTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('video') !== -1) {
tracks.push(track);
}
});
return tracks;
};
MediaStream.prototype.getAudioTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('audio') !== -1) {
tracks.push(track);
}
});
return tracks;
};
}
if (!('stop' in MediaStream.prototype)) {
MediaStream.prototype.stop = function() {
this.getAudioTracks().forEach(function(track) {
if (!!track.stop) {
track.stop();
}
});
this.getVideoTracks().forEach(function(track) {
if (!!track.stop) {
track.stop();
}
});
};
}
}
function isAudioPlusTab(connection, audioPlusTab) {
if (connection.session.audio && connection.session.audio === 'two-way') {
return false;
}
if (isFirefox && audioPlusTab !== false) {
return true;
}
if (!isChrome || chromeVersion < 50) return false;
if (typeof audioPlusTab === true) {
return true;
}
if (typeof audioPlusTab === 'undefined' && connection.session.audio && connection.session.screen && !connection.session.video) {
audioPlusTab = true;
return true;
}
return false;
}
function getAudioScreenConstraints(screen_constraints) {
if (isFirefox) {
return true;
}
if (!isChrome) return false;
return {
mandatory: {
chromeMediaSource: screen_constraints.mandatory.chromeMediaSource,
chromeMediaSourceId: screen_constraints.mandatory.chromeMediaSourceId
}
};
}
window.iOSDefaultAudioOutputDevice = window.iOSDefaultAudioOutputDevice || 'speaker'; // earpiece or speaker
// Last time updated: 2017-04-29 7:05:22 AM UTC
// Latest file can be found here: https://cdn.webrtc-experiment.com/DetectRTC.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - github.com/muaz-khan/DetectRTC
// ____________
// DetectRTC.js
// DetectRTC.hasWebcam (has webcam device!)
// DetectRTC.hasMicrophone (has microphone device!)
// DetectRTC.hasSpeakers (has speakers!)
(function() {
'use strict';
var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';
var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node;
if (isNodejs) {
var version = process.versions.node.toString().replace('v', '');
browserFakeUserAgent = 'Nodejs/' + version + ' (NodeOS) AppleWebKit/' + version + ' (KHTML, like Gecko) Nodejs/' + version + ' Nodejs/' + version
}
(function(that) {
if (typeof window !== 'undefined') {
return;
}
if (typeof window === 'undefined' && typeof global !== 'undefined') {
global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
};
/*global window:true */
that.window = global;
} else if (typeof window === 'undefined') {
// window = this;
}
if (typeof document === 'undefined') {
/*global document:true */
that.document = {};
document.createElement = document.captureStream = document.mozCaptureStream = function() {
return {};
};
}
if (typeof location === 'undefined') {
/*global location:true */
that.location = {
protocol: 'file:',
href: '',
hash: ''
};