rtcmulticonnection
Version:
RTCMultiConnection is a WebRTC JavaScript wrapper library runs top over RTCPeerConnection API to support all possible peer-to-peer features.
1,355 lines (1,132 loc) • 67.6 kB
JavaScript
(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]) {
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);
}
connection.streamEvents[stream.streamid] = {
stream: stream,
type: 'local',
mediaElement: mediaElement,
userid: connection.userid,
extra: connection.extra,
streamid: stream.streamid,
isAudioMuted: true
};
setHarkEvents(connection, connection.streamEvents[stream.streamid]);
setMuteHandlers(connection, connection.streamEvents[stream.streamid]);
connection.onstream(connection.streamEvents[stream.streamid]);
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) {
remoteUserId = remoteUserId || message.remoteUserId;
message = message || '';
connectSocket(function() {
connection.socket.emit(connection.socketMessageEvent, typeof message.password !== 'undefined' ? message : {
remoteUserId: remoteUserId,
message: message,
sender: connection.userid
}, callback || function() {});
});
};
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
// 2nd paramter can be either password or a callback function
// 3rd paramter is a callback function
connection.openOrJoin = function(roomid, password, callback) {
callback = callback || function() {};
connection.checkPresence(roomid, function(isRoomExist, roomid) {
// i.e. 2nd parameter is a callback function
if (typeof password === 'function' && typeof password !== 'undefined') {
callback = password; // switch callback functions
password = null;
}
if (!password && !!connection.password) {
password = connection.password;
}
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,
password: password || false
};
beforeJoin(connectionDescription.message, function() {
joinRoom(connectionDescription, password, function() {});
});
return;
}
connection.waitingForLocalMedia = true;
connection.isInitiator = true;
// var oldUserId = connection.userid;
// connection.userid =
connection.sessionid = roomid || connection.sessionid;
// connection.userid += '';
// connection.socket.emit('changed-uuid', connection.userid);
if (isData(connection.session)) {
openRoom(callback, password);
return;
}
connection.captureUserMedia(function() {
openRoom(callback, password);
});
});
};
// don't allow someone to join this person until he has the media
connection.waitingForLocalMedia = false;
connection.open = function(roomid, isPublicModerator, callback) {
connection.waitingForLocalMedia = true;
connection.isInitiator = true;
callback = callback || function() {};
if (typeof isPublicModerator === 'function') {
callback = isPublicModerator;
isPublicModerator = false;
}
// var oldUserId = connection.userid;
// connection.userid =
connection.sessionid = roomid || connection.sessionid;
// connection.userid += '';
connectSocket(function() {
// connection.socket.emit('changed-uuid', connection.userid);
if (isPublicModerator == true) {
connection.becomePublicModerator();
}
if (isData(connection.session)) {
openRoom(callback, connection.password);
return;
}
connection.captureUserMedia(function() {
openRoom(callback, connection.password);
});
});
};
connection.becomePublicModerator = function() {
if (!connection.isInitiator) return;
connection.socket.emit('become-a-public-moderator');
};
connection.dontMakeMeModerator = function() {
connection.socket.emit('dont-make-me-moderator');
};
// 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 = connection.connect = 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,
password: connection.password || false
};
beforeJoin(connectionDescription.message, function() {
connectSocket(function() {
joinRoom(connectionDescription, connection.password, cb);
});
});
return connectionDescription;
};
function joinRoom(connectionDescription, password, cb) {
if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) {
password = null;
}
connection.socket.emit('join-room', {
sessionid: connection.sessionid,
session: connection.session,
mediaConstraints: connection.mediaConstraints,
sdpConstraints: connection.sdpConstraints,
streams: getStreamInfoForAdmin(),
extra: connection.extra,
password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.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);
cb();
}
if (isRoomJoined === false) {
if (connection.enableLogs) {
console.warn('isRoomJoined: ', error, ' roomid: ', connection.sessionid);
}
// retry after 3 seconds
setTimeout(function() {
joinRoom(connectionDescription, password, cb);
}, 3000);
}
});
}
function openRoom(callback, password) {
if (password && (typeof password === 'function' || password.prototype || typeof password === 'object')) {
password = null;
}
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,
password: typeof password !== 'undefined' && typeof password !== 'object' ? (password || onnection.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);
}
}
});
}
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.getScreenConstraints(function(error, screen_constraints) {
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false,
video: screen_constraints,
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.getScreenConstraints(function(error, screen_constraints) {
if (error) {
throw error;
}
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false,
video: screen_constraints,
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;
}
if (connection.isInitiator === true) {
connection.dontMakeMeModerator();
}
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) {
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.rtcpMuxPolicy = 'require'; // "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.getAudioTracks) {
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.getScreenConstraints(function(error, screen_constraints) {
if (error) {
if (error === 'PermissionDeniedError') {
if (session.streamCallback) {
session.streamCallback(null);
}
if (connection.enableLogs) {
console.error('User rejected to share his screen.');
}
return;
}
return alert(error);
}
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false,
video: screen_constraints,
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 = stream.getVideoTracks().length;
stream.isAudio = !stream.isVideo && stream.getAudioTracks().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) {
stream.getAudioTracks().forEach(function(track) {
track.applyConstraints(mediaConstraints.audio);
});
}
if (mediaConstraints.video) {
stream.getVideoTracks().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 (session.getVideoTracks().length) {
replaceTrack(session.getVideoTracks()[0], remoteUserId, true);
}
if (session.getAudioTracks().length) {
replaceTrack(session.getAudioTracks()[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.getScreenConstraints(function(error, screen_constraints) {
if (error) {
return alert(error);
}
connection.invokeGetUserMedia({
audio: isAudioPlusTab(connection) ? getAudioScreenConstraints(screen_constraints) : false,
video: screen_constraints,
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 = connec