www.franconian.net/static/kevin-master/assets/js/RTCMultiConnection.js

5857 lines
213 KiB
JavaScript
Raw Normal View History

2021-12-11 23:51:33 +01:00
'use strict';
// Last time updated: 2020-12-21 6:32:02 AM UTC
// _________________________
// RTCMultiConnection v3.7.0
// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
var RTCMultiConnection = function(roomid, forceOptions) {
var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';
(function(that) {
if (!that) {
return;
}
if (typeof window !== 'undefined') {
return;
}
if (typeof global === 'undefined') {
return;
}
global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
};
if (!global.console) {
global.console = {};
}
if (typeof global.console.debug === 'undefined') {
global.console.debug = global.console.info = global.console.error = global.console.log = global.console.log || function() {
console.log(arguments);
};
}
if (typeof document === 'undefined') {
/*global document:true */
that.document = {};
document.createElement = document.captureStream = document.mozCaptureStream = function() {
var obj = {
getContext: function() {
return obj;
},
play: function() {},
pause: function() {},
drawImage: function() {},
toDataURL: function() {
return '';
}
};
return obj;
};
document.addEventListener = document.removeEventListener = that.addEventListener = that.removeEventListener = function() {};
that.HTMLVideoElement = that.HTMLMediaElement = function() {};
}
if (typeof io === 'undefined') {
that.io = function() {
return {
on: function(eventName, callback) {
callback = callback || function() {};
if (eventName === 'connect') {
callback();
}
},
emit: function(eventName, data, callback) {
callback = callback || function() {};
if (eventName === 'open-room' || eventName === 'join-room') {
callback(true, data.sessionid, null);
}
}
};
};
}
if (typeof location === 'undefined') {
/*global location:true */
that.location = {
protocol: 'file:',
href: '',
hash: '',
origin: 'self'
};
}
if (typeof screen === 'undefined') {
/*global screen:true */
that.screen = {
width: 0,
height: 0
};
}
if (typeof URL === 'undefined') {
/*global screen:true */
that.URL = {
createObjectURL: function() {
return '';
},
revokeObjectURL: function() {
return '';
}
};
}
/*global window:true */
that.window = global;
})(typeof global !== 'undefined' ? global : null);
function SocketConnection(connection, connectCallback) {
function isData(session) {
return !session.audio && !session.video && !session.screen && session.data;
}
var parameters = '';
parameters += '?userid=' + connection.userid;
parameters += '&sessionid=' + connection.sessionid;
parameters += '&msgEvent=' + connection.socketMessageEvent;
parameters += '&socketCustomEvent=' + connection.socketCustomEvent;
parameters += '&autoCloseEntireSession=' + !!connection.autoCloseEntireSession;
if (connection.session.broadcast === true) {
parameters += '&oneToMany=true';
}
parameters += '&maxParticipantsAllowed=' + connection.maxParticipantsAllowed;
if (connection.enableScalableBroadcast) {
parameters += '&enableScalableBroadcast=true';
parameters += '&maxRelayLimitPerUser=' + (connection.maxRelayLimitPerUser || 2);
}
parameters += '&extra=' + JSON.stringify(connection.extra || {});
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 url is: ', location.origin + '/');
} else {
console.info('socket.io url is: ', connection.socketURL);
}
}
try {
connection.socket = io(connection.socketURL + parameters);
} catch (e) {
connection.socket = io.connect(connection.socketURL + parameters, connection.socketOptions);
}
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
});
updateExtraBackup(remoteUserId, extra);
});
function updateExtraBackup(remoteUserId, extra) {
if (!connection.peersBackup[remoteUserId]) {
connection.peersBackup[remoteUserId] = {
userid: remoteUserId,
extra: {}
};
}
connection.peersBackup[remoteUserId].extra = extra;
}
function onMessageEvent(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
});
updateExtraBackup(message.sender, 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 === '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) {
if (connection.attachStreams.length) {
connection.waitingForLocalMedia = false;
}
if (connection.waitingForLocalMedia) {
// if someone is waiting to join you
// make sure that we've local media before making a handshake
setTimeout(function() {
onMessageEvent(message);
}, 1);
return;
}
}
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() {}
};
connection.onNewParticipant(message.sender, userPreferences);
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(connection.socketMessageEvent, onMessageEvent);
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);
}, 1000);
if (connectCallback) {
connectCallback(connection.socket);
}
});
connection.socket.on('disconnect', function(event) {
connection.onSocketDisconnect(event);
});
connection.socket.on('error', function(event) {
connection.onSocketError(event);
});
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.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('set-isInitiator-true', function(sessionid) {
if (sessionid != connection.sessionid) return;
connection.isInitiator = true;
});
}
function MultiPeers(connection) {
var self = this;
var skipPeers = ['getAllParticipants', 'getLength', 'selectFirst', 'streams', 'send', 'forEach'];
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(callback) {
this.getAllParticipants().forEach(function(participant) {
callback(connection.peers[participant]);
});
},
send: function(data, remoteUserId) {
var that = this;
if (!isNull(data.size) && !isNull(data.type)) {
if (connection.enableFileSharing) {
self.shareFile(data, remoteUserId);
return;
}
if (typeof data !== 'string') {
data = JSON.stringify(data);
}
}
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);
}
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('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.kind === 'video') {
connection.peers[remoteUserId].peer.lastVideoTrack = rtpSender.track;
rtpSender.replaceTrack(track);
}
if (!isVideoTrack && rtpSender.track.kind === 'audio') {
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.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) {
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: DetectRTC.browser.name === 'Firefox' ? 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 : [];
};
}
'use strict';
// Last Updated On: 2020-08-12 11:18:41 AM UTC
// ________________
// DetectRTC v1.4.1
// Open-Sourced: https://github.com/muaz-khan/DetectRTC
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
(function() {
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 && /*node-process*/ !process.browser;
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 location === 'undefined') {
/*global location:true */
that.location = {
protocol: 'file:',
href: '',
hash: ''
};
}
if (typeof screen === 'undefined') {
/*global screen:true */
that.screen = {
width: 0,
height: 0
};
}
})(typeof global !== 'undefined' ? global : window);
/*global navigator:true */
var navigator = window.navigator;
if (typeof navigator !== 'undefined') {
if (typeof navigator.webkitGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.webkitGetUserMedia;
}
if (typeof navigator.mozGetUserMedia !== 'undefined') {
navigator.getUserMedia = navigator.mozGetUserMedia;
}
} else {
navigator = {
getUserMedia: function() {},
userAgent: browserFakeUserAgent
};
}
var isMobileDevice = !!(/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent || ''));
var isEdge = navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob);
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1 && ('netscape' in window) && / rv:/.test(navigator.userAgent);
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var isChrome = !!window.chrome && !isOpera;
var isIE = typeof document !== 'undefined' && !!document.documentMode && !isEdge;
// this one can also be used:
// https://www.websocket.org/js/stuff.js (DetectBrowser.js)
function getBrowserInfo() {
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var browserName = navigator.appName;
var fullVersion = '' + parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion, 10);
var nameOffset, verOffset, ix;
// In Opera, the true version is after 'Opera' or after 'Version'
if (isOpera) {
browserName = 'Opera';
try {
fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0];
majorVersion = fullVersion.split('.')[0];
} catch (e) {
fullVersion = '0.0.0.0';
majorVersion = 0;
}
}
// In MSIE version <=10, the true version is after 'MSIE' in userAgent
// In IE 11, look for the string after 'rv:'
else if (isIE) {
verOffset = nAgt.indexOf('rv:');
if (verOffset > 0) { //IE 11
fullVersion = nAgt.substring(verOffset + 3);
} else { //IE 10 or earlier
verOffset = nAgt.indexOf('MSIE');
fullVersion = nAgt.substring(verOffset + 5);
}
browserName = 'IE';
}
// In Chrome, the true version is after 'Chrome'
else if (isChrome) {
verOffset = nAgt.indexOf('Chrome');
browserName = 'Chrome';
fullVersion = nAgt.substring(verOffset + 7);
}
// In Safari, the true version is after 'Safari' or after 'Version'
else if (isSafari) {
// both and safri and chrome has same userAgent
if (nAgt.indexOf('CriOS') !== -1) {
verOffset = nAgt.indexOf('CriOS');
browserName = 'Chrome';
fullVersion = nAgt.substring(verOffset + 6);
} else if (nAgt.indexOf('FxiOS') !== -1) {
verOffset = nAgt.indexOf('FxiOS');
browserName = 'Firefox';
fullVersion = nAgt.substring(verOffset + 6);
} else {
verOffset = nAgt.indexOf('Safari');
browserName = 'Safari';
fullVersion = nAgt.substring(verOffset + 7);
if ((verOffset = nAgt.indexOf('Version')) !== -1) {
fullVersion = nAgt.substring(verOffset + 8);
}
if (navigator.userAgent.indexOf('Version/') !== -1) {
fullVersion = navigator.userAgent.split('Version/')[1].split(' ')[0];
}
}
}
// In Firefox, the true version is after 'Firefox'
else if (isFirefox) {
verOffset = nAgt.indexOf('Firefox');
browserName = 'Firefox';
fullVersion = nAgt.substring(verOffset + 8);
}
// In most other browsers, 'name/version' is at the end of userAgent
else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
browserName = nAgt.substring(nameOffset, verOffset);
fullVersion = nAgt.substring(verOffset + 1);
if (browserName.toLowerCase() === browserName.toUpperCase()) {
browserName = navigator.appName;
}
}
if (isEdge) {
browserName = 'Edge';
fullVersion = navigator.userAgent.split('Edge/')[1];
// fullVersion = parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10).toString();
}
// trim the fullVersion string at semicolon/space/bracket if present
if ((ix = fullVersion.search(/[; \)]/)) !== -1) {
fullVersion = fullVersion.substring(0, ix);
}
majorVersion = parseInt('' + fullVersion, 10);
if (isNaN(majorVersion)) {
fullVersion = '' + parseFloat(navigator.appVersion);
majorVersion = parseInt(navigator.appVersion, 10);
}
return {
fullVersion: fullVersion,
version: majorVersion,
name: browserName,
isPrivateBrowsing: false
};
}
// via: https://gist.github.com/cou929/7973956
function retry(isDone, next) {
var currentTrial = 0,
maxRetry = 50,
interval = 10,
isTimeout = false;
var id = window.setInterval(
function() {
if (isDone()) {
window.clearInterval(id);
next(isTimeout);
}
if (currentTrial++ > maxRetry) {
window.clearInterval(id);
isTimeout = true;
next(isTimeout);
}
},
10
);
}
function isIE10OrLater(userAgent) {
var ua = userAgent.toLowerCase();
if (ua.indexOf('msie') === 0 && ua.indexOf('trident') === 0) {
return false;
}
var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua);
if (match && parseInt(match[1], 10) >= 10) {
return true;
}
return false;
}
function detectPrivateMode(callback) {
var isPrivate;
try {
if (window.webkitRequestFileSystem) {
window.webkitRequestFileSystem(
window.TEMPORARY, 1,
function() {
isPrivate = false;
},
function(e) {
isPrivate = true;
}
);
} else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) {
var db;
try {
db = window.indexedDB.open('test');
db.onerror = function() {
return true;
};
} catch (e) {
isPrivate = true;
}
if (typeof isPrivate === 'undefined') {
retry(
function isDone() {
return db.readyState === 'done' ? true : false;
},
function next(isTimeout) {
if (!isTimeout) {
isPrivate = db.result ? false : true;
}
}
);
}
} else if (isIE10OrLater(window.navigator.userAgent)) {
isPrivate = false;
try {
if (!window.indexedDB) {
isPrivate = true;
}
} catch (e) {
isPrivate = true;
}
} else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) {
try {
window.localStorage.setItem('test', 1);
} catch (e) {
isPrivate = true;
}
if (typeof isPrivate === 'undefined') {
isPrivate = false;
window.localStorage.removeItem('test');
}
}
} catch (e) {
isPrivate = false;
}
retry(
function isDone() {
return typeof isPrivate !== 'undefined' ? true : false;
},
function next(isTimeout) {
callback(isPrivate);
}
);
}
var isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i);
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry|BB10/i);
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i);
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i);
},
any: function() {
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
},
getOsName: function() {
var osName = 'Unknown OS';
if (isMobile.Android()) {
osName = 'Android';
}
if (isMobile.BlackBerry()) {
osName = 'BlackBerry';
}
if (isMobile.iOS()) {
osName = 'iOS';
}
if (isMobile.Opera()) {
osName = 'Opera Mini';
}
if (isMobile.Windows()) {
osName = 'Windows';
}
return osName;
}
};
// via: http://jsfiddle.net/ChristianL/AVyND/
function detectDesktopOS() {
var unknown = '-';
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var os = unknown;
var clientStrings = [{
s: 'Chrome OS',
r: /CrOS/
}, {
s: 'Windows 10',
r: /(Windows 10.0|Windows NT 10.0)/
}, {
s: 'Windows 8.1',
r: /(Windows 8.1|Windows NT 6.3)/
}, {
s: 'Windows 8',
r: /(Windows 8|Windows NT 6.2)/
}, {
s: 'Windows 7',
r: /(Windows 7|Windows NT 6.1)/
}, {
s: 'Windows Vista',
r: /Windows NT 6.0/
}, {
s: 'Windows Server 2003',
r: /Windows NT 5.2/
}, {
s: 'Windows XP',
r: /(Windows NT 5.1|Windows XP)/
}, {
s: 'Windows 2000',
r: /(Windows NT 5.0|Windows 2000)/
}, {
s: 'Windows ME',
r: /(Win 9x 4.90|Windows ME)/
}, {
s: 'Windows 98',
r: /(Windows 98|Win98)/
}, {
s: 'Windows 95',
r: /(Windows 95|Win95|Windows_95)/
}, {
s: 'Windows NT 4.0',
r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/
}, {
s: 'Windows CE',
r: /Windows CE/
}, {
s: 'Windows 3.11',
r: /Win16/
}, {
s: 'Android',
r: /Android/
}, {
s: 'Open BSD',
r: /OpenBSD/
}, {
s: 'Sun OS',
r: /SunOS/
}, {
s: 'Linux',
r: /(Linux|X11)/
}, {
s: 'iOS',
r: /(iPhone|iPad|iPod)/
}, {
s: 'Mac OS X',
r: /Mac OS X/
}, {
s: 'Mac OS',
r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/
}, {
s: 'QNX',
r: /QNX/
}, {
s: 'UNIX',
r: /UNIX/
}, {
s: 'BeOS',
r: /BeOS/
}, {
s: 'OS/2',
r: /OS\/2/
}, {
s: 'Search Bot',
r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/
}];
for (var i = 0, cs; cs = clientStrings[i]; i++) {
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}
var osVersion = unknown;
if (/Windows/.test(os)) {
if (/Windows (.*)/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
}
os = 'Windows';
}
switch (os) {
case 'Mac OS X':
if (/Mac OS X (10[\.\_\d]+)/.test(nAgt)) {
osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'Android':
if (/Android ([\.\_\d]+)/.test(nAgt)) {
osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'iOS':
if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) {
osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
if (osVersion && osVersion.length > 3) {
osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
}
}
break;
}
return {
osName: os,
osVersion: osVersion
};
}
var osName = 'Unknown OS';
var osVersion = 'Unknown OS Version';
function getAndroidVersion(ua) {
ua = (ua || navigator.userAgent).toLowerCase();
var match = ua.match(/android\s([0-9\.]*)/);
return match ? match[1] : false;
}
var osInfo = detectDesktopOS();
if (osInfo && osInfo.osName && osInfo.osName != '-') {
osName = osInfo.osName;
osVersion = osInfo.osVersion;
} else if (isMobile.any()) {
osName = isMobile.getOsName();
if (osName == 'Android') {
osVersion = getAndroidVersion();
}
}
var isNodejs = typeof process === 'object' && typeof process.versions === 'object' && process.versions.node;
if (osName === 'Unknown OS' && isNodejs) {
osName = 'Nodejs';
osVersion = process.versions.node.toString().replace('v', '');
}
var isCanvasSupportsStreamCapturing = false;
var isVideoSupportsStreamCapturing = false;
['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) {
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
return;
}
if (!isCanvasSupportsStreamCapturing && item in document.createElement('canvas')) {
isCanvasSupportsStreamCapturing = true;
}
if (!isVideoSupportsStreamCapturing && item in document.createElement('video')) {
isVideoSupportsStreamCapturing = true;
}
});
var regexIpv4Local = /^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/,
regexIpv4 = /([0-9]{1,3}(\.[0-9]{1,3}){3})/,
regexIpv6 = /[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/;
// via: https://github.com/diafygi/webrtc-ips
function DetectLocalIPAddress(callback, stream) {
if (!DetectRTC.isWebRTCSupported) {
return;
}
var isPublic = true,
isIpv4 = true;
getIPs(function(ip) {
if (!ip) {
callback(); // Pass nothing to tell that ICE-gathering-ended
} else if (ip.match(regexIpv4Local)) {
isPublic = false;
callback('Local: ' + ip, isPublic, isIpv4);
} else if (ip.match(regexIpv6)) { //via https://ourcodeworld.com/articles/read/257/how-to-get-the-client-ip-address-with-javascript-only
isIpv4 = false;
callback('Public: ' + ip, isPublic, isIpv4);
} else {
callback('Public: ' + ip, isPublic, isIpv4);
}
}, stream);
}
function getIPs(callback, stream) {
if (typeof document === 'undefined' || typeof document.getElementById !== 'function') {
return;
}
var ipDuplicates = {};
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (!RTCPeerConnection) {
var iframe = document.getElementById('iframe');
if (!iframe) {
return;
}
var win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
}
if (!RTCPeerConnection) {
return;
}
var peerConfig = null;
if (DetectRTC.browser === 'Chrome' && DetectRTC.browser.version < 58) {
// todo: add support for older Opera
peerConfig = {
optional: [{
RtpDataChannels: true
}]
};
}
var servers = {
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
};
var pc = new RTCPeerConnection(servers, peerConfig);
if (stream) {
if (pc.addStream) {
pc.addStream(stream);
} else if (pc.addTrack && stream.getTracks()[0]) {
pc.addTrack(stream.getTracks()[0], stream);
}
}
function handleCandidate(candidate) {
if (!candidate) {
callback(); // Pass nothing to tell that ICE-gathering-ended
return;
}
var match = regexIpv4.exec(candidate);
if (!match) {
return;
}
var ipAddress = match[1];
var isPublic = (candidate.match(regexIpv4Local)),
isIpv4 = true;
if (ipDuplicates[ipAddress] === undefined) {
callback(ipAddress, isPublic, isIpv4);
}
ipDuplicates[ipAddress] = true;
}
// listen for candidate events
pc.onicecandidate = function(event) {
if (event.candidate && event.candidate.candidate) {
handleCandidate(event.candidate.candidate);
} else {
handleCandidate(); // Pass nothing to tell that ICE-gathering-ended
}
};
// create data channel
if (!stream) {
try {
pc.createDataChannel('sctp', {});
} catch (e) {}
}
// create an offer sdp
if (DetectRTC.isPromisesSupported) {
pc.createOffer().then(function(result) {
pc.setLocalDescription(result).then(afterCreateOffer);
});
} else {
pc.createOffer(function(result) {
pc.setLocalDescription(result, afterCreateOffer, function() {});
}, function() {});
}
function afterCreateOffer() {
var lines = pc.localDescription.sdp.split('\n');
lines.forEach(function(line) {
if (line && line.indexOf('a=candidate:') === 0) {
handleCandidate(line);
}
});
}
}
var MediaDevices = [];
var audioInputDevices = [];
var audioOutputDevices = [];
var videoInputDevices = [];
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
// Firefox 38+ seems having support of enumerateDevices
// Thanks @xdumaine/enumerateDevices
navigator.enumerateDevices = function(callback) {
var enumerateDevices = navigator.mediaDevices.enumerateDevices();
if (enumerateDevices && enumerateDevices.then) {
navigator.mediaDevices.enumerateDevices().then(callback).catch(function() {
callback([]);
});
} else {
callback([]);
}
};
}
// Media Devices detection
var canEnumerate = false;
/*global MediaStreamTrack:true */
if (typeof MediaStreamTrack !== 'undefined' && 'getSources' in MediaStreamTrack) {
canEnumerate = true;
} else if (navigator.mediaDevices && !!navigator.mediaDevices.enumerateDevices) {
canEnumerate = true;
}
var hasMicrophone = false;
var hasSpeakers = false;
var hasWebcam = false;
var isWebsiteHasMicrophonePermissions = false;
var isWebsiteHasWebcamPermissions = false;
// http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediadevices
function checkDeviceSupport(callback) {
if (!canEnumerate) {
if (callback) {
callback();
}
return;
}
if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources) {
navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack);
}
if (!navigator.enumerateDevices && navigator.enumerateDevices) {
navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator);
}
if (!navigator.enumerateDevices) {
if (callback) {
callback();
}
return;
}
MediaDevices = [];
audioInputDevices = [];
audioOutputDevices = [];
videoInputDevices = [];
hasMicrophone = false;
hasSpeakers = false;
hasWebcam = false;
isWebsiteHasMicrophonePermissions = false;
isWebsiteHasWebcamPermissions = false;
// to prevent duplication
var alreadyUsedDevices = {};
navigator.enumerateDevices(function(devices) {
MediaDevices = [];
audioInputDevices = [];
audioOutputDevices = [];
videoInputDevices = [];
devices.forEach(function(_device) {
var device = {};
for (var d in _device) {
try {
if (typeof _device[d] !== 'function') {
device[d] = _device[d];
}
} catch (e) {}
}
if (alreadyUsedDevices[device.deviceId + device.label + device.kind]) {
return;
}
// if it is MediaStreamTrack.getSources
if (device.kind === 'audio') {
device.kind = 'audioinput';
}
if (device.kind === 'video') {
device.kind = 'videoinput';
}
if (!device.deviceId) {
device.deviceId = device.id;
}
if (!device.id) {
device.id = device.deviceId;
}
if (!device.label) {
device.isCustomLabel = true;
if (device.kind === 'videoinput') {
device.label = 'Camera ' + (videoInputDevices.length + 1);
} else if (device.kind === 'audioinput') {
device.label = 'Microphone ' + (audioInputDevices.length + 1);
} else if (device.kind === 'audiooutput') {
device.label = 'Speaker ' + (audioOutputDevices.length + 1);
} else {
device.label = 'Please invoke getUserMedia once.';
}
if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !/^(https:|chrome-extension:)$/g.test(location.protocol || '')) {
if (typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1) {
device.label = 'HTTPs is required to get label of this ' + device.kind + ' device.';
}
}
} else {
// Firefox on Android still returns empty label
if (device.kind === 'videoinput' && !isWebsiteHasWebcamPermissions) {
isWebsiteHasWebcamPermissions = true;
}
if (device.kind === 'audioinput' && !isWebsiteHasMicrophonePermissions) {
isWebsiteHasMicrophonePermissions = true;
}
}
if (device.kind === 'audioinput') {
hasMicrophone = true;
if (audioInputDevices.indexOf(device) === -1) {
audioInputDevices.push(device);
}
}
if (device.kind === 'audiooutput') {
hasSpeakers = true;
if (audioOutputDevices.indexOf(device) === -1) {
audioOutputDevices.push(device);
}
}
if (device.kind === 'videoinput') {
hasWebcam = true;
if (videoInputDevices.indexOf(device) === -1) {
videoInputDevices.push(device);
}
}
// there is no 'videoouput' in the spec.
MediaDevices.push(device);
alreadyUsedDevices[device.deviceId + device.label + device.kind] = device;
});
if (typeof DetectRTC !== 'undefined') {
// to sync latest outputs
DetectRTC.MediaDevices = MediaDevices;
DetectRTC.hasMicrophone = hasMicrophone;
DetectRTC.hasSpeakers = hasSpeakers;
DetectRTC.hasWebcam = hasWebcam;
DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions;
DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions;
DetectRTC.audioInputDevices = audioInputDevices;
DetectRTC.audioOutputDevices = audioOutputDevices;
DetectRTC.videoInputDevices = videoInputDevices;
}
if (callback) {
callback();
}
});
}
var DetectRTC = window.DetectRTC || {};
// ----------
// DetectRTC.browser.name || DetectRTC.browser.version || DetectRTC.browser.fullVersion
DetectRTC.browser = getBrowserInfo();
detectPrivateMode(function(isPrivateBrowsing) {
DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing;
});
// DetectRTC.isChrome || DetectRTC.isFirefox || DetectRTC.isEdge
DetectRTC.browser['is' + DetectRTC.browser.name] = true;
// -----------
DetectRTC.osName = osName;
DetectRTC.osVersion = osVersion;
var isNodeWebkit = typeof process === 'object' && typeof process.versions === 'object' && process.versions['node-webkit'];
// --------- Detect if system supports WebRTC 1.0 or WebRTC 1.1.
var isWebRTCSupported = false;
['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceGatherer'].forEach(function(item) {
if (isWebRTCSupported) {
return;
}
if (item in window) {
isWebRTCSupported = true;
}
});
DetectRTC.isWebRTCSupported = isWebRTCSupported;
//-------
DetectRTC.isORTCSupported = typeof RTCIceGatherer !== 'undefined';
// --------- Detect if system supports screen capturing API
var isScreenCapturingSupported = false;
if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35) {
isScreenCapturingSupported = true;
} else if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34) {
isScreenCapturingSupported = true;
} else if (DetectRTC.browser.isEdge && DetectRTC.browser.version >= 17) {
isScreenCapturingSupported = true;
} else if (DetectRTC.osName === 'Android' && DetectRTC.browser.isChrome) {
isScreenCapturingSupported = true;
}
if (!!navigator.getDisplayMedia || (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia)) {
isScreenCapturingSupported = true;
}
if (!/^(https:|chrome-extension:)$/g.test(location.protocol || '')) {
var isNonLocalHost = typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1;
if (isNonLocalHost && (DetectRTC.browser.isChrome || DetectRTC.browser.isEdge || DetectRTC.browser.isOpera)) {
isScreenCapturingSupported = false;
} else if (DetectRTC.browser.isFirefox) {
isScreenCapturingSupported = false;
}
}
DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported;
// --------- Detect if WebAudio API are supported
var webAudio = {
isSupported: false,
isCreateMediaStreamSourceSupported: false
};
['AudioContext', 'webkitAudioContext', 'mozAudioContext', 'msAudioContext'].forEach(function(item) {
if (webAudio.isSupported) {
return;
}
if (item in window) {
webAudio.isSupported = true;
if (window[item] && 'createMediaStreamSource' in window[item].prototype) {
webAudio.isCreateMediaStreamSourceSupported = true;
}
}
});
DetectRTC.isAudioContextSupported = webAudio.isSupported;
DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported;
// ---------- Detect if SCTP/RTP channels are supported.
var isRtpDataChannelsSupported = false;
if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 31) {
isRtpDataChannelsSupported = true;
}
DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported;
var isSCTPSupportd = false;
if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28) {
isSCTPSupportd = true;
} else if (DetectRTC.browser.isChrome && DetectRTC.browser.version > 25) {
isSCTPSupportd = true;
} else if (DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11) {
isSCTPSupportd = true;
}
DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd;
// ---------
DetectRTC.isMobileDevice = isMobileDevice; // "isMobileDevice" boolean is defined in "getBrowserInfo.js"
// ------
var isGetUserMediaSupported = false;
if (navigator.getUserMedia) {
isGetUserMediaSupported = true;
} else if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
isGetUserMediaSupported = true;
}
if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !/^(https:|chrome-extension:)$/g.test(location.protocol || '')) {
if (typeof document !== 'undefined' && typeof document.domain === 'string' && document.domain.search && document.domain.search(/localhost|127.0./g) === -1) {
isGetUserMediaSupported = 'Requires HTTPs';
}
}
if (DetectRTC.osName === 'Nodejs') {
isGetUserMediaSupported = false;
}
DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported;
var displayResolution = '';
if (screen.width) {
var width = (screen.width) ? screen.width : '';
var height = (screen.height) ? screen.height : '';
displayResolution += '' + width + ' x ' + height;
}
DetectRTC.displayResolution = displayResolution;
function getAspectRatio(w, h) {
function gcd(a, b) {
return (b == 0) ? a : gcd(b, a % b);
}
var r = gcd(w, h);
return (w / r) / (h / r);
}
DetectRTC.displayAspectRatio = getAspectRatio(screen.width, screen.height).toFixed(2);
// ----------
DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing;
DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing;
if (DetectRTC.browser.name == 'Chrome' && DetectRTC.browser.version >= 53) {
if (!DetectRTC.isCanvasSupportsStreamCapturing) {
DetectRTC.isCanvasSupportsStreamCapturing = 'Requires chrome flag: enable-experimental-web-platform-features';
}
if (!DetectRTC.isVideoSupportsStreamCapturing) {
DetectRTC.isVideoSupportsStreamCapturing = 'Requires chrome flag: enable-experimental-web-platform-features';
}
}
// ------
DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress;
DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING;
DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported;
if (DetectRTC.osName === 'Nodejs') {
DetectRTC.isWebSocketsSupported = true;
DetectRTC.isWebSocketsBlocked = false;
}
DetectRTC.checkWebSocketsSupport = function(callback) {
callback = callback || function() {};
try {
var starttime;
var websocket = new WebSocket('wss://echo.websocket.org:443/');
websocket.onopen = function() {
DetectRTC.isWebSocketsBlocked = false;
starttime = (new Date).getTime();
websocket.send('ping');
};
websocket.onmessage = function() {
DetectRTC.WebsocketLatency = (new Date).getTime() - starttime + 'ms';
callback();
websocket.close();
websocket = null;
};
websocket.onerror = function() {
DetectRTC.isWebSocketsBlocked = true;
callback();
};
} catch (e) {
DetectRTC.isWebSocketsBlocked = true;
callback();
}
};
// -------
DetectRTC.load = function(callback) {
callback = callback || function() {};
checkDeviceSupport(callback);
};
// check for microphone/camera support!
if (typeof checkDeviceSupport === 'function') {
// checkDeviceSupport();
}
if (typeof MediaDevices !== 'undefined') {
DetectRTC.MediaDevices = MediaDevices;
} else {
DetectRTC.MediaDevices = [];
}
DetectRTC.hasMicrophone = hasMicrophone;
DetectRTC.hasSpeakers = hasSpeakers;
DetectRTC.hasWebcam = hasWebcam;
DetectRTC.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions;
DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions;
DetectRTC.audioInputDevices = audioInputDevices;
DetectRTC.audioOutputDevices = audioOutputDevices;
DetectRTC.videoInputDevices = videoInputDevices;
// ------
var isSetSinkIdSupported = false;
if (typeof document !== 'undefined' && typeof document.createElement === 'function' && 'setSinkId' in document.createElement('video')) {
isSetSinkIdSupported = true;
}
DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported;
// -----
var isRTPSenderReplaceTracksSupported = false;
if (DetectRTC.browser.isFirefox && typeof mozRTCPeerConnection !== 'undefined' /*&& DetectRTC.browser.version > 39*/ ) {
/*global mozRTCPeerConnection:true */
if ('getSenders' in mozRTCPeerConnection.prototype) {
isRTPSenderReplaceTracksSupported = true;
}
} else if (DetectRTC.browser.isChrome && typeof webkitRTCPeerConnection !== 'undefined') {
/*global webkitRTCPeerConnection:true */
if ('getSenders' in webkitRTCPeerConnection.prototype) {
isRTPSenderReplaceTracksSupported = true;
}
}
DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported;
//------
var isRemoteStreamProcessingSupported = false;
if (DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38) {
isRemoteStreamProcessingSupported = true;
}
DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported;
//-------
var isApplyConstraintsSupported = false;
/*global MediaStreamTrack:true */
if (typeof MediaStreamTrack !== 'undefined' && 'applyConstraints' in MediaStreamTrack.prototype) {
isApplyConstraintsSupported = true;
}
DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported;
//-------
var isMultiMonitorScreenCapturingSupported = false;
if (DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43) {
// version 43 merely supports platforms for multi-monitors
// version 44 will support exact multi-monitor selection i.e. you can select any monitor for screen capturing.
isMultiMonitorScreenCapturingSupported = true;
}
DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported;
DetectRTC.isPromisesSupported = !!('Promise' in window);
// version is generated by "grunt"
DetectRTC.version = '1.4.1';
if (typeof DetectRTC === 'undefined') {
window.DetectRTC = {};
}
var MediaStream = window.MediaStream;
if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
MediaStream = webkitMediaStream;
}
if (typeof MediaStream !== 'undefined' && typeof MediaStream === 'function') {
DetectRTC.MediaStream = Object.keys(MediaStream.prototype);
} else DetectRTC.MediaStream = false;
if (typeof MediaStreamTrack !== 'undefined') {
DetectRTC.MediaStreamTrack = Object.keys(MediaStreamTrack.prototype);
} else DetectRTC.MediaStreamTrack = false;
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (typeof RTCPeerConnection !== 'undefined') {
DetectRTC.RTCPeerConnection = Object.keys(RTCPeerConnection.prototype);
} else DetectRTC.RTCPeerConnection = false;
window.DetectRTC = DetectRTC;
if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
module.exports = DetectRTC;
}
if (typeof define === 'function' && define.amd) {
define('DetectRTC', [], function() {
return DetectRTC;
});
}
})();
// globals.js
if (typeof cordova !== 'undefined') {
DetectRTC.isMobileDevice = true;
DetectRTC.browser.name = 'Chrome';
}
if (navigator && navigator.userAgent && navigator.userAgent.indexOf('Crosswalk') !== -1) {
DetectRTC.isMobileDevice = true;
DetectRTC.browser.name = 'Chrome';
}
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 (!streamEvent.stream || !getTracks(streamEvent.stream, 'audio').length) return;
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
// todo: add API documentation for connection.autoCreateMediaElement
function getRMCMediaElement(stream, callback, connection) {
if (!connection.autoCreateMediaElement) {
callback({});
return;
}
var isAudioOnly = false;
if (!getTracks(stream, 'video').length && !stream.isVideo && !stream.isScreen) {
isAudioOnly = true;
}
if (DetectRTC.browser.name === 'Firefox') {
if (connection.session.video || connection.session.screen) {
isAudioOnly = false;
}
}
var mediaElement = document.createElement(isAudioOnly ? 'audio' : 'video');
mediaElement.srcObject = stream;
mediaElement.setAttribute('autoplay', true);
mediaElement.setAttribute('playsinline', true);
mediaElement.setAttribute('controls', true);
mediaElement.setAttribute('muted', false);
mediaElement.setAttribute('volume', 1);
// http://goo.gl/WZ5nFl
// Firefox don't yet support onended for any stream (remote/local)
if (DetectRTC.browser.name === 'Firefox') {
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);
}
var played = mediaElement.play();
if (typeof played !== 'undefined') {
var cbFired = false;
setTimeout(function() {
if (!cbFired) {
cbFired = true;
callback(mediaElement);
}
}, 1000);
played.then(function() {
if (cbFired) return;
cbFired = true;
callback(mediaElement);
}).catch(function(error) {
if (cbFired) return;
cbFired = true;
callback(mediaElement);
});
} else {
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 (!('stop' in MediaStream.prototype)) {
MediaStream.prototype.stop = function() {
this.getTracks().forEach(function(track) {
track.stop();
});
};
}
}
function isAudioPlusTab(connection, audioPlusTab) {
if (connection.session.audio && connection.session.audio === 'two-way') {
return false;
}
if (DetectRTC.browser.name === 'Firefox' && audioPlusTab !== false) {
return true;
}
if (DetectRTC.browser.name !== 'Chrome' || DetectRTC.browser.version < 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 (DetectRTC.browser.name === 'Firefox') {
return true;
}
if (DetectRTC.browser.name !== 'Chrome') return false;
return {
mandatory: {
chromeMediaSource: screen_constraints.mandatory.chromeMediaSource,
chromeMediaSourceId: screen_constraints.mandatory.chromeMediaSourceId
}
};
}
window.iOSDefaultAudioOutputDevice = window.iOSDefaultAudioOutputDevice || 'speaker'; // earpiece or speaker
function getTracks(stream, kind) {
if (!stream || !stream.getTracks) {
return [];
}
return stream.getTracks().filter(function(t) {
return t.kind === (kind || 'audio');
});
}
function isUnifiedPlanSupportedDefault() {
var canAddTransceiver = false;
try {
if (typeof RTCRtpTransceiver === 'undefined') return false;
if (!('currentDirection' in RTCRtpTransceiver.prototype)) return false;
var tempPc = new RTCPeerConnection();
try {
tempPc.addTransceiver('audio');
canAddTransceiver = true;
} catch (e) {}
tempPc.close();
} catch (e) {
canAddTransceiver = false;
}
return canAddTransceiver && isUnifiedPlanSuppored();
}
function isUnifiedPlanSuppored() {
var isUnifiedPlanSupported = false;
try {
var pc = new RTCPeerConnection({
sdpSemantics: 'unified-plan'
});
try {
var config = pc.getConfiguration();
if (config.sdpSemantics == 'unified-plan')
isUnifiedPlanSupported = true;
else if (config.sdpSemantics == 'plan-b')
isUnifiedPlanSupported = false;
else
isUnifiedPlanSupported = false;
} catch (e) {
isUnifiedPlanSupported = false;
}
} catch (e) {
isUnifiedPlanSupported = false;
}
return isUnifiedPlanSupported;
}
// ios-hacks.js
function setCordovaAPIs() {
// if (DetectRTC.osName !== 'iOS') return;
if (typeof cordova === 'undefined' || typeof cordova.plugins === 'undefined' || typeof cordova.plugins.iosrtc === 'undefined') return;
var iosrtc = cordova.plugins.iosrtc;
window.webkitRTCPeerConnection = iosrtc.RTCPeerConnection;
window.RTCSessionDescription = iosrtc.RTCSessionDescription;
window.RTCIceCandidate = iosrtc.RTCIceCandidate;
window.MediaStream = iosrtc.MediaStream;
window.MediaStreamTrack = iosrtc.MediaStreamTrack;
navigator.getUserMedia = navigator.webkitGetUserMedia = iosrtc.getUserMedia;
iosrtc.debug.enable('iosrtc*');
if (typeof iosrtc.selectAudioOutput == 'function') {
iosrtc.selectAudioOutput(window.iOSDefaultAudioOutputDevice || 'speaker'); // earpiece or speaker
}
iosrtc.registerGlobals();
}
document.addEventListener('deviceready', setCordovaAPIs, false);
setCordovaAPIs();
// RTCPeerConnection.js
var defaults = {};
function setSdpConstraints(config) {
var sdpConstraints = {
OfferToReceiveAudio: !!config.OfferToReceiveAudio,
OfferToReceiveVideo: !!config.OfferToReceiveVideo
};
return sdpConstraints;
}
var RTCPeerConnection;
if (typeof window.RTCPeerConnection !== 'undefined') {
RTCPeerConnection = window.RTCPeerConnection;
} else if (typeof mozRTCPeerConnection !== 'undefined') {
RTCPeerConnection = mozRTCPeerConnection;
} else if (typeof webkitRTCPeerConnection !== 'undefined') {
RTCPeerConnection = webkitRTCPeerConnection;
}
var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
var RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
var MediaStreamTrack = window.MediaStreamTrack;
function PeerInitiator(config) {
if (typeof window.RTCPeerConnection !== 'undefined') {
RTCPeerConnection = window.RTCPeerConnection;
} else if (typeof mozRTCPeerConnection !== 'undefined') {
RTCPeerConnection = mozRTCPeerConnection;
} else if (typeof webkitRTCPeerConnection !== 'undefined') {
RTCPeerConnection = webkitRTCPeerConnection;
}
RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
MediaStreamTrack = window.MediaStreamTrack;
if (!RTCPeerConnection) {
throw 'WebRTC 1.0 (RTCPeerConnection) API are NOT available in this browser.';
}
var connection = config.rtcMultiConnection;
this.extra = config.remoteSdp ? config.remoteSdp.extra : connection.extra;
this.userid = config.userid;
this.streams = [];
this.channels = config.channels || [];
this.connectionDescription = config.connectionDescription;
this.addStream = function(session) {
connection.addStream(session, self.userid);
};
this.removeStream = function(streamid) {
connection.removeStream(streamid, self.userid);
};
var self = this;
if (config.remoteSdp) {
this.connectionDescription = config.remoteSdp.connectionDescription;
}
var allRemoteStreams = {};
defaults.sdpConstraints = setSdpConstraints({
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
});
var peer;
var renegotiatingPeer = !!config.renegotiatingPeer;
if (config.remoteSdp) {
renegotiatingPeer = !!config.remoteSdp.renegotiatingPeer;
}
var localStreams = [];
connection.attachStreams.forEach(function(stream) {
if (!!stream) {
localStreams.push(stream);
}
});
if (!renegotiatingPeer) {
var iceTransports = 'all';
if (connection.candidates.turn || connection.candidates.relay) {
if (!connection.candidates.stun && !connection.candidates.reflexive && !connection.candidates.host) {
iceTransports = 'relay';
}
}
try {
// ref: developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration
var params = {
iceServers: connection.iceServers,
iceTransportPolicy: connection.iceTransportPolicy || iceTransports
};
if (typeof connection.iceCandidatePoolSize !== 'undefined') {
params.iceCandidatePoolSize = connection.iceCandidatePoolSize;
}
if (typeof connection.bundlePolicy !== 'undefined') {
params.bundlePolicy = connection.bundlePolicy;
}
if (typeof connection.rtcpMuxPolicy !== 'undefined') {
params.rtcpMuxPolicy = connection.rtcpMuxPolicy;
}
if (!!connection.sdpSemantics) {
params.sdpSemantics = connection.sdpSemantics || 'unified-plan';
}
if (!connection.iceServers || !connection.iceServers.length) {
params = null;
connection.optionalArgument = null;
}
peer = new RTCPeerConnection(params, connection.optionalArgument);
} catch (e) {
try {
var params = {
iceServers: connection.iceServers
};
peer = new RTCPeerConnection(params);
} catch (e) {
peer = new RTCPeerConnection();
}
}
} else {
peer = config.peerRef;
}
if (!peer.getRemoteStreams && peer.getReceivers) {
peer.getRemoteStreams = function() {
var stream = new MediaStream();
peer.getReceivers().forEach(function(receiver) {
stream.addTrack(receiver.track);
});
return [stream];
};
}
if (!peer.getLocalStreams && peer.getSenders) {
peer.getLocalStreams = function() {
var stream = new MediaStream();
peer.getSenders().forEach(function(sender) {
stream.addTrack(sender.track);
});
return [stream];
};
}
peer.onicecandidate = function(event) {
if (!event.candidate) {
if (!connection.trickleIce) {
var localSdp = peer.localDescription;
config.onLocalSdp({
type: localSdp.type,
sdp: localSdp.sdp,
remotePeerSdpConstraints: config.remotePeerSdpConstraints || false,
renegotiatingPeer: !!config.renegotiatingPeer || false,
connectionDescription: self.connectionDescription,
dontGetRemoteStream: !!config.dontGetRemoteStream,
extra: connection ? connection.extra : {},
streamsToShare: streamsToShare
});
}
return;
}
if (!connection.trickleIce) return;
config.onLocalCandidate({
candidate: event.candidate.candidate,
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex
});
};
localStreams.forEach(function(localStream) {
if (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.dontGetRemoteStream) {
return;
}
if (config.dontAttachLocalStream) {
return;
}
localStream = connection.beforeAddingStream(localStream, self);
if (!localStream) return;
peer.getLocalStreams().forEach(function(stream) {
if (localStream && stream.id == localStream.id) {
localStream = null;
}
});
if (localStream && localStream.getTracks) {
localStream.getTracks().forEach(function(track) {
try {
// last parameter is redundant for unified-plan
// starting from chrome version 72
peer.addTrack(track, localStream);
} catch (e) {}
});
}
});
peer.oniceconnectionstatechange = peer.onsignalingstatechange = function() {
var extra = self.extra;
if (connection.peers[self.userid]) {
extra = connection.peers[self.userid].extra || extra;
}
if (!peer) {
return;
}
config.onPeerStateChanged({
iceConnectionState: peer.iceConnectionState,
iceGatheringState: peer.iceGatheringState,
signalingState: peer.signalingState,
extra: extra,
userid: self.userid
});
if (peer && peer.iceConnectionState && peer.iceConnectionState.search(/closed|failed/gi) !== -1 && self.streams instanceof Array) {
self.streams.forEach(function(stream) {
var streamEvent = connection.streamEvents[stream.id] || {
streamid: stream.id,
stream: stream,
type: 'remote'
};
connection.onstreamended(streamEvent);
});
}
};
var sdpConstraints = {
OfferToReceiveAudio: !!localStreams.length,
OfferToReceiveVideo: !!localStreams.length
};
if (config.localPeerSdpConstraints) sdpConstraints = config.localPeerSdpConstraints;
defaults.sdpConstraints = setSdpConstraints(sdpConstraints);
var streamObject;
var dontDuplicate = {};
peer.ontrack = function(event) {
if (!event || event.type !== 'track') return;
event.stream = event.streams[event.streams.length - 1];
if (!event.stream.id) {
event.stream.id = event.track.id;
}
if (dontDuplicate[event.stream.id] && DetectRTC.browser.name !== 'Safari') {
if (event.track) {
event.track.onended = function() { // event.track.onmute =
peer && peer.onremovestream(event);
};
}
return;
}
dontDuplicate[event.stream.id] = event.stream.id;
var streamsToShare = {};
if (config.remoteSdp && config.remoteSdp.streamsToShare) {
streamsToShare = config.remoteSdp.streamsToShare;
} else if (config.streamsToShare) {
streamsToShare = config.streamsToShare;
}
var streamToShare = streamsToShare[event.stream.id];
if (streamToShare) {
event.stream.isAudio = streamToShare.isAudio;
event.stream.isVideo = streamToShare.isVideo;
event.stream.isScreen = streamToShare.isScreen;
} else {
event.stream.isVideo = !!getTracks(event.stream, 'video').length;
event.stream.isAudio = !event.stream.isVideo;
event.stream.isScreen = false;
}
event.stream.streamid = event.stream.id;
allRemoteStreams[event.stream.id] = event.stream;
config.onRemoteStream(event.stream);
event.stream.getTracks().forEach(function(track) {
track.onended = function() { // track.onmute =
peer && peer.onremovestream(event);
};
});
event.stream.onremovetrack = function() {
peer && peer.onremovestream(event);
};
};
peer.onremovestream = function(event) {
// this event doesn't works anymore
event.stream.streamid = event.stream.id;
if (allRemoteStreams[event.stream.id]) {
delete allRemoteStreams[event.stream.id];
}
config.onRemoteStreamRemoved(event.stream);
};
if (typeof peer.removeStream !== 'function') {
// removeStream backward compatibility
peer.removeStream = function(stream) {
stream.getTracks().forEach(function(track) {
peer.removeTrack(track, stream);
});
};
}
this.addRemoteCandidate = function(remoteCandidate) {
peer.addIceCandidate(new RTCIceCandidate(remoteCandidate));
};
function oldAddRemoteSdp(remoteSdp, cb) {
cb = cb || function() {};
if (DetectRTC.browser.name !== 'Safari') {
remoteSdp.sdp = connection.processSdp(remoteSdp.sdp);
}
peer.setRemoteDescription(new RTCSessionDescription(remoteSdp), cb, function(error) {
if (!!connection.enableLogs) {
console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp);
}
cb();
});
}
this.addRemoteSdp = function(remoteSdp, cb) {
cb = cb || function() {};
if (DetectRTC.browser.name !== 'Safari') {
remoteSdp.sdp = connection.processSdp(remoteSdp.sdp);
}
peer.setRemoteDescription(new RTCSessionDescription(remoteSdp)).then(cb, function(error) {
if (!!connection.enableLogs) {
console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp);
}
cb();
}).catch(function(error) {
if (!!connection.enableLogs) {
console.error('setRemoteDescription failed', '\n', error, '\n', remoteSdp.sdp);
}
cb();
});
};
var isOfferer = true;
if (config.remoteSdp) {
isOfferer = false;
}
this.createDataChannel = function() {
var channel = peer.createDataChannel('sctp', {});
setChannelEvents(channel);
};
if (connection.session.data === true && !renegotiatingPeer) {
if (!isOfferer) {
peer.ondatachannel = function(event) {
var channel = event.channel;
setChannelEvents(channel);
};
} else {
this.createDataChannel();
}
}
this.enableDisableVideoEncoding = function(enable) {
var rtcp;
peer.getSenders().forEach(function(sender) {
if (!rtcp && sender.track.kind === 'video') {
rtcp = sender;
}
});
if (!rtcp || !rtcp.getParameters) return;
var parameters = rtcp.getParameters();
parameters.encodings[1] && (parameters.encodings[1].active = !!enable);
parameters.encodings[2] && (parameters.encodings[2].active = !!enable);
rtcp.setParameters(parameters);
};
if (config.remoteSdp) {
if (config.remoteSdp.remotePeerSdpConstraints) {
sdpConstraints = config.remoteSdp.remotePeerSdpConstraints;
}
defaults.sdpConstraints = setSdpConstraints(sdpConstraints);
this.addRemoteSdp(config.remoteSdp, function() {
createOfferOrAnswer('createAnswer');
});
}
function setChannelEvents(channel) {
// force ArrayBuffer in Firefox; which uses "Blob" by default.
channel.binaryType = 'arraybuffer';
channel.onmessage = function(event) {
config.onDataChannelMessage(event.data);
};
channel.onopen = function() {
config.onDataChannelOpened(channel);
};
channel.onerror = function(error) {
config.onDataChannelError(error);
};
channel.onclose = function(event) {
config.onDataChannelClosed(event);
};
channel.internalSend = channel.send;
channel.send = function(data) {
if (channel.readyState !== 'open') {
return;
}
channel.internalSend(data);
};
peer.channel = channel;
}
if (connection.session.audio == 'two-way' || connection.session.video == 'two-way' || connection.session.screen == 'two-way') {
defaults.sdpConstraints = setSdpConstraints({
OfferToReceiveAudio: connection.session.audio == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio),
OfferToReceiveVideo: connection.session.video == 'two-way' || connection.session.screen == 'two-way' || (config.remoteSdp && config.remoteSdp.remotePeerSdpConstraints && config.remoteSdp.remotePeerSdpConstraints.OfferToReceiveAudio)
});
}
var streamsToShare = {};
peer.getLocalStreams().forEach(function(stream) {
streamsToShare[stream.streamid] = {
isAudio: !!stream.isAudio,
isVideo: !!stream.isVideo,
isScreen: !!stream.isScreen
};
});
function oldCreateOfferOrAnswer(_method) {
peer[_method](function(localSdp) {
if (DetectRTC.browser.name !== 'Safari') {
localSdp.sdp = connection.processSdp(localSdp.sdp);
}
peer.setLocalDescription(localSdp, function() {
if (!connection.trickleIce) return;
config.onLocalSdp({
type: localSdp.type,
sdp: localSdp.sdp,
remotePeerSdpConstraints: config.remotePeerSdpConstraints || false,
renegotiatingPeer: !!config.renegotiatingPeer || false,
connectionDescription: self.connectionDescription,
dontGetRemoteStream: !!config.dontGetRemoteStream,
extra: connection ? connection.extra : {},
streamsToShare: streamsToShare
});
connection.onSettingLocalDescription(self);
}, function(error) {
if (!!connection.enableLogs) {
console.error('setLocalDescription-error', error);
}
});
}, function(error) {
if (!!connection.enableLogs) {
console.error('sdp-' + _method + '-error', error);
}
}, defaults.sdpConstraints);
}
function createOfferOrAnswer(_method) {
peer[_method](defaults.sdpConstraints).then(function(localSdp) {
if (DetectRTC.browser.name !== 'Safari') {
localSdp.sdp = connection.processSdp(localSdp.sdp);
}
peer.setLocalDescription(localSdp).then(function() {
if (!connection.trickleIce) return;
config.onLocalSdp({
type: localSdp.type,
sdp: localSdp.sdp,
remotePeerSdpConstraints: config.remotePeerSdpConstraints || false,
renegotiatingPeer: !!config.renegotiatingPeer || false,
connectionDescription: self.connectionDescription,
dontGetRemoteStream: !!config.dontGetRemoteStream,
extra: connection ? connection.extra : {},
streamsToShare: streamsToShare
});
connection.onSettingLocalDescription(self);
}, function(error) {
if (!connection.enableLogs) return;
console.error('setLocalDescription error', error);
});
}, function(error) {
if (!!connection.enableLogs) {
console.error('sdp-error', error);
}
});
}
if (isOfferer) {
createOfferOrAnswer('createOffer');
}
peer.nativeClose = peer.close;
peer.close = function() {
if (!peer) {
return;
}
try {
if (peer.nativeClose !== peer.close) {
peer.nativeClose();
}
} catch (e) {}
peer = null;
self.peer = null;
};
this.peer = peer;
}
// CodecsHandler.js
var CodecsHandler = (function() {
// use "RTCRtpTransceiver.setCodecPreferences"
function preferCodec(sdp, codecName) {
var info = splitLines(sdp);
if (!info.videoCodecNumbers) {
return sdp;
}
if (codecName === 'vp8' && info.vp8LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
if (codecName === 'vp9' && info.vp9LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
if (codecName === 'h264' && info.h264LineNumber === info.videoCodecNumbers[0]) {
return sdp;
}
sdp = preferCodecHelper(sdp, codecName, info);
return sdp;
}
function preferCodecHelper(sdp, codec, info, ignore) {
var preferCodecNumber = '';
if (codec === 'vp8') {
if (!info.vp8LineNumber) {
return sdp;
}
preferCodecNumber = info.vp8LineNumber;
}
if (codec === 'vp9') {
if (!info.vp9LineNumber) {
return sdp;
}
preferCodecNumber = info.vp9LineNumber;
}
if (codec === 'h264') {
if (!info.h264LineNumber) {
return sdp;
}
preferCodecNumber = info.h264LineNumber;
}
var newLine = info.videoCodecNumbersOriginal.split('SAVPF')[0] + 'SAVPF ';
var newOrder = [preferCodecNumber];
if (ignore) {
newOrder = [];
}
info.videoCodecNumbers.forEach(function(codecNumber) {
if (codecNumber === preferCodecNumber) return;
newOrder.push(codecNumber);
});
newLine += newOrder.join(' ');
sdp = sdp.replace(info.videoCodecNumbersOriginal, newLine);
return sdp;
}
function splitLines(sdp) {
var info = {};
sdp.split('\n').forEach(function(line) {
if (line.indexOf('m=video') === 0) {
info.videoCodecNumbers = [];
line.split('SAVPF')[1].split(' ').forEach(function(codecNumber) {
codecNumber = codecNumber.trim();
if (!codecNumber || !codecNumber.length) return;
info.videoCodecNumbers.push(codecNumber);
info.videoCodecNumbersOriginal = line;
});
}
if (line.indexOf('VP8/90000') !== -1 && !info.vp8LineNumber) {
info.vp8LineNumber = line.replace('a=rtpmap:', '').split(' ')[0];
}
if (line.indexOf('VP9/90000') !== -1 && !info.vp9LineNumber) {
info.vp9LineNumber = line.replace('a=rtpmap:', '').split(' ')[0];
}
if (line.indexOf('H264/90000') !== -1 && !info.h264LineNumber) {
info.h264LineNumber = line.replace('a=rtpmap:', '').split(' ')[0];
}
});
return info;
}
function removeVPX(sdp) {
var info = splitLines(sdp);
// last parameter below means: ignore these codecs
sdp = preferCodecHelper(sdp, 'vp9', info, true);
sdp = preferCodecHelper(sdp, 'vp8', info, true);
return sdp;
}
function disableNACK(sdp) {
if (!sdp || typeof sdp !== 'string') {
throw 'Invalid arguments.';
}
sdp = sdp.replace('a=rtcp-fb:126 nack\r\n', '');
sdp = sdp.replace('a=rtcp-fb:126 nack pli\r\n', 'a=rtcp-fb:126 pli\r\n');
sdp = sdp.replace('a=rtcp-fb:97 nack\r\n', '');
sdp = sdp.replace('a=rtcp-fb:97 nack pli\r\n', 'a=rtcp-fb:97 pli\r\n');
return sdp;
}
function prioritize(codecMimeType, peer) {
if (!peer || !peer.getSenders || !peer.getSenders().length) {
return;
}
if (!codecMimeType || typeof codecMimeType !== 'string') {
throw 'Invalid arguments.';
}
peer.getSenders().forEach(function(sender) {
var params = sender.getParameters();
for (var i = 0; i < params.codecs.length; i++) {
if (params.codecs[i].mimeType == codecMimeType) {
params.codecs.unshift(params.codecs.splice(i, 1));
break;
}
}
sender.setParameters(params);
});
}
function removeNonG722(sdp) {
return sdp.replace(/m=audio ([0-9]+) RTP\/SAVPF ([0-9 ]*)/g, 'm=audio $1 RTP\/SAVPF 9');
}
function setBAS(sdp, bandwidth, isScreen) {
if (!bandwidth) {
return sdp;
}
if (typeof isFirefox !== 'undefined' && isFirefox) {
return sdp;
}
if (isScreen) {
if (!bandwidth.screen) {
console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.');
} else if (bandwidth.screen < 300) {
console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.');
}
}
// if screen; must use at least 300kbs
if (bandwidth.screen && isScreen) {
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
}
// remove existing bandwidth lines
if (bandwidth.audio || bandwidth.video) {
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
}
if (bandwidth.audio) {
sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n');
}
if (bandwidth.screen) {
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
} else if (bandwidth.video) {
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.video + '\r\n');
}
return sdp;
}
// Find the line in sdpLines that starts with |prefix|, and, if specified,
// contains |substr| (case-insensitive search).
function findLine(sdpLines, prefix, substr) {
return findLineInRange(sdpLines, 0, -1, prefix, substr);
}
// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
// and, if specified, contains |substr| (case-insensitive search).
function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
for (var i = startLine; i < realEndLine; ++i) {
if (sdpLines[i].indexOf(prefix) === 0) {
if (!substr ||
sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
return i;
}
}
}
return null;
}
// Gets the codec payload type from an a=rtpmap:X line.
function getCodecPayloadType(sdpLine) {
var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
var result = sdpLine.match(pattern);
return (result && result.length === 2) ? result[1] : null;
}
function setVideoBitrates(sdp, params) {
params = params || {};
var xgoogle_min_bitrate = params.min;
var xgoogle_max_bitrate = params.max;
var sdpLines = sdp.split('\r\n');
// VP8
var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000');
var vp8Payload;
if (vp8Index) {
vp8Payload = getCodecPayloadType(sdpLines[vp8Index]);
}
if (!vp8Payload) {
return sdp;
}
var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000');
var rtxPayload;
if (rtxIndex) {
rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]);
}
if (!rtxIndex) {
return sdp;
}
var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString());
if (rtxFmtpLineIndex !== null) {
var appendrtxNext = '\r\n';
appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228');
sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext);
sdp = sdpLines.join('\r\n');
}
return sdp;
}
function setOpusAttributes(sdp, params) {
params = params || {};
var sdpLines = sdp.split('\r\n');
// Opus
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000');
var opusPayload;
if (opusIndex) {
opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
}
if (!opusPayload) {
return sdp;
}
var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
if (opusFmtpLineIndex === null) {
return sdp;
}
var appendOpusNext = '';
appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1');
appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1');
if (typeof params.maxaveragebitrate != 'undefined') {
appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8);
}
if (typeof params.maxplaybackrate != 'undefined') {
appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8);
}
if (typeof params.cbr != 'undefined') {
appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1');
}
if (typeof params.useinbandfec != 'undefined') {
appendOpusNext += '; useinbandfec=' + params.useinbandfec;
}
if (typeof params.usedtx != 'undefined') {
appendOpusNext += '; usedtx=' + params.usedtx;
}
if (typeof params.maxptime != 'undefined') {
appendOpusNext += '\r\na=maxptime:' + params.maxptime;
}
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext);
sdp = sdpLines.join('\r\n');
return sdp;
}
// forceStereoAudio => via webrtcexample.com
// requires getUserMedia => echoCancellation:false
function forceStereoAudio(sdp) {
var sdpLines = sdp.split('\r\n');
var fmtpLineIndex = null;
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
var opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
break;
}
}
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('a=fmtp') !== -1) {
var payload = extractSdp(sdpLines[i], /a=fmtp:(\d+)/);
if (payload === opusPayload) {
fmtpLineIndex = i;
break;
}
}
}
if (fmtpLineIndex === null) return sdp;
sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1');
sdp = sdpLines.join('\r\n');
return sdp;
}
return {
removeVPX: removeVPX,
disableNACK: disableNACK,
prioritize: prioritize,
removeNonG722: removeNonG722,
setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) {
return setBAS(sdp, bandwidth, isScreen);
},
setVideoBitrates: function(sdp, params) {
return setVideoBitrates(sdp, params);
},
setOpusAttributes: function(sdp, params) {
return setOpusAttributes(sdp, params);
},
preferVP9: function(sdp) {
return preferCodec(sdp, 'vp9');
},
preferCodec: preferCodec,
forceStereoAudio: forceStereoAudio
};
})();
// backward compatibility
window.BandwidthHandler = CodecsHandler;
// OnIceCandidateHandler.js
var OnIceCandidateHandler = (function() {
function processCandidates(connection, icePair) {
var candidate = icePair.candidate;
var iceRestrictions = connection.candidates;
var stun = iceRestrictions.stun;
var turn = iceRestrictions.turn;
if (!isNull(iceRestrictions.reflexive)) {
stun = iceRestrictions.reflexive;
}
if (!isNull(iceRestrictions.relay)) {
turn = iceRestrictions.relay;
}
if (!iceRestrictions.host && !!candidate.match(/typ host/g)) {
return;
}
if (!turn && !!candidate.match(/typ relay/g)) {
return;
}
if (!stun && !!candidate.match(/typ srflx/g)) {
return;
}
var protocol = connection.iceProtocols;
if (!protocol.udp && !!candidate.match(/ udp /g)) {
return;
}
if (!protocol.tcp && !!candidate.match(/ tcp /g)) {
return;
}
if (connection.enableLogs) {
console.debug('Your candidate pairs:', candidate);
}
return {
candidate: candidate,
sdpMid: icePair.sdpMid,
sdpMLineIndex: icePair.sdpMLineIndex
};
}
return {
processCandidates: processCandidates
};
})();
// IceServersHandler.js
var IceServersHandler = (function() {
function getIceServers(connection) {
// resiprocate: 3344+4433
// pions: 7575
var iceServers = [{
'urls': [
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302',
'stun:stun.l.google.com:19302?transport=udp',
]
}];
return iceServers;
}
return {
getIceServers: getIceServers
};
})();
// getUserMediaHandler.js
function setStreamType(constraints, stream) {
if (constraints.mandatory && constraints.mandatory.chromeMediaSource) {
stream.isScreen = true;
} else if (constraints.mozMediaSource || constraints.mediaSource) {
stream.isScreen = true;
} else if (constraints.video) {
stream.isVideo = true;
} else if (constraints.audio) {
stream.isAudio = true;
}
}
// allow users to manage this object (to support re-capturing of screen/etc.)
window.currentUserMediaRequest = {
streams: [],
mutex: false,
queueRequests: [],
remove: function(idInstance) {
this.mutex = false;
var stream = this.streams[idInstance];
if (!stream) {
return;
}
stream = stream.stream;
var options = stream.currentUserMediaRequestOptions;
if (this.queueRequests.indexOf(options)) {
delete this.queueRequests[this.queueRequests.indexOf(options)];
this.queueRequests = removeNullEntries(this.queueRequests);
}
this.streams[idInstance].stream = null;
delete this.streams[idInstance];
}
};
function getUserMediaHandler(options) {
if (currentUserMediaRequest.mutex === true) {
currentUserMediaRequest.queueRequests.push(options);
return;
}
currentUserMediaRequest.mutex = true;
// easy way to match
var idInstance = JSON.stringify(options.localMediaConstraints);
function streaming(stream, returnBack) {
setStreamType(options.localMediaConstraints, stream);
var streamEndedEvent = 'ended';
if ('oninactive' in stream) {
streamEndedEvent = 'inactive';
}
stream.addEventListener(streamEndedEvent, function() {
delete currentUserMediaRequest.streams[idInstance];
currentUserMediaRequest.mutex = false;
if (currentUserMediaRequest.queueRequests.indexOf(options)) {
delete currentUserMediaRequest.queueRequests[currentUserMediaRequest.queueRequests.indexOf(options)];
currentUserMediaRequest.queueRequests = removeNullEntries(currentUserMediaRequest.queueRequests);
}
}, false);
currentUserMediaRequest.streams[idInstance] = {
stream: stream
};
currentUserMediaRequest.mutex = false;
if (currentUserMediaRequest.queueRequests.length) {
getUserMediaHandler(currentUserMediaRequest.queueRequests.shift());
}
// callback
options.onGettingLocalMedia(stream, returnBack);
}
if (currentUserMediaRequest.streams[idInstance]) {
streaming(currentUserMediaRequest.streams[idInstance].stream, true);
} else {
var isBlackBerry = !!(/BB10|BlackBerry/i.test(navigator.userAgent || ''));
if (isBlackBerry || typeof navigator.mediaDevices === 'undefined' || typeof navigator.mediaDevices.getUserMedia !== 'function') {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.getUserMedia(options.localMediaConstraints, function(stream) {
stream.streamid = stream.streamid || stream.id || getRandomString();
stream.idInstance = idInstance;
streaming(stream);
}, function(error) {
options.onLocalMediaError(error, options.localMediaConstraints);
});
return;
}
if (typeof navigator.mediaDevices === 'undefined') {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
var getUserMediaSuccess = function() {};
var getUserMediaFailure = function() {};
var getUserMediaStream, getUserMediaError;
navigator.mediaDevices = {
getUserMedia: function(hints) {
navigator.getUserMedia(hints, function(getUserMediaSuccess) {
getUserMediaSuccess(stream);
getUserMediaStream = stream;
}, function(error) {
getUserMediaFailure(error);
getUserMediaError = error;
});
return {
then: function(successCB) {
if (getUserMediaStream) {
successCB(getUserMediaStream);
return;
}
getUserMediaSuccess = successCB;
return {
then: function(failureCB) {
if (getUserMediaError) {
failureCB(getUserMediaError);
return;
}
getUserMediaFailure = failureCB;
}
}
}
}
}
};
}
if (options.localMediaConstraints.isScreen === true) {
if (navigator.mediaDevices.getDisplayMedia) {
navigator.mediaDevices.getDisplayMedia(options.localMediaConstraints).then(function(stream) {
stream.streamid = stream.streamid || stream.id || getRandomString();
stream.idInstance = idInstance;
streaming(stream);
}).catch(function(error) {
options.onLocalMediaError(error, options.localMediaConstraints);
});
} else if (navigator.getDisplayMedia) {
navigator.getDisplayMedia(options.localMediaConstraints).then(function(stream) {
stream.streamid = stream.streamid || stream.id || getRandomString();
stream.idInstance = idInstance;
streaming(stream);
}).catch(function(error) {
options.onLocalMediaError(error, options.localMediaConstraints);
});
} else {
throw new Error('getDisplayMedia API is not availabe in this browser.');
}
return;
}
navigator.mediaDevices.getUserMedia(options.localMediaConstraints).then(function(stream) {
stream.streamid = stream.streamid || stream.id || getRandomString();
stream.idInstance = idInstance;
streaming(stream);
}).catch(function(error) {
options.onLocalMediaError(error, options.localMediaConstraints);
});
}
}
// StreamsHandler.js
var StreamsHandler = (function() {
function handleType(type) {
if (!type) {
return;
}
if (typeof type === 'string' || typeof type === 'undefined') {
return type;
}
if (type.audio && type.video) {
return null;
}
if (type.audio) {
return 'audio';
}
if (type.video) {
return 'video';
}
return;
}
function setHandlers(stream, syncAction, connection) {
if (!stream || !stream.addEventListener) return;
if (typeof syncAction == 'undefined' || syncAction == true) {
var streamEndedEvent = 'ended';
if ('oninactive' in stream) {
streamEndedEvent = 'inactive';
}
stream.addEventListener(streamEndedEvent, function() {
StreamsHandler.onSyncNeeded(this.streamid, streamEndedEvent);
}, false);
}
stream.mute = function(type, isSyncAction) {
type = handleType(type);
if (typeof isSyncAction !== 'undefined') {
syncAction = isSyncAction;
}
if (typeof type == 'undefined' || type == 'audio') {
getTracks(stream, 'audio').forEach(function(track) {
track.enabled = false;
connection.streamEvents[stream.streamid].isAudioMuted = true;
});
}
if (typeof type == 'undefined' || type == 'video') {
getTracks(stream, 'video').forEach(function(track) {
track.enabled = false;
});
}
if (typeof syncAction == 'undefined' || syncAction == true) {
StreamsHandler.onSyncNeeded(stream.streamid, 'mute', type);
}
connection.streamEvents[stream.streamid].muteType = type || 'both';
fireEvent(stream, 'mute', type);
};
stream.unmute = function(type, isSyncAction) {
type = handleType(type);
if (typeof isSyncAction !== 'undefined') {
syncAction = isSyncAction;
}
graduallyIncreaseVolume();
if (typeof type == 'undefined' || type == 'audio') {
getTracks(stream, 'audio').forEach(function(track) {
track.enabled = true;
connection.streamEvents[stream.streamid].isAudioMuted = false;
});
}
if (typeof type == 'undefined' || type == 'video') {
getTracks(stream, 'video').forEach(function(track) {
track.enabled = true;
});
// make sure that video unmute doesn't affects audio
if (typeof type !== 'undefined' && type == 'video' && connection.streamEvents[stream.streamid].isAudioMuted) {
(function looper(times) {
if (!times) {
times = 0;
}
times++;
// check until five-seconds
if (times < 100 && connection.streamEvents[stream.streamid].isAudioMuted) {
stream.mute('audio');
setTimeout(function() {
looper(times);
}, 50);
}
})();
}
}
if (typeof syncAction == 'undefined' || syncAction == true) {
StreamsHandler.onSyncNeeded(stream.streamid, 'unmute', type);
}
connection.streamEvents[stream.streamid].unmuteType = type || 'both';
fireEvent(stream, 'unmute', type);
};
function graduallyIncreaseVolume() {
if (!connection.streamEvents[stream.streamid].mediaElement) {
return;
}
var mediaElement = connection.streamEvents[stream.streamid].mediaElement;
mediaElement.volume = 0;
afterEach(200, 5, function() {
try {
mediaElement.volume += .20;
} catch (e) {
mediaElement.volume = 1;
}
});
}
}
function afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes) {
startedTimes = (startedTimes || 0) + 1;
if (startedTimes >= numberOfTimes) return;
setTimeout(function() {
callback();
afterEach(setTimeoutInteval, numberOfTimes, callback, startedTimes);
}, setTimeoutInteval);
}
return {
setHandlers: setHandlers,
onSyncNeeded: function(streamid, action, type) {}
};
})();
// TextReceiver.js & TextSender.js
function TextReceiver(connection) {
var content = {};
function receive(data, userid, extra) {
// uuid is used to uniquely identify sending instance
var uuid = data.uuid;
if (!content[uuid]) {
content[uuid] = [];
}
content[uuid].push(data.message);
if (data.last) {
var message = content[uuid].join('');
if (data.isobject) {
message = JSON.parse(message);
}
// latency detection
var receivingTime = new Date().getTime();
var latency = receivingTime - data.sendingTime;
var e = {
data: message,
userid: userid,
extra: extra,
latency: latency
};
if (connection.autoTranslateText) {
e.original = e.data;
connection.Translator.TranslateText(e.data, function(translatedText) {
e.data = translatedText;
connection.onmessage(e);
});
} else {
connection.onmessage(e);
}
delete content[uuid];
}
}
return {
receive: receive
};
}
// TextSender.js
var TextSender = {
send: function(config) {
var connection = config.connection;
var channel = config.channel,
remoteUserId = config.remoteUserId,
initialText = config.text,
packetSize = connection.chunkSize || 1000,
textToTransfer = '',
isobject = false;
if (!isString(initialText)) {
isobject = true;
initialText = JSON.stringify(initialText);
}
// uuid is used to uniquely identify sending instance
var uuid = getRandomString();
var sendingTime = new Date().getTime();
sendText(initialText);
function sendText(textMessage, text) {
var data = {
type: 'text',
uuid: uuid,
sendingTime: sendingTime
};
if (textMessage) {
text = textMessage;
data.packets = parseInt(text.length / packetSize);
}
if (text.length > packetSize) {
data.message = text.slice(0, packetSize);
} else {
data.message = text;
data.last = true;
data.isobject = isobject;
}
channel.send(data, remoteUserId);
textToTransfer = text.slice(data.message.length);
if (textToTransfer.length) {
setTimeout(function() {
sendText(null, textToTransfer);
}, connection.chunkInterval || 100);
}
}
}
};
// FileProgressBarHandler.js
var FileProgressBarHandler = (function() {
function handle(connection) {
var progressHelper = {};
// www.RTCMultiConnection.org/docs/onFileStart/
connection.onFileStart = function(file) {
var div = document.createElement('div');
div.title = file.name;
div.innerHTML = '<label>0%</label> <progress></progress>';
if (file.remoteUserId) {
div.innerHTML += ' (Sharing with:' + file.remoteUserId + ')';
}
if (!connection.filesContainer) {
connection.filesContainer = document.body || document.documentElement;
}
connection.filesContainer.insertBefore(div, connection.filesContainer.firstChild);
if (!file.remoteUserId) {
progressHelper[file.uuid] = {
div: div,
progress: div.querySelector('progress'),
label: div.querySelector('label')
};
progressHelper[file.uuid].progress.max = file.maxChunks;
return;
}
if (!progressHelper[file.uuid]) {
progressHelper[file.uuid] = {};
}
progressHelper[file.uuid][file.remoteUserId] = {
div: div,
progress: div.querySelector('progress'),
label: div.querySelector('label')
};
progressHelper[file.uuid][file.remoteUserId].progress.max = file.maxChunks;
};
// www.RTCMultiConnection.org/docs/onFileProgress/
connection.onFileProgress = function(chunk) {
var helper = progressHelper[chunk.uuid];
if (!helper) {
return;
}
if (chunk.remoteUserId) {
helper = progressHelper[chunk.uuid][chunk.remoteUserId];
if (!helper) {
return;
}
}
helper.progress.value = chunk.currentPosition || chunk.maxChunks || helper.progress.max;
updateLabel(helper.progress, helper.label);
};
// www.RTCMultiConnection.org/docs/onFileEnd/
connection.onFileEnd = function(file) {
var helper = progressHelper[file.uuid];
if (!helper) {
console.error('No such progress-helper element exist.', file);
return;
}
if (file.remoteUserId) {
helper = progressHelper[file.uuid][file.remoteUserId];
if (!helper) {
return;
}
}
var div = helper.div;
if (file.type.indexOf('image') != -1) {
div.innerHTML = '<a href="' + file.url + '" download="' + file.name + '">Download <strong style="color:red;">' + file.name + '</strong> </a><br /><img src="' + file.url + '" title="' + file.name + '" style="max-width: 80%;">';
} else {
div.innerHTML = '<a href="' + file.url + '" download="' + file.name + '">Download <strong style="color:red;">' + file.name + '</strong> </a><br /><iframe src="' + file.url + '" title="' + file.name + '" style="width: 80%;border: 0;height: inherit;margin-top:1em;"></iframe>';
}
};
function updateLabel(progress, label) {
if (progress.position === -1) {
return;
}
var position = +progress.position.toFixed(2).split('.')[1] || 100;
label.innerHTML = position + '%';
}
}
return {
handle: handle
};
})();
// _____________________
// RTCMultiConnection.js
(function(connection) {
forceOptions = forceOptions || {
useDefaultDevices: true
};
connection.channel = connection.sessionid = (roomid || location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')) + '';
var mPeer = new MultiPeers(connection);
var preventDuplicateOnStreamEvents = {};
mPeer.onGettingLocalMedia = function(stream, callback) {
callback = callback || function() {};
if (preventDuplicateOnStreamEvents[stream.streamid]) {
callback();
return;
}
preventDuplicateOnStreamEvents[stream.streamid] = true;
try {
stream.type = 'local';
} catch (e) {}
connection.setStreamEndHandler(stream);
getRMCMediaElement(stream, function(mediaElement) {
mediaElement.id = stream.streamid;
mediaElement.muted = true;
mediaElement.volume = 0;
if (connection.attachStreams.indexOf(stream) === -1) {
connection.attachStreams.push(stream);
}
if (typeof StreamsHandler !== 'undefined') {
StreamsHandler.setHandlers(stream, true, connection);
}
var isAudioMuted = stream.getAudioTracks().filter(function(track) {
return track.enabled;
}).length === 0;
connection.streamEvents[stream.streamid] = {
stream: stream,
type: 'local',
mediaElement: mediaElement,
userid: connection.userid,
extra: connection.extra,
streamid: stream.streamid,
isAudioMuted: isAudioMuted
};
try {
setHarkEvents(connection, connection.streamEvents[stream.streamid]);
setMuteHandlers(connection, connection.streamEvents[stream.streamid]);
connection.onstream(connection.streamEvents[stream.streamid]);
} catch (e) {
//
}
callback();
}, connection);
};
mPeer.onGettingRemoteMedia = function(stream, remoteUserId) {
try {
stream.type = 'remote';
} catch (e) {}
connection.setStreamEndHandler(stream, 'remote-stream');
getRMCMediaElement(stream, function(mediaElement) {
mediaElement.id = stream.streamid;
if (typeof StreamsHandler !== 'undefined') {
StreamsHandler.setHandlers(stream, false, connection);
}
connection.streamEvents[stream.streamid] = {
stream: stream,
type: 'remote',
userid: remoteUserId,
extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {},
mediaElement: mediaElement,
streamid: stream.streamid
};
setMuteHandlers(connection, connection.streamEvents[stream.streamid]);
connection.onstream(connection.streamEvents[stream.streamid]);
}, connection);
};
mPeer.onRemovingRemoteMedia = function(stream, remoteUserId) {
var streamEvent = connection.streamEvents[stream.streamid];
if (!streamEvent) {
streamEvent = {
stream: stream,
type: 'remote',
userid: remoteUserId,
extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {},
streamid: stream.streamid,
mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null
};
}
if (connection.peersBackup[streamEvent.userid]) {
streamEvent.extra = connection.peersBackup[streamEvent.userid].extra;
}
connection.onstreamended(streamEvent);
delete connection.streamEvents[stream.streamid];
};
mPeer.onNegotiationNeeded = function(message, remoteUserId, callback) {
callback = callback || function() {};
remoteUserId = remoteUserId || message.remoteUserId;
message = message || '';
// usually a message looks like this
var messageToDeliver = {
remoteUserId: remoteUserId,
message: message,
sender: connection.userid
};
if (message.remoteUserId && message.message && message.sender) {
// if a code is manually passing required data
messageToDeliver = message;
}
connectSocket(function() {
connection.socket.emit(connection.socketMessageEvent, messageToDeliver, callback);
});
};
function onUserLeft(remoteUserId) {
connection.deletePeer(remoteUserId);
}
mPeer.onUserLeft = onUserLeft;
mPeer.disconnectWith = function(remoteUserId, callback) {
if (connection.socket) {
connection.socket.emit('disconnect-with', remoteUserId, callback || function() {});
}
connection.deletePeer(remoteUserId);
};
connection.socketOptions = {
// 'force new connection': true, // For SocketIO version < 1.0
// 'forceNew': true, // For SocketIO version >= 1.0
'transport': 'polling' // fixing transport:unknown issues
};
function connectSocket(connectCallback) {
connection.socketAutoReConnect = true;
if (connection.socket) { // todo: check here readySate/etc. to make sure socket is still opened
if (connectCallback) {
connectCallback(connection.socket);
}
return;
}
if (typeof SocketConnection === 'undefined') {
if (typeof FirebaseConnection !== 'undefined') {
window.SocketConnection = FirebaseConnection;
} else if (typeof PubNubConnection !== 'undefined') {
window.SocketConnection = PubNubConnection;
} else {
throw 'SocketConnection.js seems missed.';
}
}
new SocketConnection(connection, function(s) {
if (connectCallback) {
connectCallback(connection.socket);
}
});
}
// 1st paramter is roomid
// 2rd paramter is a callback function
connection.openOrJoin = function(roomid, callback) {
callback = callback || function() {};
connection.checkPresence(roomid, function(isRoomExist, roomid) {
if (isRoomExist) {
connection.sessionid = roomid;
var localPeerSdpConstraints = false;
var remotePeerSdpConstraints = false;
var isOneWay = !!connection.session.oneway;
var isDataOnly = isData(connection.session);
remotePeerSdpConstraints = {
OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo
}
localPeerSdpConstraints = {
OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo
}
var connectionDescription = {
remoteUserId: connection.sessionid,
message: {
newParticipationRequest: true,
isOneWay: isOneWay,
isDataOnly: isDataOnly,
localPeerSdpConstraints: localPeerSdpConstraints,
remotePeerSdpConstraints: remotePeerSdpConstraints
},
sender: connection.userid
};
beforeJoin(connectionDescription.message, function() {
joinRoom(connectionDescription, callback);
});
return;
}
connection.waitingForLocalMedia = true;
connection.isInitiator = true;
connection.sessionid = roomid || connection.sessionid;
if (isData(connection.session)) {
openRoom(callback);
return;
}
connection.captureUserMedia(function() {
openRoom(callback);
});
});
};
// don't allow someone to join this person until he has the media
connection.waitingForLocalMedia = false;
connection.open = function(roomid, callback) {
callback = callback || function() {};
connection.waitingForLocalMedia = true;
connection.isInitiator = true;
connection.sessionid = roomid || connection.sessionid;
connectSocket(function() {
if (isData(connection.session)) {
openRoom(callback);
return;
}
connection.captureUserMedia(function() {
openRoom(callback);
});
});
};
// this object keeps extra-data records for all connected users
// this object is never cleared so you can always access extra-data even if a user left
connection.peersBackup = {};
connection.deletePeer = function(remoteUserId) {
if (!remoteUserId || !connection.peers[remoteUserId]) {
return;
}
var eventObject = {
userid: remoteUserId,
extra: connection.peers[remoteUserId] ? connection.peers[remoteUserId].extra : {}
};
if (connection.peersBackup[eventObject.userid]) {
eventObject.extra = connection.peersBackup[eventObject.userid].extra;
}
connection.onleave(eventObject);
if (!!connection.peers[remoteUserId]) {
connection.peers[remoteUserId].streams.forEach(function(stream) {
stream.stop();
});
var peer = connection.peers[remoteUserId].peer;
if (peer && peer.iceConnectionState !== 'closed') {
try {
peer.close();
} catch (e) {}
}
if (connection.peers[remoteUserId]) {
connection.peers[remoteUserId].peer = null;
delete connection.peers[remoteUserId];
}
}
}
connection.rejoin = function(connectionDescription) {
if (connection.isInitiator || !connectionDescription || !Object.keys(connectionDescription).length) {
return;
}
var extra = {};
if (connection.peers[connectionDescription.remoteUserId]) {
extra = connection.peers[connectionDescription.remoteUserId].extra;
connection.deletePeer(connectionDescription.remoteUserId);
}
if (connectionDescription && connectionDescription.remoteUserId) {
connection.join(connectionDescription.remoteUserId);
connection.onReConnecting({
userid: connectionDescription.remoteUserId,
extra: extra
});
}
};
connection.join = function(remoteUserId, options) {
connection.sessionid = (remoteUserId ? remoteUserId.sessionid || remoteUserId.remoteUserId || remoteUserId : false) || connection.sessionid;
connection.sessionid += '';
var localPeerSdpConstraints = false;
var remotePeerSdpConstraints = false;
var isOneWay = false;
var isDataOnly = false;
if ((remoteUserId && remoteUserId.session) || !remoteUserId || typeof remoteUserId === 'string') {
var session = remoteUserId ? remoteUserId.session || connection.session : connection.session;
isOneWay = !!session.oneway;
isDataOnly = isData(session);
remotePeerSdpConstraints = {
OfferToReceiveAudio: connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: connection.sdpConstraints.mandatory.OfferToReceiveVideo
};
localPeerSdpConstraints = {
OfferToReceiveAudio: isOneWay ? !!connection.session.audio : connection.sdpConstraints.mandatory.OfferToReceiveAudio,
OfferToReceiveVideo: isOneWay ? !!connection.session.video || !!connection.session.screen : connection.sdpConstraints.mandatory.OfferToReceiveVideo
};
}
options = options || {};
var cb = function() {};
if (typeof options === 'function') {
cb = options;
options = {};
}
if (typeof options.localPeerSdpConstraints !== 'undefined') {
localPeerSdpConstraints = options.localPeerSdpConstraints;
}
if (typeof options.remotePeerSdpConstraints !== 'undefined') {
remotePeerSdpConstraints = options.remotePeerSdpConstraints;
}
if (typeof options.isOneWay !== 'undefined') {
isOneWay = options.isOneWay;
}
if (typeof options.isDataOnly !== 'undefined') {
isDataOnly = options.isDataOnly;
}
var connectionDescription = {
remoteUserId: connection.sessionid,
message: {
newParticipationRequest: true,
isOneWay: isOneWay,
isDataOnly: isDataOnly,
localPeerSdpConstraints: localPeerSdpConstraints,
remotePeerSdpConstraints: remotePeerSdpConstraints
},
sender: connection.userid
};
beforeJoin(connectionDescription.message, function() {
connectSocket(function() {
joinRoom(connectionDescription, cb);
});
});
return connectionDescription;
};
function joinRoom(connectionDescription, cb) {
connection.socket.emit('join-room', {
sessionid: connection.sessionid,
session: connection.session,
mediaConstraints: connection.mediaConstraints,
sdpConstraints: connection.sdpConstraints,
streams: getStreamInfoForAdmin(),
extra: connection.extra,
password: typeof connection.password !== 'undefined' && typeof connection.password !== 'object' ? connection.password : ''
}, function(isRoomJoined, error) {
if (isRoomJoined === true) {
if (connection.enableLogs) {
console.log('isRoomJoined: ', isRoomJoined, ' roomid: ', connection.sessionid);
}
if (!!connection.peers[connection.sessionid]) {
// on socket disconnect & reconnect
return;
}
mPeer.onNegotiationNeeded(connectionDescription);
}
if (isRoomJoined === false) {
if (connection.enableLogs) {
console.warn('isRoomJoined: ', error, ' roomid: ', connection.sessionid);
}
// [disabled] retry after 3 seconds
false && setTimeout(function() {
joinRoom(connectionDescription, cb);
}, 3000);
}
cb(isRoomJoined, connection.sessionid, error);
});
}
connection.publicRoomIdentifier = '';
function openRoom(callback) {
if (connection.enableLogs) {
console.log('Sending open-room signal to socket.io');
}
connection.waitingForLocalMedia = false;
connection.socket.emit('open-room', {
sessionid: connection.sessionid,
session: connection.session,
mediaConstraints: connection.mediaConstraints,
sdpConstraints: connection.sdpConstraints,
streams: getStreamInfoForAdmin(),
extra: connection.extra,
identifier: connection.publicRoomIdentifier,
password: typeof connection.password !== 'undefined' && typeof connection.password !== 'object' ? connection.password : ''
}, function(isRoomOpened, error) {
if (isRoomOpened === true) {
if (connection.enableLogs) {
console.log('isRoomOpened: ', isRoomOpened, ' roomid: ', connection.sessionid);
}
callback(isRoomOpened, connection.sessionid);
}
if (isRoomOpened === false) {
if (connection.enableLogs) {
console.warn('isRoomOpened: ', error, ' roomid: ', connection.sessionid);
}
callback(isRoomOpened, connection.sessionid, error);
}
});
}
function getStreamInfoForAdmin() {
try {
return connection.streamEvents.selectAll('local').map(function(event) {
return {
streamid: event.streamid,
tracks: event.stream.getTracks().length
};
});
} catch (e) {
return [];
}
}
function beforeJoin(userPreferences, callback) {
if (connection.dontCaptureUserMedia || userPreferences.isDataOnly) {
callback();
return;
}
var localMediaConstraints = {};
if (userPreferences.localPeerSdpConstraints.OfferToReceiveAudio) {
localMediaConstraints.audio = connection.mediaConstraints.audio;
}
if (userPreferences.localPeerSdpConstraints.OfferToReceiveVideo) {
localMediaConstraints.video = connection.mediaConstraints.video;
}
var session = userPreferences.session || connection.session;
if (session.oneway && session.audio !== 'two-way' && session.video !== 'two-way' && session.screen !== 'two-way') {
callback();
return;
}
if (session.oneway && session.audio && session.audio === 'two-way') {
session = {
audio: true
};
}
if (session.audio || session.video || session.screen) {
if (session.screen) {
if (DetectRTC.browser.name === 'Edge') {
navigator.getDisplayMedia({
video: true,
audio: isAudioPlusTab(connection)
}).then(function(screen) {
screen.isScreen = true;
mPeer.onGettingLocalMedia(screen);
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
connection.invokeGetUserMedia(null, callback);
} else {
callback(screen);
}
}, function(error) {
console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.');
});
} else {
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection),
video: true,
isScreen: true
}, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, callback) : callback);
}
} else if (session.audio || session.video) {
connection.invokeGetUserMedia(null, callback, session);
}
}
}
connection.getUserMedia = connection.captureUserMedia = function(callback, sessionForced) {
callback = callback || function() {};
var session = sessionForced || connection.session;
if (connection.dontCaptureUserMedia || isData(session)) {
callback();
return;
}
if (session.audio || session.video || session.screen) {
if (session.screen) {
if (DetectRTC.browser.name === 'Edge') {
navigator.getDisplayMedia({
video: true,
audio: isAudioPlusTab(connection)
}).then(function(screen) {
screen.isScreen = true;
mPeer.onGettingLocalMedia(screen);
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
var nonScreenSession = {};
for (var s in session) {
if (s !== 'screen') {
nonScreenSession[s] = session[s];
}
}
connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession);
return;
}
callback(screen);
}, function(error) {
console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.');
});
} else {
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection),
video: true,
isScreen: true
}, function(stream) {
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
var nonScreenSession = {};
for (var s in session) {
if (s !== 'screen') {
nonScreenSession[s] = session[s];
}
}
connection.invokeGetUserMedia(sessionForced, callback, nonScreenSession);
return;
}
callback(stream);
});
}
} else if (session.audio || session.video) {
connection.invokeGetUserMedia(sessionForced, callback, session);
}
}
};
connection.onbeforeunload = function(arg1, dontCloseSocket) {
if (!connection.closeBeforeUnload) {
return;
}
connection.peers.getAllParticipants().forEach(function(participant) {
mPeer.onNegotiationNeeded({
userLeft: true
}, participant);
if (connection.peers[participant] && connection.peers[participant].peer) {
connection.peers[participant].peer.close();
}
delete connection.peers[participant];
});
if (!dontCloseSocket) {
connection.closeSocket();
}
connection.isInitiator = false;
};
if (!window.ignoreBeforeUnload) {
// user can implement its own version of window.onbeforeunload
connection.closeBeforeUnload = true;
window.addEventListener('beforeunload', connection.onbeforeunload, false);
} else {
connection.closeBeforeUnload = false;
}
connection.userid = getRandomString();
connection.changeUserId = function(newUserId, callback) {
callback = callback || function() {};
connection.userid = newUserId || getRandomString();
connection.socket.emit('changed-uuid', connection.userid, callback);
};
connection.extra = {};
connection.attachStreams = [];
connection.session = {
audio: true,
video: true
};
connection.enableFileSharing = false;
// all values in kbps
connection.bandwidth = {
screen: false,
audio: false,
video: false
};
connection.codecs = {
audio: 'opus',
video: 'VP9'
};
connection.processSdp = function(sdp) {
// ignore SDP modification if unified-pan is supported
if (isUnifiedPlanSupportedDefault()) {
return sdp;
}
if (DetectRTC.browser.name === 'Safari') {
return sdp;
}
if (connection.codecs.video.toUpperCase() === 'VP8') {
sdp = CodecsHandler.preferCodec(sdp, 'vp8');
}
if (connection.codecs.video.toUpperCase() === 'VP9') {
sdp = CodecsHandler.preferCodec(sdp, 'vp9');
}
if (connection.codecs.video.toUpperCase() === 'H264') {
sdp = CodecsHandler.preferCodec(sdp, 'h264');
}
if (connection.codecs.audio === 'G722') {
sdp = CodecsHandler.removeNonG722(sdp);
}
if (DetectRTC.browser.name === 'Firefox') {
return sdp;
}
if (connection.bandwidth.video || connection.bandwidth.screen) {
sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, connection.bandwidth, !!connection.session.screen);
}
if (connection.bandwidth.video) {
sdp = CodecsHandler.setVideoBitrates(sdp, {
min: connection.bandwidth.video * 8 * 1024,
max: connection.bandwidth.video * 8 * 1024
});
}
if (connection.bandwidth.audio) {
sdp = CodecsHandler.setOpusAttributes(sdp, {
maxaveragebitrate: connection.bandwidth.audio * 8 * 1024,
maxplaybackrate: connection.bandwidth.audio * 8 * 1024,
stereo: 1,
maxptime: 3
});
}
return sdp;
};
if (typeof CodecsHandler !== 'undefined') {
connection.BandwidthHandler = connection.CodecsHandler = CodecsHandler;
}
connection.mediaConstraints = {
audio: {
mandatory: {},
optional: connection.bandwidth.audio ? [{
bandwidth: connection.bandwidth.audio * 8 * 1024 || 128 * 8 * 1024
}] : []
},
video: {
mandatory: {},
optional: connection.bandwidth.video ? [{
bandwidth: connection.bandwidth.video * 8 * 1024 || 128 * 8 * 1024
}, {
facingMode: 'user'
}] : [{
facingMode: 'user'
}]
}
};
if (DetectRTC.browser.name === 'Firefox') {
connection.mediaConstraints = {
audio: true,
video: true
};
}
if (!forceOptions.useDefaultDevices && !DetectRTC.isMobileDevice) {
DetectRTC.load(function() {
var lastAudioDevice, lastVideoDevice;
// it will force RTCMultiConnection to capture last-devices
// i.e. if external microphone is attached to system, we should prefer it over built-in devices.
DetectRTC.MediaDevices.forEach(function(device) {
if (device.kind === 'audioinput' && connection.mediaConstraints.audio !== false) {
lastAudioDevice = device;
}
if (device.kind === 'videoinput' && connection.mediaConstraints.video !== false) {
lastVideoDevice = device;
}
});
if (lastAudioDevice) {
if (DetectRTC.browser.name === 'Firefox') {
if (connection.mediaConstraints.audio !== true) {
connection.mediaConstraints.audio.deviceId = lastAudioDevice.id;
} else {
connection.mediaConstraints.audio = {
deviceId: lastAudioDevice.id
}
}
return;
}
if (connection.mediaConstraints.audio == true) {
connection.mediaConstraints.audio = {
mandatory: {},
optional: []
}
}
if (!connection.mediaConstraints.audio.optional) {
connection.mediaConstraints.audio.optional = [];
}
var optional = [{
sourceId: lastAudioDevice.id
}];
connection.mediaConstraints.audio.optional = optional.concat(connection.mediaConstraints.audio.optional);
}
if (lastVideoDevice) {
if (DetectRTC.browser.name === 'Firefox') {
if (connection.mediaConstraints.video !== true) {
connection.mediaConstraints.video.deviceId = lastVideoDevice.id;
} else {
connection.mediaConstraints.video = {
deviceId: lastVideoDevice.id
}
}
return;
}
if (connection.mediaConstraints.video == true) {
connection.mediaConstraints.video = {
mandatory: {},
optional: []
}
}
if (!connection.mediaConstraints.video.optional) {
connection.mediaConstraints.video.optional = [];
}
var optional = [{
sourceId: lastVideoDevice.id
}];
connection.mediaConstraints.video.optional = optional.concat(connection.mediaConstraints.video.optional);
}
});
}
connection.sdpConstraints = {
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
},
optional: [{
VoiceActivityDetection: false
}]
};
connection.sdpSemantics = null; // "unified-plan" or "plan-b", ref: webrtc.org/web-apis/chrome/unified-plan/
connection.iceCandidatePoolSize = null; // 0
connection.bundlePolicy = null; // max-bundle
connection.rtcpMuxPolicy = null; // "require" or "negotiate"
connection.iceTransportPolicy = null; // "relay" or "all"
connection.optionalArgument = {
optional: [{
DtlsSrtpKeyAgreement: true
}, {
googImprovedWifiBwe: true
}, {
googScreencastMinBitrate: 300
}, {
googIPv6: true
}, {
googDscp: true
}, {
googCpuUnderuseThreshold: 55
}, {
googCpuOveruseThreshold: 85
}, {
googSuspendBelowMinBitrate: true
}, {
googCpuOveruseDetection: true
}],
mandatory: {}
};
connection.iceServers = IceServersHandler.getIceServers(connection);
connection.candidates = {
host: true,
stun: true,
turn: true
};
connection.iceProtocols = {
tcp: true,
udp: true
};
// EVENTs
connection.onopen = function(event) {
if (!!connection.enableLogs) {
console.info('Data connection has been opened between you & ', event.userid);
}
};
connection.onclose = function(event) {
if (!!connection.enableLogs) {
console.warn('Data connection has been closed between you & ', event.userid);
}
};
connection.onerror = function(error) {
if (!!connection.enableLogs) {
console.error(error.userid, 'data-error', error);
}
};
connection.onmessage = function(event) {
if (!!connection.enableLogs) {
console.debug('data-message', event.userid, event.data);
}
};
connection.send = function(data, remoteUserId) {
connection.peers.send(data, remoteUserId);
};
connection.close = connection.disconnect = connection.leave = function() {
connection.onbeforeunload(false, true);
};
connection.closeEntireSession = function(callback) {
callback = callback || function() {};
connection.socket.emit('close-entire-session', function looper() {
if (connection.getAllParticipants().length) {
setTimeout(looper, 100);
return;
}
connection.onEntireSessionClosed({
sessionid: connection.sessionid,
userid: connection.userid,
extra: connection.extra
});
connection.changeUserId(null, function() {
connection.close();
callback();
});
});
};
connection.onEntireSessionClosed = function(event) {
if (!connection.enableLogs) return;
console.info('Entire session is closed: ', event.sessionid, event.extra);
};
connection.onstream = function(e) {
var parentNode = connection.videosContainer;
parentNode.insertBefore(e.mediaElement, parentNode.firstChild);
var played = e.mediaElement.play();
if (typeof played !== 'undefined') {
played.catch(function() {
/*** iOS 11 doesn't allow automatic play and rejects ***/
}).then(function() {
setTimeout(function() {
e.mediaElement.play();
}, 2000);
});
return;
}
setTimeout(function() {
e.mediaElement.play();
}, 2000);
};
connection.onstreamended = function(e) {
if (!e.mediaElement) {
e.mediaElement = document.getElementById(e.streamid);
}
if (!e.mediaElement || !e.mediaElement.parentNode) {
return;
}
e.mediaElement.parentNode.removeChild(e.mediaElement);
};
connection.direction = 'many-to-many';
connection.removeStream = function(streamid, remoteUserId) {
var stream;
connection.attachStreams.forEach(function(localStream) {
if (localStream.id === streamid) {
stream = localStream;
}
});
if (!stream) {
console.warn('No such stream exist.', streamid);
return;
}
connection.peers.getAllParticipants().forEach(function(participant) {
if (remoteUserId && participant !== remoteUserId) {
return;
}
var user = connection.peers[participant];
try {
user.peer.removeStream(stream);
} catch (e) {}
});
connection.renegotiate();
};
connection.addStream = function(session, remoteUserId) {
if (!!session.getTracks) {
if (connection.attachStreams.indexOf(session) === -1) {
if (!session.streamid) {
session.streamid = session.id;
}
connection.attachStreams.push(session);
}
connection.renegotiate(remoteUserId);
return;
}
if (isData(session)) {
connection.renegotiate(remoteUserId);
return;
}
if (session.audio || session.video || session.screen) {
if (session.screen) {
if (DetectRTC.browser.name === 'Edge') {
navigator.getDisplayMedia({
video: true,
audio: isAudioPlusTab(connection)
}).then(function(screen) {
screen.isScreen = true;
mPeer.onGettingLocalMedia(screen);
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
connection.invokeGetUserMedia(null, function(stream) {
gumCallback(stream);
});
} else {
gumCallback(screen);
}
}, function(error) {
console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.');
});
} else {
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection),
video: true,
isScreen: true
}, function(stream) {
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
connection.invokeGetUserMedia(null, function(stream) {
gumCallback(stream);
});
} else {
gumCallback(stream);
}
});
}
} else if (session.audio || session.video) {
connection.invokeGetUserMedia(null, gumCallback);
}
}
function gumCallback(stream) {
if (session.streamCallback) {
session.streamCallback(stream);
}
connection.renegotiate(remoteUserId);
}
};
connection.invokeGetUserMedia = function(localMediaConstraints, callback, session) {
if (!session) {
session = connection.session;
}
if (!localMediaConstraints) {
localMediaConstraints = connection.mediaConstraints;
}
getUserMediaHandler({
onGettingLocalMedia: function(stream) {
var videoConstraints = localMediaConstraints.video;
if (videoConstraints) {
if (videoConstraints.mediaSource || videoConstraints.mozMediaSource) {
stream.isScreen = true;
} else if (videoConstraints.mandatory && videoConstraints.mandatory.chromeMediaSource) {
stream.isScreen = true;
}
}
if (!stream.isScreen) {
stream.isVideo = !!getTracks(stream, 'video').length;
stream.isAudio = !stream.isVideo && getTracks(stream, 'audio').length;
}
mPeer.onGettingLocalMedia(stream, function() {
if (typeof callback === 'function') {
callback(stream);
}
});
},
onLocalMediaError: function(error, constraints) {
mPeer.onLocalMediaError(error, constraints);
},
localMediaConstraints: localMediaConstraints || {
audio: session.audio ? localMediaConstraints.audio : false,
video: session.video ? localMediaConstraints.video : false
}
});
};
function applyConstraints(stream, mediaConstraints) {
if (!stream) {
if (!!connection.enableLogs) {
console.error('No stream to applyConstraints.');
}
return;
}
if (mediaConstraints.audio) {
getTracks(stream, 'audio').forEach(function(track) {
track.applyConstraints(mediaConstraints.audio);
});
}
if (mediaConstraints.video) {
getTracks(stream, 'video').forEach(function(track) {
track.applyConstraints(mediaConstraints.video);
});
}
}
connection.applyConstraints = function(mediaConstraints, streamid) {
if (!MediaStreamTrack || !MediaStreamTrack.prototype.applyConstraints) {
alert('track.applyConstraints is NOT supported in your browser.');
return;
}
if (streamid) {
var stream;
if (connection.streamEvents[streamid]) {
stream = connection.streamEvents[streamid].stream;
}
applyConstraints(stream, mediaConstraints);
return;
}
connection.attachStreams.forEach(function(stream) {
applyConstraints(stream, mediaConstraints);
});
};
function replaceTrack(track, remoteUserId, isVideoTrack) {
if (remoteUserId) {
mPeer.replaceTrack(track, remoteUserId, isVideoTrack);
return;
}
connection.peers.getAllParticipants().forEach(function(participant) {
mPeer.replaceTrack(track, participant, isVideoTrack);
});
}
connection.replaceTrack = function(session, remoteUserId, isVideoTrack) {
session = session || {};
if (!RTCPeerConnection.prototype.getSenders) {
connection.addStream(session);
return;
}
if (session instanceof MediaStreamTrack) {
replaceTrack(session, remoteUserId, isVideoTrack);
return;
}
if (session instanceof MediaStream) {
if (getTracks(session, 'video').length) {
replaceTrack(getTracks(session, 'video')[0], remoteUserId, true);
}
if (getTracks(session, 'audio').length) {
replaceTrack(getTracks(session, 'audio')[0], remoteUserId, false);
}
return;
}
if (isData(session)) {
throw 'connection.replaceTrack requires audio and/or video and/or screen.';
return;
}
if (session.audio || session.video || session.screen) {
if (session.screen) {
if (DetectRTC.browser.name === 'Edge') {
navigator.getDisplayMedia({
video: true,
audio: isAudioPlusTab(connection)
}).then(function(screen) {
screen.isScreen = true;
mPeer.onGettingLocalMedia(screen);
if ((session.audio || session.video) && !isAudioPlusTab(connection)) {
connection.invokeGetUserMedia(null, gumCallback);
} else {
gumCallback(screen);
}
}, function(error) {
console.error('Unable to capture screen on Edge. HTTPs and version 17+ is required.');
});
} else {
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection),
video: true,
isScreen: true
}, (session.audio || session.video) && !isAudioPlusTab(connection) ? connection.invokeGetUserMedia(null, gumCallback) : gumCallback);
}
} else if (session.audio || session.video) {
connection.invokeGetUserMedia(null, gumCallback);
}
}
function gumCallback(stream) {
connection.replaceTrack(stream, remoteUserId, isVideoTrack || session.video || session.screen);
}
};
connection.resetTrack = function(remoteUsersIds, isVideoTrack) {
if (!remoteUsersIds) {
remoteUsersIds = connection.getAllParticipants();
}
if (typeof remoteUsersIds == 'string') {
remoteUsersIds = [remoteUsersIds];
}
remoteUsersIds.forEach(function(participant) {
var peer = connection.peers[participant].peer;
if ((typeof isVideoTrack === 'undefined' || isVideoTrack === true) && peer.lastVideoTrack) {
connection.replaceTrack(peer.lastVideoTrack, participant, true);
}
if ((typeof isVideoTrack === 'undefined' || isVideoTrack === false) && peer.lastAudioTrack) {
connection.replaceTrack(peer.lastAudioTrack, participant, false);
}
});
};
connection.renegotiate = function(remoteUserId) {
if (remoteUserId) {
mPeer.renegotiatePeer(remoteUserId);
return;
}
connection.peers.getAllParticipants().forEach(function(participant) {
mPeer.renegotiatePeer(participant);
});
};
connection.setStreamEndHandler = function(stream, isRemote) {
if (!stream || !stream.addEventListener) return;
isRemote = !!isRemote;
if (stream.alreadySetEndHandler) {
return;
}
stream.alreadySetEndHandler = true;
var streamEndedEvent = 'ended';
if ('oninactive' in stream) {
streamEndedEvent = 'inactive';
}
stream.addEventListener(streamEndedEvent, function() {
if (stream.idInstance) {
currentUserMediaRequest.remove(stream.idInstance);
}
if (!isRemote) {
// reset attachStreams
var streams = [];
connection.attachStreams.forEach(function(s) {
if (s.id != stream.id) {
streams.push(s);
}
});
connection.attachStreams = streams;
}
// connection.renegotiate();
var streamEvent = connection.streamEvents[stream.streamid];
if (!streamEvent) {
streamEvent = {
stream: stream,
streamid: stream.streamid,
type: isRemote ? 'remote' : 'local',
userid: connection.userid,
extra: connection.extra,
mediaElement: connection.streamEvents[stream.streamid] ? connection.streamEvents[stream.streamid].mediaElement : null
};
}
if (isRemote && connection.peers[streamEvent.userid]) {
// reset remote "streams"
var peer = connection.peers[streamEvent.userid].peer;
var streams = [];
peer.getRemoteStreams().forEach(function(s) {
if (s.id != stream.id) {
streams.push(s);
}
});
connection.peers[streamEvent.userid].streams = streams;
}
if (streamEvent.userid === connection.userid && streamEvent.type === 'remote') {
return;
}
if (connection.peersBackup[streamEvent.userid]) {
streamEvent.extra = connection.peersBackup[streamEvent.userid].extra;
}
connection.onstreamended(streamEvent);
delete connection.streamEvents[stream.streamid];
}, false);
};
connection.onMediaError = function(error, constraints) {
if (!!connection.enableLogs) {
console.error(error, constraints);
}
};
connection.autoCloseEntireSession = false;
connection.filesContainer = connection.videosContainer = document.body || document.documentElement;
connection.isInitiator = false;
connection.shareFile = mPeer.shareFile;
if (typeof FileProgressBarHandler !== 'undefined') {
FileProgressBarHandler.handle(connection);
}
if (typeof TranslationHandler !== 'undefined') {
TranslationHandler.handle(connection);
}
connection.token = getRandomString;
connection.onNewParticipant = function(participantId, userPreferences) {
connection.acceptParticipationRequest(participantId, userPreferences);
};
connection.acceptParticipationRequest = function(participantId, userPreferences) {
if (userPreferences.successCallback) {
userPreferences.successCallback();
delete userPreferences.successCallback;
}
mPeer.createNewPeer(participantId, userPreferences);
};
if (typeof StreamsHandler !== 'undefined') {
connection.StreamsHandler = StreamsHandler;
}
connection.onleave = function(userid) {};
connection.invokeSelectFileDialog = function(callback) {
var selector = new FileSelector();
selector.accept = '*.*';
selector.selectSingleFile(callback);
};
connection.onmute = function(e) {
if (!e || !e.mediaElement) {
return;
}
if (e.muteType === 'both' || e.muteType === 'video') {
e.mediaElement.src = null;
var paused = e.mediaElement.pause();
if (typeof paused !== 'undefined') {
paused.then(function() {
e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png';
});
} else {
e.mediaElement.poster = e.snapshot || 'https://cdn.webrtc-experiment.com/images/muted.png';
}
} else if (e.muteType === 'audio') {
e.mediaElement.muted = true;
}
};
connection.onunmute = function(e) {
if (!e || !e.mediaElement || !e.stream) {
return;
}
if (e.unmuteType === 'both' || e.unmuteType === 'video') {
e.mediaElement.poster = null;
e.mediaElement.srcObject = e.stream;
e.mediaElement.play();
} else if (e.unmuteType === 'audio') {
e.mediaElement.muted = false;
}
};
connection.onExtraDataUpdated = function(event) {
event.status = 'online';
connection.onUserStatusChanged(event, true);
};
connection.getAllParticipants = function(sender) {
return connection.peers.getAllParticipants(sender);
};
if (typeof StreamsHandler !== 'undefined') {
StreamsHandler.onSyncNeeded = function(streamid, action, type) {
connection.peers.getAllParticipants().forEach(function(participant) {
mPeer.onNegotiationNeeded({
streamid: streamid,
action: action,
streamSyncNeeded: true,
type: type || 'both'
}, participant);
});
};
}
connection.connectSocket = function(callback) {
connectSocket(callback);
};
connection.closeSocket = function() {
try {
io.sockets = {};
} catch (e) {};
if (!connection.socket) return;
if (typeof connection.socket.disconnect === 'function') {
connection.socket.disconnect();
}
if (typeof connection.socket.resetProps === 'function') {
connection.socket.resetProps();
}
connection.socket = null;
};
connection.getSocket = function(callback) {
if (!callback && connection.enableLogs) {
console.warn('getSocket.callback paramter is required.');
}
callback = callback || function() {};
if (!connection.socket) {
connectSocket(function() {
callback(connection.socket);
});
} else {
callback(connection.socket);
}
return connection.socket; // callback is preferred over return-statement
};
connection.getRemoteStreams = mPeer.getRemoteStreams;
var skipStreams = ['selectFirst', 'selectAll', 'forEach'];
connection.streamEvents = {
selectFirst: function(options) {
return connection.streamEvents.selectAll(options)[0];
},
selectAll: function(options) {
if (!options) {
// default will always be all streams
options = {
local: true,
remote: true,
isScreen: true,
isAudio: true,
isVideo: true
};
}
if (options == 'local') {
options = {
local: true
};
}
if (options == 'remote') {
options = {
remote: true
};
}
if (options == 'screen') {
options = {
isScreen: true
};
}
if (options == 'audio') {
options = {
isAudio: true
};
}
if (options == 'video') {
options = {
isVideo: true
};
}
var streams = [];
Object.keys(connection.streamEvents).forEach(function(key) {
var event = connection.streamEvents[key];
if (skipStreams.indexOf(key) !== -1) return;
var ignore = true;
if (options.local && event.type === 'local') {
ignore = false;
}
if (options.remote && event.type === 'remote') {
ignore = false;
}
if (options.isScreen && event.stream.isScreen) {
ignore = false;
}
if (options.isVideo && event.stream.isVideo) {
ignore = false;
}
if (options.isAudio && event.stream.isAudio) {
ignore = false;
}
if (options.userid && event.userid === options.userid) {
ignore = false;
}
if (ignore === false) {
streams.push(event);
}
});
return streams;
}
};
connection.socketURL = '/'; // generated via config.json
connection.socketMessageEvent = 'RTCMultiConnection-Message'; // generated via config.json
connection.socketCustomEvent = 'RTCMultiConnection-Custom-Message'; // generated via config.json
connection.DetectRTC = DetectRTC;
connection.setCustomSocketEvent = function(customEvent) {
if (customEvent) {
connection.socketCustomEvent = customEvent;
}
if (!connection.socket) {
return;
}
connection.socket.emit('set-custom-socket-event-listener', connection.socketCustomEvent);
};
connection.getNumberOfBroadcastViewers = function(broadcastId, callback) {
if (!connection.socket || !broadcastId || !callback) return;
connection.socket.emit('get-number-of-users-in-specific-broadcast', broadcastId, callback);
};
connection.onNumberOfBroadcastViewersUpdated = function(event) {
if (!connection.enableLogs || !connection.isInitiator) return;
console.info('Number of broadcast (', event.broadcastId, ') viewers', event.numberOfBroadcastViewers);
};
connection.onUserStatusChanged = function(event, dontWriteLogs) {
if (!!connection.enableLogs && !dontWriteLogs) {
console.info(event.userid, event.status);
}
};
connection.getUserMediaHandler = getUserMediaHandler;
connection.multiPeersHandler = mPeer;
connection.enableLogs = true;
connection.setCustomSocketHandler = function(customSocketHandler) {
if (typeof SocketConnection !== 'undefined') {
SocketConnection = customSocketHandler;
}
};
// default value should be 15k because [old]Firefox's receiving limit is 16k!
// however 64k works chrome-to-chrome
connection.chunkSize = 40 * 1000;
connection.maxParticipantsAllowed = 1000;
// eject or leave single user
connection.disconnectWith = mPeer.disconnectWith;
// check if room exist on server
// we will pass roomid to the server and wait for callback (i.e. server's response)
connection.checkPresence = function(roomid, callback) {
roomid = roomid || connection.sessionid;
if (SocketConnection.name === 'SSEConnection') {
SSEConnection.checkPresence(roomid, function(isRoomExist, _roomid, extra) {
if (!connection.socket) {
if (!isRoomExist) {
connection.userid = _roomid;
}
connection.connectSocket(function() {
callback(isRoomExist, _roomid, extra);
});
return;
}
callback(isRoomExist, _roomid);
});
return;
}
if (!connection.socket) {
connection.connectSocket(function() {
connection.checkPresence(roomid, callback);
});
return;
}
connection.socket.emit('check-presence', roomid + '', function(isRoomExist, _roomid, extra) {
if (connection.enableLogs) {
console.log('checkPresence.isRoomExist: ', isRoomExist, ' roomid: ', _roomid);
}
callback(isRoomExist, _roomid, extra);
});
};
connection.onReadyForOffer = function(remoteUserId, userPreferences) {
connection.multiPeersHandler.createNewPeer(remoteUserId, userPreferences);
};
connection.setUserPreferences = function(userPreferences) {
if (connection.dontAttachStream) {
userPreferences.dontAttachLocalStream = true;
}
if (connection.dontGetRemoteStream) {
userPreferences.dontGetRemoteStream = true;
}
return userPreferences;
};
connection.updateExtraData = function() {
connection.socket.emit('extra-data-updated', connection.extra);
};
connection.enableScalableBroadcast = false;
connection.maxRelayLimitPerUser = 3; // each broadcast should serve only 3 users
connection.dontCaptureUserMedia = false;
connection.dontAttachStream = false;
connection.dontGetRemoteStream = false;
connection.onReConnecting = function(event) {
if (connection.enableLogs) {
console.info('ReConnecting with', event.userid, '...');
}
};
connection.beforeAddingStream = function(stream) {
return stream;
};
connection.beforeRemovingStream = function(stream) {
return stream;
};
if (typeof isChromeExtensionAvailable !== 'undefined') {
connection.checkIfChromeExtensionAvailable = isChromeExtensionAvailable;
}
if (typeof isFirefoxExtensionAvailable !== 'undefined') {
connection.checkIfChromeExtensionAvailable = isFirefoxExtensionAvailable;
}
if (typeof getChromeExtensionStatus !== 'undefined') {
connection.getChromeExtensionStatus = getChromeExtensionStatus;
}
connection.modifyScreenConstraints = function(screen_constraints) {
return screen_constraints;
};
connection.onPeerStateChanged = function(state) {
if (connection.enableLogs) {
if (state.iceConnectionState.search(/closed|failed/gi) !== -1) {
console.error('Peer connection is closed between you & ', state.userid, state.extra, 'state:', state.iceConnectionState);
}
}
};
connection.isOnline = true;
listenEventHandler('online', function() {
connection.isOnline = true;
});
listenEventHandler('offline', function() {
connection.isOnline = false;
});
connection.isLowBandwidth = false;
if (navigator && navigator.connection && navigator.connection.type) {
connection.isLowBandwidth = navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g) !== -1;
if (connection.isLowBandwidth) {
connection.bandwidth = {
audio: false,
video: false,
screen: false
};
if (connection.mediaConstraints.audio && connection.mediaConstraints.audio.optional && connection.mediaConstraints.audio.optional.length) {
var newArray = [];
connection.mediaConstraints.audio.optional.forEach(function(opt) {
if (typeof opt.bandwidth === 'undefined') {
newArray.push(opt);
}
});
connection.mediaConstraints.audio.optional = newArray;
}
if (connection.mediaConstraints.video && connection.mediaConstraints.video.optional && connection.mediaConstraints.video.optional.length) {
var newArray = [];
connection.mediaConstraints.video.optional.forEach(function(opt) {
if (typeof opt.bandwidth === 'undefined') {
newArray.push(opt);
}
});
connection.mediaConstraints.video.optional = newArray;
}
}
}
connection.getExtraData = function(remoteUserId, callback) {
if (!remoteUserId) throw 'remoteUserId is required.';
if (typeof callback === 'function') {
connection.socket.emit('get-remote-user-extra-data', remoteUserId, function(extra, remoteUserId, error) {
callback(extra, remoteUserId, error);
});
return;
}
if (!connection.peers[remoteUserId]) {
if (connection.peersBackup[remoteUserId]) {
return connection.peersBackup[remoteUserId].extra;
}
return {};
}
return connection.peers[remoteUserId].extra;
};
if (!!forceOptions.autoOpenOrJoin) {
connection.openOrJoin(connection.sessionid);
}
connection.onUserIdAlreadyTaken = function(useridAlreadyTaken, yourNewUserId) {
// via #683
connection.close();
connection.closeSocket();
connection.isInitiator = false;
connection.userid = connection.token();
connection.join(connection.sessionid);
if (connection.enableLogs) {
console.warn('Userid already taken.', useridAlreadyTaken, 'Your new userid:', connection.userid);
}
};
connection.trickleIce = true;
connection.version = '3.7.0';
connection.onSettingLocalDescription = function(event) {
if (connection.enableLogs) {
console.info('Set local description for remote user', event.userid);
}
};
connection.resetScreen = function() {
sourceId = null;
if (DetectRTC && DetectRTC.screen) {
delete DetectRTC.screen.sourceId;
}
currentUserMediaRequest = {
streams: [],
mutex: false,
queueRequests: []
};
};
// if disabled, "event.mediaElement" for "onstream" will be NULL
connection.autoCreateMediaElement = true;
// set password
connection.password = null;
// set password
connection.setPassword = function(password, callback) {
callback = callback || function() {};
if (connection.socket) {
connection.socket.emit('set-password', password, callback);
} else {
connection.password = password;
callback(true, connection.sessionid, null);
}
};
connection.onSocketDisconnect = function(event) {
if (connection.enableLogs) {
console.warn('socket.io connection is closed');
}
};
connection.onSocketError = function(event) {
if (connection.enableLogs) {
console.warn('socket.io connection is failed');
}
};
// error messages
connection.errors = {
ROOM_NOT_AVAILABLE: 'Room not available',
INVALID_PASSWORD: 'Invalid password',
USERID_NOT_AVAILABLE: 'User ID does not exist',
ROOM_PERMISSION_DENIED: 'Room permission denied',
ROOM_FULL: 'Room full',
DID_NOT_JOIN_ANY_ROOM: 'Did not join any room yet',
INVALID_SOCKET: 'Invalid socket',
PUBLIC_IDENTIFIER_MISSING: 'publicRoomIdentifier is required',
INVALID_ADMIN_CREDENTIAL: 'Invalid username or password attempted'
};
})(this);
};
if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
module.exports = exports = RTCMultiConnection;
}
if (typeof define === 'function' && define.amd) {
define('RTCMultiConnection', [], function() {
return RTCMultiConnection;
});
}