5857 lines
213 KiB
JavaScript
5857 lines
213 KiB
JavaScript
'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;
|
|
});
|
|
}
|