+
+
+
+
+ © Laurent Di Biase - 2020
+
+
+
\ No newline at end of file
diff --git a/assets/listen-broadcast.js b/assets/listen-broadcast.js
new file mode 100644
index 0000000..91b6021
--- /dev/null
+++ b/assets/listen-broadcast.js
@@ -0,0 +1,208 @@
+// ......................................................
+// .......................UI Code........................
+// ......................................................
+
+
+document.getElementById('join-room').onclick = function() {
+ //disableInputButtons();
+
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: true,
+ OfferToReceiveVideo: false
+ };
+ connection.join(roomid);
+};
+
+document.getElementById('leave-room').onclick = function() {
+ document.getElementById('join-room').disabled = false;
+ connection.getAllParticipants().forEach(function(participantId) {
+ connection.disconnectWith(participantId);
+ });
+ // close the URL display
+ document.getElementById('room-urls').style.display = "none";
+ // close socket.io connection
+ connection.closeSocket();
+};
+
+// ......................................................
+// ..................RTCMultiConnection Code.............
+// ......................................................
+
+
+var connection = new RTCMultiConnection();
+
+connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/';
+
+
+
+// keep room opened even if owner leaves
+connection.autoCloseEntireSession = true;
+
+connection.session = {
+ audio: true,
+ video: false,
+ oneway: true
+};
+
+connection.mediaConstraints = {
+ audio: {
+ sampleRate: 48000,
+ channelCount: 2,
+ volume: 1.0,
+ echoCancellation:false,
+ autoGainControl:false,
+ noiseSuppression:false,
+ highPassFilter:false
+ },
+ video: false
+};
+
+connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: false,
+ OfferToReceiveVideo: false
+};
+
+// https://www.rtcmulticonnection.org/docs/iceServers/
+// use your own TURN-server here!
+connection.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',
+ ]
+}];
+
+connection.audiosContainer = document.getElementById('audios-container');
+
+connection.onstream = function(event) {
+ var existing = document.getElementById(event.streamid);
+ if(existing && existing.parentNode) {
+ existing.parentNode.removeChild(existing);
+ }
+
+ event.mediaElement.removeAttribute('src');
+ event.mediaElement.removeAttribute('srcObject');
+ event.mediaElement.muted = true;
+ event.mediaElement.volume = 0;
+
+ var audio = document.createElement('audio');
+
+ if(event.type === 'local') {
+ audio.volume = 0;
+ try {
+ audio.setAttributeNode(document.createAttribute('muted'));
+ } catch (e) {
+ audio.setAttribute('muted', true);
+ }
+ }
+ audio.srcObject = event.stream;
+
+ var mediaElement = getHTMLMediaElement(audio, {
+ title: event.userid,
+ showOnMouseEnter: false
+ });
+
+ connection.audiosContainer.appendChild(mediaElement);
+
+ setTimeout(function() {
+ mediaElement.media.play();
+ }, 5000);
+
+ mediaElement.id = event.streamid;
+};
+
+connection.onstreamended = function(event) {
+ var mediaElement = document.getElementById(event.streamid);
+ if (mediaElement) {
+ mediaElement.parentNode.removeChild(mediaElement);
+
+ if(event.userid === connection.sessionid && !connection.isInitiator) {
+ alert('Broadcast is ended. We will reload this page to clear the cache.');
+ location.reload();
+ }
+ }
+};
+
+
+///*************Socket.io*****************
+/*var socket = connection.getSocket();
+
+socket.on('buttonEvent', roomid );
+
+Storage();
+
+*/
+
+// ......................................................
+// ......................Handling Room-ID................
+// ......................................................
+
+function showRoomURL(roomid) {
+ var roomHashURL = '#' + roomid;
+ var roomQueryStringURL = '?roomid=' + roomid;
+
+ var html = '
Unique URL for your room: ';
+
+ html += 'Hash URL:
' + roomHashURL + ' ';
+ html += '
';
+ html += 'QueryString URL:
' + roomQueryStringURL + ' ';
+
+ var roomURLsDiv = document.getElementById('room-urls');
+ roomURLsDiv.innerHTML = html;
+
+ roomURLsDiv.style.display = 'block';
+}
+
+(function() {
+ var params = {},
+ r = /([^&=]+)=?([^&]*)/g;
+
+ function d(s) {
+ return decodeURIComponent(s.replace(/\+/g, ' '));
+ }
+ var match, search = window.location.search;
+ while (match = r.exec(search.substring(1)))
+ params[d(match[1])] = d(match[2]);
+ window.params = params;
+})();
+
+var roomid = '';
+if (localStorage.getItem(connection.socketMessageEvent)) {
+ roomid = localStorage.getItem(connection.socketMessageEvent);
+} else {
+ roomid = connection.token();
+}
+
+function Storage() {
+ localStorage.setItem(connection.socketMessageEvent, roomid);
+};
+
+var hashString = location.hash.replace('#', '');
+if (hashString.length && hashString.indexOf('comment-') == 0) {
+ hashString = '';
+}
+
+var roomid = params.roomid;
+if (!roomid && hashString.length) {
+ roomid = hashString;
+}
+
+if (roomid && roomid.length) {
+ localStorage.setItem(connection.socketMessageEvent, roomid);
+
+ // auto-join-room
+ (function reCheckRoomPresence() {
+ connection.checkPresence(roomid, function(isRoomExist) {
+ if (isRoomExist) {
+ connection.join(roomid);
+ return;
+ }
+
+ setTimeout(reCheckRoomPresence, 5000);
+ });
+ })();
+
+}
+
+
diff --git a/assets/listen.js b/assets/listen.js
new file mode 100644
index 0000000..90e291a
--- /dev/null
+++ b/assets/listen.js
@@ -0,0 +1,359 @@
+let joinRoom = document.getElementById('join-room');
+let leaveRoom = document.getElementById('leave-room');
+
+let roomID;
+// ......................................................
+// .......................UI Code........................
+// ......................................................
+
+
+
+
+
+leaveRoom.addEventListener('click',function() {
+ document.getElementById('join-room').disabled = false;
+ connection.getAllParticipants().forEach(function(participantId) {
+ connection.disconnectWith(participantId);
+ });
+ // close the URL display
+ document.getElementById('room-urls').style.display = "none";
+ // close socket.io connection
+ connection.closeSocket();
+});
+
+// ......................................................
+// ..................RTCMultiConnection Code.............
+// ......................................................
+
+let connection = new RTCMultiConnection();
+
+// recording is disabled because it is resulting for browser-crash
+// if you enable below line, please also uncomment "RecordRTC.js" in html section.
+var enableRecordings = false;
+
+//var connection = new RTCMultiConnection();
+
+// https://www.rtcmulticonnection.org/docs/iceServers/
+// use your own TURN-server here!
+connection.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',
+ ]
+}];
+
+// its mandatory in v3
+connection.enableScalableBroadcast = true;
+
+// each relaying-user should serve only 1 users
+connection.maxRelayLimitPerUser = 1;
+
+// we don't need to keep room-opened
+// scalable-broadcast.js will handle stuff itself.
+connection.autoCloseEntireSession = true;
+
+// by default, socket.io server is assumed to be deployed on your own URL
+//connection.socketURL = '/';
+
+// comment-out below line if you do not have your own socket.io server
+connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/';
+
+connection.socketMessageEvent = 'scalable-media-broadcast-demo';
+
+//document.getElementById('broadcast-id').value = connection.userid;
+
+// user need to connect server, so that others can reach him.
+connection.connectSocket(function(socket) {
+ socket.on('logs', function(log) {
+ document.querySelector('h3').innerHTML = log.replace(//g, '___').replace(/----/g, '(
').replace(/___/g, ' )');
+ });
+
+ // this event is emitted when a broadcast is already created.
+ socket.on('join-broadcaster', function(hintsToJoinBroadcast) {
+ console.log('join-broadcaster', hintsToJoinBroadcast);
+
+ connection.session = hintsToJoinBroadcast.typeOfStreams;
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: !!connection.session.video,
+ OfferToReceiveAudio: !!connection.session.audio
+ };
+ connection.broadcastId = hintsToJoinBroadcast.broadcastId;
+ connection.join(hintsToJoinBroadcast.userid);
+ });
+
+ socket.on('rejoin-broadcast', function(broadcastId) {
+ console.log('rejoin-broadcast', broadcastId);
+
+ connection.attachStreams = [];
+ socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) {
+ if (!isBroadcastExists) {
+ // the first person (i.e. real-broadcaster) MUST set his user-id
+ connection.userid = broadcastId;
+ }
+
+ socket.emit('join-broadcast', {
+ broadcastId: broadcastId,
+ userid: connection.userid,
+ typeOfStreams: connection.session
+ });
+ });
+ });
+
+ socket.on('broadcast-stopped', function(broadcastId) {
+ // alert('Broadcast has been stopped.');
+ // location.reload();
+ console.error('broadcast-stopped', broadcastId);
+ alert('This broadcast has been stopped.');
+ });
+/*
+ // this event is emitted when a broadcast is absent.
+ socket.on('start-broadcasting', function(typeOfStreams) {
+ console.log('start-broadcasting', typeOfStreams);
+
+ // host i.e. sender should always use this!
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: false,
+ OfferToReceiveAudio: false
+ };
+ connection.session = typeOfStreams;
+
+ // "open" method here will capture media-stream
+ // we can skip this function always; it is totally optional here.
+ // we can use "connection.getUserMediaHandler" instead
+ connection.open(connection.userid);
+ });*/
+});
+
+window.onbeforeunload = function() {
+ // Firefox is ugly.
+ document.getElementById('join-room').disabled = false;
+};
+
+//var videoPreview = document.getElementById('video-preview');
+var audioMonitoring = document.getElementById('audio-monitoring');
+
+connection.onstream = function(event) {
+ /*if (connection.isInitiator && event.type !== 'local') {
+ return;
+ }
+
+ connection.isUpperUserLeft = false;
+ audioMonitoring.srcObject = event.stream;
+ audioMonitoring.play();
+
+ audioMonitoring.userid = event.userid;
+
+ if (event.type === 'local') {
+ audioMonitoring.muted = true;
+ }*/
+
+ if (connection.isInitiator == false && event.type === 'remote') {
+ // he is merely relaying the media
+ connection.dontCaptureUserMedia = true;
+ connection.attachStreams = [event.stream];
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: false,
+ OfferToReceiveVideo: false
+ };
+
+ connection.getSocket(function(socket) {
+ socket.emit('can-relay-broadcast');
+
+ if (connection.DetectRTC.browser.name === 'Chrome') {
+ connection.getAllParticipants().forEach(function(p) {
+ if (p + '' != event.userid + '') {
+ var peer = connection.peers[p].peer;
+ peer.getLocalStreams().forEach(function(localStream) {
+ peer.removeStream(localStream);
+ });
+ event.stream.getTracks().forEach(function(track) {
+ peer.addTrack(track, event.stream);
+ });
+ connection.dontAttachStream = true;
+ connection.renegotiate(p);
+ connection.dontAttachStream = false;
+ }
+ });
+ }
+
+ if (connection.DetectRTC.browser.name === 'Firefox') {
+ // Firefox is NOT supporting removeStream method
+ // that's why using alternative hack.
+ // NOTE: Firefox seems unable to replace-tracks of the remote-media-stream
+ // need to ask all deeper nodes to rejoin
+ connection.getAllParticipants().forEach(function(p) {
+ if (p + '' != event.userid + '') {
+ connection.replaceTrack(event.stream, p);
+ }
+ });
+ }
+
+ // Firefox seems UN_ABLE to record remote MediaStream
+ // WebAudio solution merely records audio
+ // so recording is skipped for Firefox.
+ if (connection.DetectRTC.browser.name === 'Chrome') {
+ repeatedlyRecordStream(event.stream);
+ }
+ });
+ }
+
+ // to keep room-id in cache
+ localStorage.setItem(connection.socketMessageEvent, connection.sessionid);
+};
+
+// ask node.js server to look for a broadcast
+// if broadcast is available, simply join it. i.e. "join-broadcaster" event should be emitted.
+// if broadcast is absent, simply create it. i.e. "start-broadcasting" event should be fired.
+connection.socket.on('clickedReceived', function() {
+ //disableInputButtons();
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: true,
+ OfferToReceiveVideo: false
+ };
+ connection.join(roomID.value);
+ socket.emit('confirmation', function(){
+ console.log('Click bien reçu !')
+ })
+
+
+ var broadcastId = roomID.value;
+ if (broadcastId.replace(/^\s+|\s+$/g, '').length <= 0) {
+ alert('Please enter broadcast-id');
+ document.getElementById('broadcast-id').focus();
+ }
+
+ document.getElementById('join-room').disabled = true;
+
+ connection.extra.broadcastId = broadcastId;
+
+ connection.session = {
+ audio: true,
+ video: false,
+ oneway: true
+ };
+
+ connection.mediaConstraints = {
+ audio: {
+ sampleRate: 48000,
+ channelCount: 2,
+ volume: 1.0,
+ echoCancellation:false,
+ autoGainControl:false,
+ noiseSuppression:false,
+ highPassFilter:false
+ },
+ video: false
+ };
+
+ connection.getSocket(function(socket) {
+ socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) {
+ if (!isBroadcastExists) {
+ // the first person (i.e. real-broadcaster) MUST set his user-id
+ connection.userid = broadcastId;
+ }
+
+ console.log('check-broadcast-presence', broadcastId, isBroadcastExists);
+
+ socket.emit('join-broadcast', {
+ broadcastId: broadcastId,
+ userid: connection.userid,
+ typeOfStreams: connection.session
+ });
+ });
+ });
+
+});
+
+connection.onstreamended = function() {};
+
+connection.onleave = function(event) {
+ if (event.userid !== audioMonitoring.userid) return;
+
+ connection.getSocket(function(socket) {
+ socket.emit('can-not-relay-broadcast');
+
+ connection.isUpperUserLeft = true;
+
+ if (allRecordedBlobs.length) {
+ // playing lats recorded blob
+ var lastBlob = allRecordedBlobs[allRecordedBlobs.length - 1];
+ audioMonitoring.src = URL.createObjectURL(lastBlob);
+ audioMonitoring.play();
+ allRecordedBlobs = [];
+ } else if (connection.currentRecorder) {
+ var recorder = connection.currentRecorder;
+ connection.currentRecorder = null;
+ recorder.stopRecording(function() {
+ if (!connection.isUpperUserLeft) return;
+
+ audioMonitoring.src = URL.createObjectURL(recorder.getBlob());
+ audioMonitoring.play();
+ });
+ }
+
+ if (connection.currentRecorder) {
+ connection.currentRecorder.stopRecording();
+ connection.currentRecorder = null;
+ }
+ });
+};
+
+var allRecordedBlobs = [];
+
+function repeatedlyRecordStream(stream) {
+ if (!enableRecordings) {
+ return;
+ }
+
+ connection.currentRecorder = RecordRTC(stream, {
+ type: 'audio'
+ });
+
+ connection.currentRecorder.startRecording();
+
+ setTimeout(function() {
+ if (connection.isUpperUserLeft || !connection.currentRecorder) {
+ return;
+ }
+
+ connection.currentRecorder.stopRecording(function() {
+ allRecordedBlobs.push(connection.currentRecorder.getBlob());
+
+ if (connection.isUpperUserLeft) {
+ return;
+ }
+
+ connection.currentRecorder = null;
+ repeatedlyRecordStream(stream);
+ });
+ }, 30 * 1000); // 30-seconds
+};
+
+function disableInputButtons() {
+ document.getElementById('open-or-join').disabled = true;
+ document.getElementById('broadcast-id').disabled = true;
+}
+
+// ......................................................
+// ......................Handling broadcast-id................
+// ......................................................
+
+var broadcastId = '';
+if (localStorage.getItem(connection.socketMessageEvent)) {
+ broadcastId = localStorage.getItem(connection.socketMessageEvent);
+} else {
+ broadcastId = connection.token();
+}
+broadcastId.value = broadcastId;
+
+// below section detects how many users are viewing your broadcast
+
+connection.onNumberOfBroadcastViewersUpdated = function(event) {
+ if (!connection.isInitiator) return;
+
+ document.getElementById('broadcast-viewers-counter').innerHTML = 'Number of broadcast viewers:
' + event.numberOfBroadcastViewers + ' ';
+};
+
+
diff --git a/assets/play.js b/assets/play.js
new file mode 100644
index 0000000..7ad80de
--- /dev/null
+++ b/assets/play.js
@@ -0,0 +1,498 @@
+/*This is the part of code give here:
+https://github.com/muaz-khan/RTCMultiConnection/blob/master/docs/getting-started.md
+for
+"Getting Started from Scratch"
+
+and from this exemple with room ID and chat box:
+https://jsfiddle.net/zd9Lsdfk/50/
+*/
+
+
+const timeDisplay = document.querySelector("p");
+const startButton = document.getElementById("open-room");
+const stopButton = document.getElementById("leave-room");
+//const sendButton = document.getElementById("send-message");
+
+let localAudio = document.querySelector("#localAudio");
+let localContainer = document.getElementById("localContainer");
+let remoteContainer = document.getElementById("remoteContainer");
+let roomID = document.getElementById("room-id");
+
+let socket;
+
+let connection = new RTCMultiConnection();
+
+
+// ......................................................
+// .......................UI Code........................
+// ......................................................
+
+startButton.addEventListener('click', function() {
+ disableInputButtons();
+ connection.openOrJoin(roomID.value, function(isRoomExist, roomid, socket) {
+
+ if (isRoomExist === false && connection.isInitiator === true) {
+ // if room doesn't exist, it means that current user will create the room
+ showRoomURL(roomid);
+ }
+
+ if(isRoomExist) {
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: true,
+ OfferToReceiveVideo: false
+ };
+ }
+
+ showRoomURL(connection.sessionid);
+
+
+ connection.socket.emit('clickedSend', roomID);
+
+ connection.socket.on('broadcast-stopped', function(broadcastId) {
+ // alert('Broadcast has been stopped.');
+ // location.reload();
+ console.error('broadcast-stopped', broadcastId);
+ alert('This broadcast has been stopped.');
+ });
+
+ // this event is emitted when a broadcast is absent.
+ connection.socket.on('start-broadcasting', function(typeOfStreams) {
+ console.log('start-broadcasting', typeOfStreams);
+
+ // host i.e. sender should always use this!
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: false,
+ OfferToReceiveAudio: false
+ };
+ connection.session = typeOfStreams;
+
+ // "open" method here will capture media-stream
+ // we can skip this function always; it is totally optional here.
+ // we can use "connection.getUserMediaHandler" instead
+ connection.open(connection.userid);
+ });
+ });
+});
+
+
+
+stopButton.addEventListener('click',function() {
+ startButton.disabled = false;
+ connection.getAllParticipants().forEach(function(participantId) {
+ connection.disconnectWith(participantId);
+ });
+ // close the URL display
+ document.getElementById('room-urls').style.display = "none";
+ // close socket.io connection
+ connection.closeSocket();
+});
+
+
+
+
+// ......................................................
+// .....................Socket.io........................
+// ......................................................
+
+
+
+/*
+
+/// connection.socket:
+
+ex 1:
+connection.open('roomid', function() {
+ connection.socket.emit('clicked', roomID);
+
+});
+
+ex 2:
+
+connection.onstream = function(event) {
+ if(event.type === 'remote') {
+ connection.socket.emit('get-remote-user-extra-data', event.userid, function(extra) {
+ alert( extra.joinTime );
+ });
+ }
+}:
+///
+
+///This method allows you set custom socket listener before starting or joining a room.
+
+connection.socketCustomEvent = 'abcdef';
+connection.openOrJoin('roomid', function() {
+ connection.socket.on(connection.socketCustomEvent, function(message) {
+ alert(message);
+ });
+
+ connection.socket.emit(connection.socketCustomEvent, 'My userid is: ' + connection.userid);
+});
+
+
+//Pour récupérer le nom de chaque participant
+//This method allows you set custom socket listeners anytime, during a live session.
+
+connection.setCustomSocketEvent('abcdef');
+connection.socket.on('abcdef', function(message) {
+ alert(message);
+});
+
+connection.socket.emit('abcdef', 'My userid is: ' + connection.userid);
+
+/////////////////RTCMutliconnection.js/////////////
+
+ //Connexion automatique à l'ouverture de la page
+ connection.socket.on('connect', function() {
+ if (connection.enableLogs) {
+ console.info('socket.io connection is opened.');
+ }
+ //fait quelques choses
+ });
+ // A l'ouverture de la session envoie des infos
+ 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);
+ }
+ });
+ }
+*/
+
+
+// ......................................................
+// ..................RTCMultiConnection Code.............
+// ......................................................
+
+
+//WebRTC Supported Detection
+if (connection.DetectRTC.isWebRTCSupported === false) {
+ alert('Please try a WebRTC compatible web browser e.g. Chrome, Firefox or Opera.');
+}
+
+
+connection.enableFileSharing = true; // by default, it is "false".
+
+// keep room opened even if owner leaves
+connection.autoCloseEntireSession = true;
+
+// this line is VERY_important
+connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/';
+
+
+connection.onEntireSessionClosed = function(event) {
+ console.info('Entire session is closed: ', event.sessionid, event.extra);
+};
+
+connection.userid = 'unique-userid' || "User" + Math.ceil(Math.random() * 100);
+
+// all below lines are optional; however recommended.
+connection.session = {
+ audio: true,
+ video: false,
+ data: true
+};
+
+// allow 6 users
+connection.maxParticipantsAllowed = 7;
+
+connection.onRoomFull = function(roomid) {
+ alert('Room is full.');
+};
+
+connection.mediaConstraints = {
+ audio: {
+ sampleRate: 48000,
+ channelCount: 2,
+ volume: 1.0,
+ echoCancellation:false,
+ autoGainControl:false,
+ noiseSuppression:false,
+ highPassFilter:false
+ },
+ video: false
+};
+
+connection.sdpConstraints.mandatory = {
+ OfferToReceiveAudio: {
+ sampleRate: 48000,
+ channelCount: 2,
+ volume: 1.0,
+ echoCancellation:false,
+ autoGainControl:false,
+ noiseSuppression:false,
+ highPassFilter:false
+ },
+ OfferToReceiveVideo: false
+};
+
+// https://www.rtcmulticonnection.org/docs/iceServers/
+// use your own TURN-server here!
+connection.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',
+ ]
+}];
+
+connection.onmessage = appendDIV;
+connection.filesContainer = document.getElementById('file-container');
+
+
+connection.onopen = function() {
+ document.getElementById('input-text-chat').disabled = false;
+ document.getElementById('share-file').disabled = false;
+
+};
+
+connection.onclose = connection.onleave = function(event) {
+ alert(event.userid + ' leave the session');
+};
+
+function disableInputButtons() {
+ roomID.onkeyup();
+ startButton.disabled = true;
+ stopButton.disabled = false;
+ roomID.disabled = true;
+}
+
+
+connection.onstream = function(event) {
+ var audioElement = event.mediaElement;
+ //Create each element type into a particuliar container
+ if (event.type === 'local') {
+ localContainer.appendChild(audioElement);
+ }
+ if (event.type === 'remote') {
+ remoteContainer.appendChild(audioElement);
+ alert(event.userid + ' join the session');
+ }
+};
+
+connection.onstreamended = function(event) {
+ var mediaElement = document.getElementById(event.streamid);
+ if (mediaElement) {
+ mediaElement.parentNode.removeChild(mediaElement);
+ }
+};
+
+// ......................................................
+// .................Select Input Part....................
+// ......................................................
+/*
+connection.onMediaError = function(e) {
+ if (e.message === 'Concurrent mic process limit.') {
+ if (DetectRTC.audioInputDevices.length <= 1) {
+ alert('Please select external microphone. Check github issue number 483.');
+ return;
+ }
+
+ var secondaryMic = DetectRTC.audioInputDevices[1].deviceId;
+ connection.mediaConstraints.audio = {
+ deviceId: secondaryMic
+ };
+
+ connection.join(connection.sessionid);
+ }
+};
+*/
+// ......................................................
+// .............Scalable Broadcast Part..................
+// ......................................................
+
+/*
+//document.getElementById('broadcast-id').value = connection.userid;
+
+// user need to connect server, so that others can reach him.
+connection.socket.on(function(socket) {
+
+ socket.on('broadcast-stopped', function(broadcastId) {
+ // alert('Broadcast has been stopped.');
+ // location.reload();
+ console.error('broadcast-stopped', broadcastId);
+ alert('This broadcast has been stopped.');
+ });
+
+ // this event is emitted when a broadcast is absent.
+ socket.on('start-broadcasting', function(typeOfStreams) {
+ console.log('start-broadcasting', typeOfStreams);
+
+ // host i.e. sender should always use this!
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: false,
+ OfferToReceiveAudio: false
+ };
+ connection.session = typeOfStreams;
+
+ // "open" method here will capture media-stream
+ // we can skip this function always; it is totally optional here.
+ // we can use "connection.getUserMediaHandler" instead
+ connection.open(connection.userid);
+ });
+});
+
+window.onbeforeunload = function() {
+ // Firefox is ugly.
+ startButton.disabled = false;
+};
+
+/*document.getElementById('share-session').onclick = function(){
+ this.disabled = true;
+ var allUserStreams = connection.getRemoteStreams();
+ connection.dontCaptureUserMedia = false;
+ connection.mediaConstraints.audio = true;
+ connection.attachStreams = [allUserStreams];
+ connection.addStream({
+ audio: true,
+ oneway: true,
+ allUserStreams: true
+ });
+};
+*/
+
+// ......................................................
+// .................MultiStreamsMixer....................
+// ......................................................
+
+//// all remote users
+//var allUserStreams = connection.getRemoteStreams();
+
+
+//connection.addStream(audioMixer.getMixedStream());
+
+// ......................................................
+// ................FileSharing/TextChat Code.............
+// ......................................................
+
+
+document.getElementById('share-file').onclick = function() {
+ var fileSelector = new FileSelector();
+ fileSelector.selectSingleFile(function(file) {
+ connection.send(file);
+ });
+};
+
+document.getElementById('input-text-chat').onkeyup = function send(e) {
+ if(e.keyCode != 13) return;
+
+ // removing trailing/leading whitespace
+ this.value = this.value.replace(/^\s+|\s+$/g, '');
+
+ if (!this.value.length) return;
+
+ connection.send(this.value);
+ appendDIV(this.value);
+ this.value = '';
+};
+
+var chatContainer = document.querySelector('#conversation-panel');
+function appendDIV(event) {
+ var div = document.createElement('div');
+ div.innerHTML = event.data || event ;
+ chatContainer.insertBefore(div, chatContainer.firstChild);
+ div.tabIndex = 0;
+ div.focus();
+
+ document.getElementById('input-text-chat').focus();
+}
+
+
+
+// ......................................................
+// ......................Handling Room-ID................
+// ......................................................
+
+function showRoomURL(roomid) {
+ var roomHashURL = '#' + roomid;
+
+ var html = '
Session Open ';
+
+ html += 'Share URL:
' + roomHashURL + ' ';
+
+ var roomURLsDiv = document.getElementById('room-urls');
+ roomURLsDiv.innerHTML = html;
+
+ roomURLsDiv.style.display = 'block';
+}
+
+(function() {
+ var params = {},
+ r = /([^&=]+)=?([^&]*)/g;
+
+ function d(s) {
+ return decodeURIComponent(s.replace(/\+/g, ' '));
+ }
+ var match, search = window.location.search;
+ while (match = r.exec(search.substring(1)))
+ params[d(match[1])] = d(match[2]);
+ window.params = params;
+})();
+
+var roomid = '';
+if (localStorage.getItem(connection.socketMessageEvent)) {
+ roomid = localStorage.getItem(connection.socketMessageEvent);
+} else {
+ roomid = connection.token();
+}
+document.getElementById('room-id').value = roomid;
+document.getElementById('room-id').onkeyup = function() {
+ localStorage.setItem(connection.socketMessageEvent, this.value);
+};
+
+var hashString = location.hash.replace('#', '');
+if(hashString.length && hashString.indexOf('comment-') == 0) {
+ hashString = '';
+}
+
+var roomid = params.roomid;
+if(!roomid && hashString.length) {
+ roomid = hashString;
+}
+
+if(roomid && roomid.length) {
+ document.getElementById('room-id').value = roomid;
+ localStorage.setItem(connection.socketMessageEvent, roomid);
+
+ // auto-join-room
+ (function reCheckRoomPresence() {
+ connection.checkPresence(roomid, function(isRoomExists) {
+ if(isRoomExists) {
+ connection.join(roomid);
+ return;
+ }
+
+ setTimeout(reCheckRoomPresence, 5000);
+ });
+ })();
+
+ disableInputButtons();
+}
+
+
+
diff --git a/bundle.js b/bundle.js
new file mode 100644
index 0000000..143f749
--- /dev/null
+++ b/bundle.js
@@ -0,0 +1,485 @@
+(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i
/g, '___').replace(/----/g, '(
').replace(/___/g, ' )');
+ });
+
+ // this event is emitted when a broadcast is already created.
+ socket.on('join-broadcaster', function(hintsToJoinBroadcast) {
+ console.log('join-broadcaster', hintsToJoinBroadcast);
+
+ connection.session = hintsToJoinBroadcast.typeOfStreams;
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: !!connection.session.video,
+ OfferToReceiveAudio: !!connection.session.audio
+ };
+ connection.broadcastId = hintsToJoinBroadcast.broadcastId;
+ connection.join(hintsToJoinBroadcast.userid);
+ });
+
+ socket.on('rejoin-broadcast', function(broadcastId) {
+ console.log('rejoin-broadcast', broadcastId);
+
+ connection.attachStreams = [];
+ socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) {
+ if (!isBroadcastExists) {
+ // the first person (i.e. real-broadcaster) MUST set his user-id
+ connection.userid = broadcastId;
+ }
+
+ socket.emit('join-broadcast', {
+ broadcastId: broadcastId,
+ userid: connection.userid,
+ typeOfStreams: connection.session
+ });
+ });
+ });
+
+ socket.on('broadcast-stopped', function(broadcastId) {
+ // alert('Broadcast has been stopped.');
+ // location.reload();
+ console.error('broadcast-stopped', broadcastId);
+ alert('This broadcast has been stopped.');
+ });
+
+ // this event is emitted when a broadcast is absent.
+ socket.on('start-broadcasting', function(typeOfStreams) {
+ console.log('start-broadcasting', typeOfStreams);
+
+ // host i.e. sender should always use this!
+ connection.sdpConstraints.mandatory = {
+ OfferToReceiveVideo: false,
+ OfferToReceiveAudio: false
+ };
+ connection.session = typeOfStreams;
+
+ // "open" method here will capture media-stream
+ // we can skip this function always; it is totally optional here.
+ // we can use "connection.getUserMediaHandler" instead
+ connection.open(connection.userid);
+ });
+});
+
+window.onbeforeunload = function() {
+ // Firefox is ugly.
+ startButton.disabled = false;
+};
+
+/*document.getElementById('share-session').onclick = function(){
+ this.disabled = true;
+ var allUserStreams = connection.getRemoteStreams();
+ connection.dontCaptureUserMedia = false;
+ connection.mediaConstraints.audio = true;
+ connection.attachStreams = [allUserStreams];
+ connection.addStream({
+ audio: true,
+ oneway: true,
+ allUserStreams: true
+ });
+};
+*/
+
+// ......................................................
+// .................MultiStreamsMixer....................
+// ......................................................
+
+//// all remote users
+//var allUserStreams = connection.getRemoteStreams();
+
+
+//connection.addStream(audioMixer.getMixedStream());
+
+// ......................................................
+// ................FileSharing/TextChat Code.............
+// ......................................................
+
+
+document.getElementById('share-file').onclick = function() {
+ var fileSelector = new FileSelector();
+ fileSelector.selectSingleFile(function(file) {
+ connection.send(file);
+ });
+};
+
+document.getElementById('input-text-chat').onkeyup = function send(e) {
+ if(e.keyCode != 13) return;
+
+ // removing trailing/leading whitespace
+ this.value = this.value.replace(/^\s+|\s+$/g, '');
+
+ if (!this.value.length) return;
+
+ connection.send(this.value);
+ appendDIV(this.value);
+ this.value = '';
+};
+
+var chatContainer = document.querySelector('#conversation-panel');
+function appendDIV(event) {
+ var div = document.createElement('div');
+ div.innerHTML = event.data || event ;
+ chatContainer.insertBefore(div, chatContainer.firstChild);
+ div.tabIndex = 0;
+ div.focus();
+
+ document.getElementById('input-text-chat').focus();
+}
+
+
+
+// ......................................................
+// ......................Handling Room-ID................
+// ......................................................
+
+function showRoomURL(roomid) {
+ var roomHashURL = '#' + roomid;
+
+ var html = '
Session Open ';
+
+ html += 'Share URL:
' + roomHashURL + ' ';
+
+ var roomURLsDiv = document.getElementById('room-urls');
+ roomURLsDiv.innerHTML = html;
+
+ roomURLsDiv.style.display = 'block';
+}
+
+(function() {
+ var params = {},
+ r = /([^&=]+)=?([^&]*)/g;
+
+ function d(s) {
+ return decodeURIComponent(s.replace(/\+/g, ' '));
+ }
+ var match, search = window.location.search;
+ while (match = r.exec(search.substring(1)))
+ params[d(match[1])] = d(match[2]);
+ window.params = params;
+})();
+
+var roomid = '';
+if (localStorage.getItem(connection.socketMessageEvent)) {
+ roomid = localStorage.getItem(connection.socketMessageEvent);
+} else {
+ roomid = connection.token();
+}
+document.getElementById('room-id').value = roomid;
+document.getElementById('room-id').onkeyup = function() {
+ localStorage.setItem(connection.socketMessageEvent, this.value);
+};
+
+var hashString = location.hash.replace('#', '');
+if(hashString.length && hashString.indexOf('comment-') == 0) {
+ hashString = '';
+}
+
+var roomid = params.roomid;
+if(!roomid && hashString.length) {
+ roomid = hashString;
+}
+
+if(roomid && roomid.length) {
+ document.getElementById('room-id').value = roomid;
+ localStorage.setItem(connection.socketMessageEvent, roomid);
+
+ // auto-join-room
+ (function reCheckRoomPresence() {
+ connection.checkPresence(roomid, function(isRoomExists) {
+ if(isRoomExists) {
+ connection.join(roomid);
+ return;
+ }
+
+ setTimeout(reCheckRoomPresence, 5000);
+ });
+ })();
+
+ disableInputButtons();
+}
+
+
+
+
+},{}]},{},[1]);
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..ebc14b5
--- /dev/null
+++ b/config.json
@@ -0,0 +1,17 @@
+{
+ "socketURL": "/",
+ "dirPath": "",
+ "homePage": "index.html",
+ "socketMessageEvent": "RTCMultiConnection-Message",
+ "socketCustomEvent": "RTCMultiConnection-Custom-Message",
+ "port": "8080",
+ "enableLogs": "false",
+ "autoRebootServerOnFailure": "false",
+ "isUseHTTPs": "false",
+ "sslKey": "./fake-keys/privatekey.pem",
+ "sslCert": "./fake-keys/certificate.pem",
+ "sslCabundle": "",
+ "enableAdmin": "true",
+ "adminUserName": "username",
+ "adminPassword": "password"
+}
diff --git a/css/main.css b/css/main.css
new file mode 100644
index 0000000..3af1807
--- /dev/null
+++ b/css/main.css
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+
+* {
+ box-sizing: border-box;
+}
+
+.logo img{
+ width: 100%;
+ max-width: 60px;
+ height: auto;
+}
+
+
+.hidden {
+ display: none;
+}
+
+.highlight {
+ background-color: #eee;
+ font-size: 1.2em;
+ margin: 0 0 30px 0;
+ padding: 0.2em 1.5em;
+}
+
+.warning {
+ color: red;
+ font-weight: 400;
+}
+
+@media screen and (min-width: 1000px) {
+ /* hack! to detect non-touch devices */
+ div#links a {
+ line-height: 0.8em;
+ }
+}
+/*
+body {
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+ margin: 0;
+ padding: 1em;
+ word-break: break-word;
+}
+*/
+
+button {
+ background-color: #d84a38;
+ border: none;
+ border-radius: 2px;
+ color: white;
+ font-family: 'Arial', sans-serif;
+ font-size: 0.8em;
+ margin: 0 20px 1em 0;
+ padding: 0.5em 0.7em 0.6em 0.7em;
+ width: 96px;
+}
+
+button:active {
+ background-color: #cf402f;
+}
+
+button:hover {
+ background-color: #cf402f;
+}
+
+button[disabled] {
+ color: #ccc;
+}
+
+button[disabled]:hover {
+ background-color: #d84a38;
+}
+
+canvas {
+ background-color: #ccc;
+ max-width: 100%;
+ width: 100%;
+}
+
+code {
+ font-family: 'Roboto', sans-serif;
+ font-weight: 400;
+}
+
+div#container {
+ margin: 0 auto 0 auto;
+ max-width: 60em;
+ padding: 1em 1.5em 1.3em 1.5em;
+}
+
+div#links {
+ padding: 0.5em 0 0 0;
+}
+
+h1 {
+ border-bottom: 1px solid #ccc;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 500;
+ margin: 0 0 0.8em 0;
+ padding: 0 0 0.2em 0;
+}
+
+h2 {
+ color: #444;
+ font-weight: 500;
+}
+
+h3 {
+ border-top: 1px solid #eee;
+ color: #666;
+ font-weight: 500;
+ margin: 10px 0 10px 0;
+ white-space: nowrap;
+}
+
+li {
+ margin: 0 0 0.4em 0;
+}
+
+html {
+ /* avoid annoying page width change
+ when moving from the home page */
+ overflow-y: scroll;
+}
+
+img {
+ border: none;
+ max-width: 100%;
+}
+
+input[type=radio] {
+ position: relative;
+ top: -1px;
+}
+
+p {
+ color: #444;
+ font-weight: 300;
+}
+
+p#data {
+ border-top: 1px dotted #666;
+ font-family: Courier New, monospace;
+ line-height: 1.3em;
+ max-height: 1000px;
+ overflow-y: auto;
+ padding: 1em 0 0 0;
+}
+
+p.borderBelow {
+ border-bottom: 1px solid #aaa;
+ padding: 0 0 20px 0;
+}
+
+section p:last-of-type {
+ margin: 0;
+}
+
+section {
+ border-bottom: 1px solid #eee;
+ margin: 0 0 30px 0;
+ padding: 0 0 20px 0;
+}
+
+section:last-of-type {
+ border-bottom: none;
+ padding: 0 0 1em 0;
+}
+
+select {
+ margin: 0 1em 1em 0;
+ position: relative;
+ top: -1px;
+}
+
+h1 span {
+ white-space: nowrap;
+}
+
+a {
+ color: #6fa8dc;
+ font-weight: 300;
+ text-decoration: none;
+}
+
+h1 a {
+ font-weight: 300;
+ margin: 0 10px 0 0;
+ white-space: nowrap;
+}
+
+a:hover {
+ color: #3d85c6;
+ text-decoration: underline;
+}
+
+a#viewSource {
+ display: block;
+ margin: 1.3em 0 0 0;
+ border-top: 1px solid #999;
+ padding: 1em 0 0 0;
+}
+
+div#errorMsg p {
+ color: #F00;
+}
+
+div#links a {
+ display: block;
+ line-height: 1.3em;
+ margin: 0 0 1.5em 0;
+}
+
+div.outputSelector {
+ margin: -1.3em 0 2em 0;
+}
+
+p.description {
+ margin: 0 0 0.5em 0;
+}
+
+strong {
+ font-weight: 500;
+}
+
+textarea {
+ resize: none;
+ font-family: 'Roboto', sans-serif;
+}
+
+ul {
+ margin: 0 0 0.5em 0;
+}
+
+@media screen and (max-width: 650px) {
+ .highlight {
+ font-size: 1em;
+ margin: 0 0 20px 0;
+ padding: 0.2em 1em;
+ }
+
+ h1 {
+ font-size: 24px;
+ }
+}
+
+
+/* Header/logo Title */
+.header {
+ padding: 80px;
+ text-align: center;
+ background: #1abc9c;
+ color: white;
+}
+
+/* Increase the font size of the heading */
+.header h1 {
+ font-size: 40px;
+}
+
+/* Style the top navigation bar */
+.navbar {
+ overflow: hidden;
+ background-color: #f1f1f1;
+}
+
+/* Style the navigation bar links */
+.navbar a {
+ float: left;
+ display: block;
+ color: black;
+ text-align: center;
+ padding: 14px 20px;
+ text-decoration: none;
+}
+
+/* Right-aligned link */
+.navbar a.right {
+ float: right;
+}
+
+/* Change color on hover */
+.navbar a:hover {
+ background-color: #ddd;
+ color: black;
+}
+
+/* Column container */
+.row {
+ display: -ms-flexbox; /* IE10 */
+ display: flex;
+ -ms-flex-wrap: wrap; /* IE10 */
+ flex-wrap: wrap;
+}
+
+/* Responsive layout - when the screen is less than 700px wide, make the two columns stack on top of each other instead of next to each other */
+@media screen and (max-width: 480px) {
+ .row {
+ flex-direction: column;
+ }
+}
+
+@media screen and (max-width: 400px) {
+ .navbar a {
+ float: none;
+ width: 100%;
+ }
+}
+
+
+@media screen and (max-width: 550px) {
+ button:active {
+ background-color: darkRed;
+ }
+
+ h1 {
+ font-size: 22px;
+ }
+}
+
+@media screen and (max-width: 450px) {
+ h1 {
+ font-size: 20px;
+ }
+}
+
+
+
diff --git a/dev/FileBufferReader.js b/dev/FileBufferReader.js
new file mode 100644
index 0000000..d42945b
--- /dev/null
+++ b/dev/FileBufferReader.js
@@ -0,0 +1,1236 @@
+// Last time updated: 2017-08-27 5:48:35 AM UTC
+
+// ________________
+// FileBufferReader
+
+// Open-Sourced: https://github.com/muaz-khan/FileBufferReader
+
+// --------------------------------------------------
+// Muaz Khan - www.MuazKhan.com
+// MIT License - www.WebRTC-Experiment.com/licence
+// --------------------------------------------------
+
+'use strict';
+
+(function() {
+
+ function FileBufferReader() {
+ var fbr = this;
+ var fbrHelper = new FileBufferReaderHelper();
+
+ fbr.chunks = {};
+ fbr.users = {};
+
+ fbr.readAsArrayBuffer = function(file, callback, extra) {
+ var options = {
+ file: file,
+ earlyCallback: function(chunk) {
+ callback(fbrClone(chunk, {
+ currentPosition: -1
+ }));
+ },
+ extra: extra || {
+ userid: 0
+ }
+ };
+
+ if (file.extra && Object.keys(file.extra).length) {
+ Object.keys(file.extra).forEach(function(key) {
+ options.extra[key] = file.extra[key];
+ });
+ }
+
+ fbrHelper.readAsArrayBuffer(fbr, options);
+ };
+
+ fbr.getNextChunk = function(fileUUID, callback, userid) {
+ var currentPosition;
+
+ if (typeof fileUUID.currentPosition !== 'undefined') {
+ currentPosition = fileUUID.currentPosition;
+ fileUUID = fileUUID.uuid;
+ }
+
+ var allFileChunks = fbr.chunks[fileUUID];
+ if (!allFileChunks) {
+ return;
+ }
+
+ if (typeof userid !== 'undefined') {
+ if (!fbr.users[userid + '']) {
+ fbr.users[userid + ''] = {
+ fileUUID: fileUUID,
+ userid: userid,
+ currentPosition: -1
+ };
+ }
+
+ if (typeof currentPosition !== 'undefined') {
+ fbr.users[userid + ''].currentPosition = currentPosition;
+ }
+
+ fbr.users[userid + ''].currentPosition++;
+ currentPosition = fbr.users[userid + ''].currentPosition;
+ } else {
+ if (typeof currentPosition !== 'undefined') {
+ fbr.chunks[fileUUID].currentPosition = currentPosition;
+ }
+
+ fbr.chunks[fileUUID].currentPosition++;
+ currentPosition = fbr.chunks[fileUUID].currentPosition;
+ }
+
+ var nextChunk = allFileChunks[currentPosition];
+ if (!nextChunk) {
+ delete fbr.chunks[fileUUID];
+ fbr.convertToArrayBuffer({
+ chunkMissing: true,
+ currentPosition: currentPosition,
+ uuid: fileUUID
+ }, callback);
+ return;
+ }
+
+ nextChunk = fbrClone(nextChunk);
+
+ if (typeof userid !== 'undefined') {
+ nextChunk.remoteUserId = userid + '';
+ }
+
+ if (!!nextChunk.start) {
+ fbr.onBegin(nextChunk);
+ }
+
+ if (!!nextChunk.end) {
+ fbr.onEnd(nextChunk);
+ }
+
+ fbr.onProgress(nextChunk);
+
+ fbr.convertToArrayBuffer(nextChunk, function(buffer) {
+ if (nextChunk.currentPosition == nextChunk.maxChunks) {
+ callback(buffer, true);
+ return;
+ }
+
+ callback(buffer, false);
+ });
+ };
+
+ var fbReceiver = new FileBufferReceiver(fbr);
+
+ fbr.addChunk = function(chunk, callback) {
+ if (!chunk) {
+ return;
+ }
+
+ fbReceiver.receive(chunk, function(chunk) {
+ fbr.convertToArrayBuffer({
+ readyForNextChunk: true,
+ currentPosition: chunk.currentPosition,
+ uuid: chunk.uuid
+ }, callback);
+ });
+ };
+
+ fbr.chunkMissing = function(chunk) {
+ delete fbReceiver.chunks[chunk.uuid];
+ delete fbReceiver.chunksWaiters[chunk.uuid];
+ };
+
+ fbr.onBegin = function() {};
+ fbr.onEnd = function() {};
+ fbr.onProgress = function() {};
+
+ fbr.convertToObject = FileConverter.ConvertToObject;
+ fbr.convertToArrayBuffer = FileConverter.ConvertToArrayBuffer
+
+ // for backward compatibility----it is redundant.
+ fbr.setMultipleUsers = function() {};
+
+ // extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
+ function fbrClone(from, to) {
+ if (from == null || typeof from != "object") return from;
+ if (from.constructor != Object && from.constructor != Array) return from;
+ if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
+ from.constructor == String || from.constructor == Number || from.constructor == Boolean)
+ return new from.constructor(from);
+
+ to = to || new from.constructor();
+
+ for (var name in from) {
+ to[name] = typeof to[name] == "undefined" ? fbrClone(from[name], null) : to[name];
+ }
+
+ return to;
+ }
+ }
+
+ function FileBufferReaderHelper() {
+ var fbrHelper = this;
+
+ function processInWebWorker(_function) {
+ var blob = URL.createObjectURL(new Blob([_function.toString(),
+ 'this.onmessage = function (e) {' + _function.name + '(e.data);}'
+ ], {
+ type: 'application/javascript'
+ }));
+
+ var worker = new Worker(blob);
+ return worker;
+ }
+
+ fbrHelper.readAsArrayBuffer = function(fbr, options) {
+ var earlyCallback = options.earlyCallback;
+ delete options.earlyCallback;
+
+ function processChunk(chunk) {
+ if (!fbr.chunks[chunk.uuid]) {
+ fbr.chunks[chunk.uuid] = {
+ currentPosition: -1
+ };
+ }
+
+ options.extra = options.extra || {
+ userid: 0
+ };
+
+ chunk.userid = options.userid || options.extra.userid || 0;
+ chunk.extra = options.extra;
+
+ fbr.chunks[chunk.uuid][chunk.currentPosition] = chunk;
+
+ if (chunk.end && earlyCallback) {
+ earlyCallback(chunk.uuid);
+ earlyCallback = null;
+ }
+
+ // for huge files
+ if ((chunk.maxChunks > 200 && chunk.currentPosition == 200) && earlyCallback) {
+ earlyCallback(chunk.uuid);
+ earlyCallback = null;
+ }
+ }
+ if (false && typeof Worker !== 'undefined') {
+ var webWorker = processInWebWorker(fileReaderWrapper);
+
+ webWorker.onmessage = function(event) {
+ processChunk(event.data);
+ };
+
+ webWorker.postMessage(options);
+ } else {
+ fileReaderWrapper(options, processChunk);
+ }
+ };
+
+ function fileReaderWrapper(options, callback) {
+ callback = callback || function(chunk) {
+ postMessage(chunk);
+ };
+
+ var file = options.file;
+ if (!file.uuid) {
+ file.uuid = (Math.random() * 100).toString().replace(/\./g, '');
+ }
+
+ var chunkSize = options.chunkSize || 15 * 1000;
+ if (options.extra && options.extra.chunkSize) {
+ chunkSize = options.extra.chunkSize;
+ }
+
+ var sliceId = 0;
+ var cacheSize = chunkSize;
+
+ var chunksPerSlice = Math.floor(Math.min(100000000, cacheSize) / chunkSize);
+ var sliceSize = chunksPerSlice * chunkSize;
+ var maxChunks = Math.ceil(file.size / chunkSize);
+
+ file.maxChunks = maxChunks;
+
+ var numOfChunksInSlice;
+ var currentPosition = 0;
+ var hasEntireFile;
+ var chunks = [];
+
+ callback({
+ currentPosition: currentPosition,
+ uuid: file.uuid,
+ maxChunks: maxChunks,
+ size: file.size,
+ name: file.name,
+ type: file.type,
+ lastModifiedDate: (file.lastModifiedDate || new Date()).toString(),
+ start: true
+ });
+
+ var blob, reader = new FileReader();
+
+ reader.onloadend = function(evt) {
+ if (evt.target.readyState == FileReader.DONE) {
+ addChunks(file.name, evt.target.result, function() {
+ sliceId++;
+ if ((sliceId + 1) * sliceSize < file.size) {
+ blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize);
+ reader.readAsArrayBuffer(blob);
+ } else if (sliceId * sliceSize < file.size) {
+ blob = file.slice(sliceId * sliceSize, file.size);
+ reader.readAsArrayBuffer(blob);
+ } else {
+ file.url = URL.createObjectURL(file);
+ callback({
+ currentPosition: currentPosition,
+ uuid: file.uuid,
+ maxChunks: maxChunks,
+ size: file.size,
+ name: file.name,
+ lastModifiedDate: (file.lastModifiedDate || new Date()).toString(),
+ url: URL.createObjectURL(file),
+ type: file.type,
+ end: true
+ });
+ }
+ });
+ }
+ };
+
+ currentPosition += 1;
+
+ blob = file.slice(sliceId * sliceSize, (sliceId + 1) * sliceSize);
+ reader.readAsArrayBuffer(blob);
+
+ function addChunks(fileName, binarySlice, addChunkCallback) {
+ numOfChunksInSlice = Math.ceil(binarySlice.byteLength / chunkSize);
+ for (var i = 0; i < numOfChunksInSlice; i++) {
+ var start = i * chunkSize;
+ chunks[currentPosition] = binarySlice.slice(start, Math.min(start + chunkSize, binarySlice.byteLength));
+
+ callback({
+ uuid: file.uuid,
+ buffer: chunks[currentPosition],
+ currentPosition: currentPosition,
+ maxChunks: maxChunks,
+
+ size: file.size,
+ name: file.name,
+ lastModifiedDate: (file.lastModifiedDate || new Date()).toString(),
+ type: file.type
+ });
+
+ currentPosition++;
+ }
+
+ if (currentPosition == maxChunks) {
+ hasEntireFile = true;
+ }
+
+ addChunkCallback();
+ }
+ }
+ }
+
+ function FileSelector() {
+ var selector = this;
+
+ var noFileSelectedCallback = function() {};
+
+ selector.selectSingleFile = function(callback, failure) {
+ if (failure) {
+ noFileSelectedCallback = failure;
+ }
+
+ selectFile(callback);
+ };
+ selector.selectMultipleFiles = function(callback, failure) {
+ if (failure) {
+ noFileSelectedCallback = failure;
+ }
+
+ selectFile(callback, true);
+ };
+ selector.selectDirectory = function(callback, failure) {
+ if (failure) {
+ noFileSelectedCallback = failure;
+ }
+
+ selectFile(callback, true, true);
+ };
+
+ selector.accept = '*.*';
+
+ function selectFile(callback, multiple, directory) {
+ callback = callback || function() {};
+
+ var file = document.createElement('input');
+ file.type = 'file';
+
+ if (multiple) {
+ file.multiple = true;
+ }
+
+ if (directory) {
+ file.webkitdirectory = true;
+ }
+
+ file.accept = selector.accept;
+
+ file.onclick = function() {
+ file.clickStarted = true;
+ };
+
+ document.body.onfocus = function() {
+ setTimeout(function() {
+ if (!file.clickStarted) return;
+ file.clickStarted = false;
+
+ if (!file.value) {
+ noFileSelectedCallback();
+ }
+ }, 500);
+ };
+
+ file.onchange = function() {
+ if (multiple) {
+ if (!file.files.length) {
+ console.error('No file selected.');
+ return;
+ }
+
+ var arr = [];
+ Array.from(file.files).forEach(function(file) {
+ file.url = file.webkitRelativePath;
+ arr.push(file);
+ });
+ callback(arr);
+ return;
+ }
+
+ if (!file.files[0]) {
+ console.error('No file selected.');
+ return;
+ }
+
+ callback(file.files[0]);
+
+ file.parentNode.removeChild(file);
+ };
+ file.style.display = 'none';
+ (document.body || document.documentElement).appendChild(file);
+ fireClickEvent(file);
+ }
+
+ function getValidFileName(fileName) {
+ if (!fileName) {
+ fileName = 'file' + (new Date).toISOString().replace(/:|\.|-/g, '')
+ }
+
+ var a = fileName;
+ a = a.replace(/^.*[\\\/]([^\\\/]*)$/i, "$1");
+ a = a.replace(/\s/g, "_");
+ a = a.replace(/,/g, '');
+ a = a.toLowerCase();
+ return a;
+ }
+
+ function fireClickEvent(element) {
+ if (typeof element.click === 'function') {
+ element.click();
+ return;
+ }
+
+ if (typeof element.change === 'function') {
+ element.change();
+ return;
+ }
+
+ if (typeof document.createEvent('Event') !== 'undefined') {
+ var event = document.createEvent('Event');
+
+ if (typeof event.initEvent === 'function' && typeof element.dispatchEvent === 'function') {
+ event.initEvent('click', true, true);
+ element.dispatchEvent(event);
+ return;
+ }
+ }
+
+ var event = new MouseEvent('click', {
+ view: window,
+ bubbles: true,
+ cancelable: true
+ });
+
+ element.dispatchEvent(event);
+ }
+ }
+
+ function FileBufferReceiver(fbr) {
+ var fbReceiver = this;
+
+ fbReceiver.chunks = {};
+ fbReceiver.chunksWaiters = {};
+
+ function receive(chunk, callback) {
+ if (!chunk.uuid) {
+ fbr.convertToObject(chunk, function(object) {
+ receive(object);
+ });
+ return;
+ }
+
+ if (chunk.start && !fbReceiver.chunks[chunk.uuid]) {
+ fbReceiver.chunks[chunk.uuid] = {};
+ if (fbr.onBegin) fbr.onBegin(chunk);
+ }
+
+ if (!chunk.end && chunk.buffer) {
+ fbReceiver.chunks[chunk.uuid][chunk.currentPosition] = chunk.buffer;
+ }
+
+ if (chunk.end) {
+ var chunksObject = fbReceiver.chunks[chunk.uuid];
+ var chunksArray = [];
+ Object.keys(chunksObject).forEach(function(item, idx) {
+ chunksArray.push(chunksObject[item]);
+ });
+
+ var blob = new Blob(chunksArray, {
+ type: chunk.type
+ });
+ blob = merge(blob, chunk);
+ blob.url = URL.createObjectURL(blob);
+ blob.uuid = chunk.uuid;
+
+ if (!blob.size) console.error('Something went wrong. Blob Size is 0.');
+
+ if (fbr.onEnd) fbr.onEnd(blob);
+
+ // clear system memory
+ delete fbReceiver.chunks[chunk.uuid];
+ delete fbReceiver.chunksWaiters[chunk.uuid];
+ }
+
+ if (chunk.buffer && fbr.onProgress) fbr.onProgress(chunk);
+
+ if (!chunk.end) {
+ callback(chunk);
+
+ fbReceiver.chunksWaiters[chunk.uuid] = function() {
+ function looper() {
+ if (!chunk.buffer) {
+ return;
+ }
+
+ if (!fbReceiver.chunks[chunk.uuid]) {
+ return;
+ }
+
+ if (chunk.currentPosition != chunk.maxChunks && !fbReceiver.chunks[chunk.uuid][chunk.currentPosition]) {
+ callback(chunk);
+ setTimeout(looper, 5000);
+ }
+ }
+ setTimeout(looper, 5000);
+ };
+
+ fbReceiver.chunksWaiters[chunk.uuid]();
+ }
+ }
+
+ fbReceiver.receive = receive;
+ }
+
+ var FileConverter = {
+ ConvertToArrayBuffer: function(object, callback) {
+ binarize.pack(object, function(dataView) {
+ callback(dataView.buffer);
+ });
+ },
+ ConvertToObject: function(buffer, callback) {
+ binarize.unpack(buffer, callback);
+ }
+ };
+
+ function merge(mergein, mergeto) {
+ if (!mergein) mergein = {};
+ if (!mergeto) return mergein;
+
+ for (var item in mergeto) {
+ try {
+ mergein[item] = mergeto[item];
+ } catch (e) {}
+ }
+ return mergein;
+ }
+
+ var debug = false;
+
+ var BIG_ENDIAN = false,
+ LITTLE_ENDIAN = true,
+ TYPE_LENGTH = Uint8Array.BYTES_PER_ELEMENT,
+ LENGTH_LENGTH = Uint16Array.BYTES_PER_ELEMENT,
+ BYTES_LENGTH = Uint32Array.BYTES_PER_ELEMENT;
+
+ var Types = {
+ NULL: 0,
+ UNDEFINED: 1,
+ STRING: 2,
+ NUMBER: 3,
+ BOOLEAN: 4,
+ ARRAY: 5,
+ OBJECT: 6,
+ INT8ARRAY: 7,
+ INT16ARRAY: 8,
+ INT32ARRAY: 9,
+ UINT8ARRAY: 10,
+ UINT16ARRAY: 11,
+ UINT32ARRAY: 12,
+ FLOAT32ARRAY: 13,
+ FLOAT64ARRAY: 14,
+ ARRAYBUFFER: 15,
+ BLOB: 16,
+ FILE: 16,
+ BUFFER: 17 // Special type for node.js
+ };
+
+ if (debug) {
+ var TypeNames = [
+ 'NULL',
+ 'UNDEFINED',
+ 'STRING',
+ 'NUMBER',
+ 'BOOLEAN',
+ 'ARRAY',
+ 'OBJECT',
+ 'INT8ARRAY',
+ 'INT16ARRAY',
+ 'INT32ARRAY',
+ 'UINT8ARRAY',
+ 'UINT16ARRAY',
+ 'UINT32ARRAY',
+ 'FLOAT32ARRAY',
+ 'FLOAT64ARRAY',
+ 'ARRAYBUFFER',
+ 'BLOB',
+ 'BUFFER'
+ ];
+ }
+
+ var Length = [
+ null, // Types.NULL
+ null, // Types.UNDEFINED
+ 'Uint16', // Types.STRING
+ 'Float64', // Types.NUMBER
+ 'Uint8', // Types.BOOLEAN
+ null, // Types.ARRAY
+ null, // Types.OBJECT
+ 'Int8', // Types.INT8ARRAY
+ 'Int16', // Types.INT16ARRAY
+ 'Int32', // Types.INT32ARRAY
+ 'Uint8', // Types.UINT8ARRAY
+ 'Uint16', // Types.UINT16ARRAY
+ 'Uint32', // Types.UINT32ARRAY
+ 'Float32', // Types.FLOAT32ARRAY
+ 'Float64', // Types.FLOAT64ARRAY
+ 'Uint8', // Types.ARRAYBUFFER
+ 'Uint8', // Types.BLOB, Types.FILE
+ 'Uint8' // Types.BUFFER
+ ];
+
+ var binary_dump = function(view, start, length) {
+ var table = [],
+ endianness = BIG_ENDIAN,
+ ROW_LENGTH = 40;
+ table[0] = [];
+ for (var i = 0; i < ROW_LENGTH; i++) {
+ table[0][i] = i < 10 ? '0' + i.toString(10) : i.toString(10);
+ }
+ for (i = 0; i < length; i++) {
+ var code = view.getUint8(start + i, endianness);
+ var index = ~~(i / ROW_LENGTH) + 1;
+ if (typeof table[index] === 'undefined') table[index] = [];
+ table[index][i % ROW_LENGTH] = code < 16 ? '0' + code.toString(16) : code.toString(16);
+ }
+ console.log('%c' + table[0].join(' '), 'font-weight: bold;');
+ for (i = 1; i < table.length; i++) {
+ console.log(table[i].join(' '));
+ }
+ };
+
+ var find_type = function(obj) {
+ var type = undefined;
+
+ if (obj === undefined) {
+ type = Types.UNDEFINED;
+
+ } else if (obj === null) {
+ type = Types.NULL;
+
+ } else {
+ var const_name = obj.constructor.name;
+ var const_name_reflection = obj.constructor.toString().match(/\w+/g)[1];
+ if (const_name !== undefined && Types[const_name.toUpperCase()] !== undefined) {
+ // return type by .constructor.name if possible
+ type = Types[const_name.toUpperCase()];
+
+ } else if (const_name_reflection !== undefined && Types[const_name_reflection.toUpperCase()] !== undefined) {
+ type = Types[const_name_reflection.toUpperCase()];
+
+ } else {
+ // Work around when constructor.name is not defined
+ switch (typeof obj) {
+ case 'string':
+ type = Types.STRING;
+ break;
+
+ case 'number':
+ type = Types.NUMBER;
+ break;
+
+ case 'boolean':
+ type = Types.BOOLEAN;
+ break;
+
+ case 'object':
+ if (obj instanceof Array) {
+ type = Types.ARRAY;
+
+ } else if (obj instanceof Int8Array) {
+ type = Types.INT8ARRAY;
+
+ } else if (obj instanceof Int16Array) {
+ type = Types.INT16ARRAY;
+
+ } else if (obj instanceof Int32Array) {
+ type = Types.INT32ARRAY;
+
+ } else if (obj instanceof Uint8Array) {
+ type = Types.UINT8ARRAY;
+
+ } else if (obj instanceof Uint16Array) {
+ type = Types.UINT16ARRAY;
+
+ } else if (obj instanceof Uint32Array) {
+ type = Types.UINT32ARRAY;
+
+ } else if (obj instanceof Float32Array) {
+ type = Types.FLOAT32ARRAY;
+
+ } else if (obj instanceof Float64Array) {
+ type = Types.FLOAT64ARRAY;
+
+ } else if (obj instanceof ArrayBuffer) {
+ type = Types.ARRAYBUFFER;
+
+ } else if (obj instanceof Blob) { // including File
+ type = Types.BLOB;
+
+ } else if (obj instanceof Buffer) { // node.js only
+ type = Types.BUFFER;
+
+ } else if (obj instanceof Object) {
+ type = Types.OBJECT;
+
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ return type;
+ };
+
+ var utf16_utf8 = function(string) {
+ return unescape(encodeURIComponent(string));
+ };
+
+ var utf8_utf16 = function(bytes) {
+ return decodeURIComponent(escape(bytes));
+ };
+
+ /**
+ * packs seriarized elements array into a packed ArrayBuffer
+ * @param {Array} serialized Serialized array of elements.
+ * @return {DataView} view of packed binary
+ */
+ var pack = function(serialized) {
+ var cursor = 0,
+ i = 0,
+ j = 0,
+ endianness = BIG_ENDIAN;
+
+ var ab = new ArrayBuffer(serialized[0].byte_length + serialized[0].header_size);
+ var view = new DataView(ab);
+
+ for (i = 0; i < serialized.length; i++) {
+ var start = cursor,
+ header_size = serialized[i].header_size,
+ type = serialized[i].type,
+ length = serialized[i].length,
+ value = serialized[i].value,
+ byte_length = serialized[i].byte_length,
+ type_name = Length[type],
+ unit = type_name === null ? 0 : window[type_name + 'Array'].BYTES_PER_ELEMENT;
+
+ // Set type
+ if (type === Types.BUFFER) {
+ // on node.js Blob is emulated using Buffer type
+ view.setUint8(cursor, Types.BLOB, endianness);
+ } else {
+ view.setUint8(cursor, type, endianness);
+ }
+ cursor += TYPE_LENGTH;
+
+ if (debug) {
+ console.info('Packing', type, TypeNames[type]);
+ }
+
+ // Set length if required
+ if (type === Types.ARRAY || type === Types.OBJECT) {
+ view.setUint16(cursor, length, endianness);
+ cursor += LENGTH_LENGTH;
+
+ if (debug) {
+ console.info('Content Length', length);
+ }
+ }
+
+ // Set byte length
+ view.setUint32(cursor, byte_length, endianness);
+ cursor += BYTES_LENGTH;
+
+ if (debug) {
+ console.info('Header Size', header_size, 'bytes');
+ console.info('Byte Length', byte_length, 'bytes');
+ }
+
+ switch (type) {
+ case Types.NULL:
+ case Types.UNDEFINED:
+ // NULL and UNDEFINED doesn't have any payload
+ break;
+
+ case Types.STRING:
+ if (debug) {
+ console.info('Actual Content %c"' + value + '"', 'font-weight:bold;');
+ }
+ for (j = 0; j < length; j++, cursor += unit) {
+ view.setUint16(cursor, value.charCodeAt(j), endianness);
+ }
+ break;
+
+ case Types.NUMBER:
+ case Types.BOOLEAN:
+ if (debug) {
+ console.info('%c' + value.toString(), 'font-weight:bold;');
+ }
+ view['set' + type_name](cursor, value, endianness);
+ cursor += unit;
+ break;
+
+ case Types.INT8ARRAY:
+ case Types.INT16ARRAY:
+ case Types.INT32ARRAY:
+ case Types.UINT8ARRAY:
+ case Types.UINT16ARRAY:
+ case Types.UINT32ARRAY:
+ case Types.FLOAT32ARRAY:
+ case Types.FLOAT64ARRAY:
+ var _view = new Uint8Array(view.buffer, cursor, byte_length);
+ _view.set(new Uint8Array(value.buffer));
+ cursor += byte_length;
+ break;
+
+ case Types.ARRAYBUFFER:
+ case Types.BUFFER:
+ var _view = new Uint8Array(view.buffer, cursor, byte_length);
+ _view.set(new Uint8Array(value));
+ cursor += byte_length;
+ break;
+
+ case Types.BLOB:
+ case Types.ARRAY:
+ case Types.OBJECT:
+ break;
+
+ default:
+ throw 'TypeError: Unexpected type found.';
+ }
+
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ }
+
+ return view;
+ };
+
+ /**
+ * Unpack binary data into an object with value and cursor
+ * @param {DataView} view [description]
+ * @param {Number} cursor [description]
+ * @return {Object}
+ */
+ var unpack = function(view, cursor) {
+ var i = 0,
+ endianness = BIG_ENDIAN,
+ start = cursor;
+ var type, length, byte_length, value, elem;
+
+ // Retrieve "type"
+ type = view.getUint8(cursor, endianness);
+ cursor += TYPE_LENGTH;
+
+ if (debug) {
+ console.info('Unpacking', type, TypeNames[type]);
+ }
+
+ // Retrieve "length"
+ if (type === Types.ARRAY || type === Types.OBJECT) {
+ length = view.getUint16(cursor, endianness);
+ cursor += LENGTH_LENGTH;
+
+ if (debug) {
+ console.info('Content Length', length);
+ }
+ }
+
+ // Retrieve "byte_length"
+ byte_length = view.getUint32(cursor, endianness);
+ cursor += BYTES_LENGTH;
+
+ if (debug) {
+ console.info('Byte Length', byte_length, 'bytes');
+ }
+
+ var type_name = Length[type];
+ var unit = type_name === null ? 0 : window[type_name + 'Array'].BYTES_PER_ELEMENT;
+
+ switch (type) {
+ case Types.NULL:
+ case Types.UNDEFINED:
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ // NULL and UNDEFINED doesn't have any octet
+ value = null;
+ break;
+
+ case Types.STRING:
+ length = byte_length / unit;
+ var string = [];
+ for (i = 0; i < length; i++) {
+ var code = view.getUint16(cursor, endianness);
+ cursor += unit;
+ string.push(String.fromCharCode(code));
+ }
+ value = string.join('');
+ if (debug) {
+ console.info('Actual Content %c"' + value + '"', 'font-weight:bold;');
+ binary_dump(view, start, cursor - start);
+ }
+ break;
+
+ case Types.NUMBER:
+ value = view.getFloat64(cursor, endianness);
+ cursor += unit;
+ if (debug) {
+ console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;');
+ binary_dump(view, start, cursor - start);
+ }
+ break;
+
+ case Types.BOOLEAN:
+ value = view.getUint8(cursor, endianness) === 1 ? true : false;
+ cursor += unit;
+ if (debug) {
+ console.info('Actual Content %c"' + value.toString() + '"', 'font-weight:bold;');
+ binary_dump(view, start, cursor - start);
+ }
+ break;
+
+ case Types.INT8ARRAY:
+ case Types.INT16ARRAY:
+ case Types.INT32ARRAY:
+ case Types.UINT8ARRAY:
+ case Types.UINT16ARRAY:
+ case Types.UINT32ARRAY:
+ case Types.FLOAT32ARRAY:
+ case Types.FLOAT64ARRAY:
+ case Types.ARRAYBUFFER:
+ elem = view.buffer.slice(cursor, cursor + byte_length);
+ cursor += byte_length;
+
+ // If ArrayBuffer
+ if (type === Types.ARRAYBUFFER) {
+ value = elem;
+
+ // If other TypedArray
+ } else {
+ value = new window[type_name + 'Array'](elem);
+ }
+
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ break;
+
+ case Types.BLOB:
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ // If Blob is available (on browser)
+ if (window.Blob) {
+ var mime = unpack(view, cursor);
+ var buffer = unpack(view, mime.cursor);
+ cursor = buffer.cursor;
+ value = new Blob([buffer.value], {
+ type: mime.value
+ });
+ } else {
+ // node.js implementation goes here
+ elem = view.buffer.slice(cursor, cursor + byte_length);
+ cursor += byte_length;
+ // node.js implementatino uses Buffer to help Blob
+ value = new Buffer(elem);
+ }
+ break;
+
+ case Types.ARRAY:
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ value = [];
+ for (i = 0; i < length; i++) {
+ // Retrieve array element
+ elem = unpack(view, cursor);
+ cursor = elem.cursor;
+ value.push(elem.value);
+ }
+ break;
+
+ case Types.OBJECT:
+ if (debug) {
+ binary_dump(view, start, cursor - start);
+ }
+ value = {};
+ for (i = 0; i < length; i++) {
+ // Retrieve object key and value in sequence
+ var key = unpack(view, cursor);
+ var val = unpack(view, key.cursor);
+ cursor = val.cursor;
+ value[key.value] = val.value;
+ }
+ break;
+
+ default:
+ throw 'TypeError: Type not supported.';
+ }
+ return {
+ value: value,
+ cursor: cursor
+ };
+ };
+
+ /**
+ * deferred function to process multiple serialization in order
+ * @param {array} array [description]
+ * @param {Function} callback [description]
+ * @return {void} no return value
+ */
+ var deferredSerialize = function(array, callback) {
+ var length = array.length,
+ results = [],
+ count = 0,
+ byte_length = 0;
+ for (var i = 0; i < array.length; i++) {
+ (function(index) {
+ serialize(array[index], function(result) {
+ // store results in order
+ results[index] = result;
+ // count byte length
+ byte_length += result[0].header_size + result[0].byte_length;
+ // when all results are on table
+ if (++count === length) {
+ // finally concatenate all reuslts into a single array in order
+ var array = [];
+ for (var j = 0; j < results.length; j++) {
+ array = array.concat(results[j]);
+ }
+ callback(array, byte_length);
+ }
+ });
+ })(i);
+ }
+ };
+
+ /**
+ * Serializes object and return byte_length
+ * @param {mixed} obj JavaScript object you want to serialize
+ * @return {Array} Serialized array object
+ */
+ var serialize = function(obj, callback) {
+ var subarray = [],
+ unit = 1,
+ header_size = TYPE_LENGTH + BYTES_LENGTH,
+ type, byte_length = 0,
+ length = 0,
+ value = obj;
+
+ type = find_type(obj);
+
+ unit = Length[type] === undefined || Length[type] === null ? 0 :
+ window[Length[type] + 'Array'].BYTES_PER_ELEMENT;
+
+ switch (type) {
+ case Types.UNDEFINED:
+ case Types.NULL:
+ break;
+
+ case Types.NUMBER:
+ case Types.BOOLEAN:
+ byte_length = unit;
+ break;
+
+ case Types.STRING:
+ length = obj.length;
+ byte_length += length * unit;
+ break;
+
+ case Types.INT8ARRAY:
+ case Types.INT16ARRAY:
+ case Types.INT32ARRAY:
+ case Types.UINT8ARRAY:
+ case Types.UINT16ARRAY:
+ case Types.UINT32ARRAY:
+ case Types.FLOAT32ARRAY:
+ case Types.FLOAT64ARRAY:
+ length = obj.length;
+ byte_length += length * unit;
+ break;
+
+ case Types.ARRAY:
+ deferredSerialize(obj, function(subarray, byte_length) {
+ callback([{
+ type: type,
+ length: obj.length,
+ header_size: header_size + LENGTH_LENGTH,
+ byte_length: byte_length,
+ value: null
+ }].concat(subarray));
+ });
+ return;
+
+ case Types.OBJECT:
+ var deferred = [];
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ deferred.push(key);
+ deferred.push(obj[key]);
+ length++;
+ }
+ }
+ deferredSerialize(deferred, function(subarray, byte_length) {
+ callback([{
+ type: type,
+ length: length,
+ header_size: header_size + LENGTH_LENGTH,
+ byte_length: byte_length,
+ value: null
+ }].concat(subarray));
+ });
+ return;
+
+ case Types.ARRAYBUFFER:
+ byte_length += obj.byteLength;
+ break;
+
+ case Types.BLOB:
+ var mime_type = obj.type;
+ var reader = new FileReader();
+ reader.onload = function(e) {
+ deferredSerialize([mime_type, e.target.result], function(subarray, byte_length) {
+ callback([{
+ type: type,
+ length: length,
+ header_size: header_size,
+ byte_length: byte_length,
+ value: null
+ }].concat(subarray));
+ });
+ };
+ reader.onerror = function(e) {
+ throw 'FileReader Error: ' + e;
+ };
+ reader.readAsArrayBuffer(obj);
+ return;
+
+ case Types.BUFFER:
+ byte_length += obj.length;
+ break;
+
+ default:
+ throw 'TypeError: Type "' + obj.constructor.name + '" not supported.';
+ }
+
+ callback([{
+ type: type,
+ length: length,
+ header_size: header_size,
+ byte_length: byte_length,
+ value: value
+ }].concat(subarray));
+ };
+
+ /**
+ * Deserialize binary and return JavaScript object
+ * @param ArrayBuffer buffer ArrayBuffer you want to deserialize
+ * @return mixed Retrieved JavaScript object
+ */
+ var deserialize = function(buffer, callback) {
+ var view = buffer instanceof DataView ? buffer : new DataView(buffer);
+ var result = unpack(view, 0);
+ return result.value;
+ };
+
+ if (debug) {
+ window.Test = {
+ BIG_ENDIAN: BIG_ENDIAN,
+ LITTLE_ENDIAN: LITTLE_ENDIAN,
+ Types: Types,
+ pack: pack,
+ unpack: unpack,
+ serialize: serialize,
+ deserialize: deserialize
+ };
+ }
+
+ var binarize = {
+ pack: function(obj, callback) {
+ try {
+ if (debug) console.info('%cPacking Start', 'font-weight: bold; color: red;', obj);
+ serialize(obj, function(array) {
+ if (debug) console.info('Serialized Object', array);
+ callback(pack(array));
+ });
+ } catch (e) {
+ throw e;
+ }
+ },
+ unpack: function(buffer, callback) {
+ try {
+ if (debug) console.info('%cUnpacking Start', 'font-weight: bold; color: red;', buffer);
+ var result = deserialize(buffer);
+ if (debug) console.info('Deserialized Object', result);
+ callback(result);
+ } catch (e) {
+ throw e;
+ }
+ }
+ };
+
+ window.FileConverter = FileConverter;
+ window.FileSelector = FileSelector;
+ window.FileBufferReader = FileBufferReader;
+})();
diff --git a/dev/MultiStreamsMixer.js b/dev/MultiStreamsMixer.js
new file mode 100644
index 0000000..5abb002
--- /dev/null
+++ b/dev/MultiStreamsMixer.js
@@ -0,0 +1,571 @@
+// Last time updated: 2019-06-24 6:39:09 PM UTC
+
+// ________________________
+// MultiStreamsMixer v1.2.3
+
+// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
+
+// --------------------------------------------------
+// Muaz Khan - www.MuazKhan.com
+// MIT License - www.WebRTC-Experiment.com/licence
+// --------------------------------------------------
+
+function MultiStreamsMixer(arrayOfMediaStreams, elementClass) {
+
+ var browserFakeUserAgent = 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45';
+
+ (function(that) {
+ if (typeof RecordRTC !== 'undefined') {
+ return;
+ }
+
+ 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.log === 'undefined' || typeof global.console.error === 'undefined') {
+ global.console.error = global.console.log = global.console.log || function() {
+ console.log(arguments);
+ };
+ }
+
+ if (typeof document === 'undefined') {
+ /*global document:true */
+ that.document = {
+ documentElement: {
+ appendChild: function() {
+ return '';
+ }
+ }
+ };
+
+ document.createElement = document.captureStream = document.mozCaptureStream = function() {
+ var obj = {
+ getContext: function() {
+ return obj;
+ },
+ play: function() {},
+ pause: function() {},
+ drawImage: function() {},
+ toDataURL: function() {
+ return '';
+ },
+ style: {}
+ };
+ return obj;
+ };
+
+ that.HTMLVideoElement = function() {};
+ }
+
+ 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
+ };
+ }
+
+ 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);
+
+ // requires: chrome://flags/#enable-experimental-web-platform-features
+
+ elementClass = elementClass || 'multi-streams-mixer';
+
+ var videos = [];
+ var isStopDrawingFrames = false;
+
+ var canvas = document.createElement('canvas');
+ var context = canvas.getContext('2d');
+ canvas.style.opacity = 0;
+ canvas.style.position = 'absolute';
+ canvas.style.zIndex = -1;
+ canvas.style.top = '-1000em';
+ canvas.style.left = '-1000em';
+ canvas.className = elementClass;
+ (document.body || document.documentElement).appendChild(canvas);
+
+ this.disableLogs = false;
+ this.frameInterval = 10;
+
+ this.width = 360;
+ this.height = 240;
+
+ // use gain node to prevent echo
+ this.useGainNode = true;
+
+ var self = this;
+
+ // _____________________________
+ // Cross-Browser-Declarations.js
+
+ // WebAudio API representer
+ var AudioContext = window.AudioContext;
+
+ if (typeof AudioContext === 'undefined') {
+ if (typeof webkitAudioContext !== 'undefined') {
+ /*global AudioContext:true */
+ AudioContext = webkitAudioContext;
+ }
+
+ if (typeof mozAudioContext !== 'undefined') {
+ /*global AudioContext:true */
+ AudioContext = mozAudioContext;
+ }
+ }
+
+ /*jshint -W079 */
+ var URL = window.URL;
+
+ if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') {
+ /*global URL:true */
+ URL = webkitURL;
+ }
+
+ if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator?
+ if (typeof navigator.webkitGetUserMedia !== 'undefined') {
+ navigator.getUserMedia = navigator.webkitGetUserMedia;
+ }
+
+ if (typeof navigator.mozGetUserMedia !== 'undefined') {
+ navigator.getUserMedia = navigator.mozGetUserMedia;
+ }
+ }
+
+ var MediaStream = window.MediaStream;
+
+ if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
+ MediaStream = webkitMediaStream;
+ }
+
+ /*global MediaStream:true */
+ if (typeof MediaStream !== 'undefined') {
+ // override "stop" method for all browsers
+ if (typeof MediaStream.prototype.stop === 'undefined') {
+ MediaStream.prototype.stop = function() {
+ this.getTracks().forEach(function(track) {
+ track.stop();
+ });
+ };
+ }
+ }
+
+ var Storage = {};
+
+ if (typeof AudioContext !== 'undefined') {
+ Storage.AudioContext = AudioContext;
+ } else if (typeof webkitAudioContext !== 'undefined') {
+ Storage.AudioContext = webkitAudioContext;
+ }
+
+ function setSrcObject(stream, element) {
+ if ('srcObject' in element) {
+ element.srcObject = stream;
+ } else if ('mozSrcObject' in element) {
+ element.mozSrcObject = stream;
+ } else {
+ element.srcObject = stream;
+ }
+ }
+
+ this.startDrawingFrames = function() {
+ drawVideosToCanvas();
+ };
+
+ function drawVideosToCanvas() {
+ if (isStopDrawingFrames) {
+ return;
+ }
+
+ var videosLength = videos.length;
+
+ var fullcanvas = false;
+ var remaining = [];
+ videos.forEach(function(video) {
+ if (!video.stream) {
+ video.stream = {};
+ }
+
+ if (video.stream.fullcanvas) {
+ fullcanvas = video;
+ } else {
+ // todo: video.stream.active or video.stream.live to fix blank frames issues?
+ remaining.push(video);
+ }
+ });
+
+ if (fullcanvas) {
+ canvas.width = fullcanvas.stream.width;
+ canvas.height = fullcanvas.stream.height;
+ } else if (remaining.length) {
+ canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width;
+
+ var height = 1;
+ if (videosLength === 3 || videosLength === 4) {
+ height = 2;
+ }
+ if (videosLength === 5 || videosLength === 6) {
+ height = 3;
+ }
+ if (videosLength === 7 || videosLength === 8) {
+ height = 4;
+ }
+ if (videosLength === 9 || videosLength === 10) {
+ height = 5;
+ }
+ canvas.height = remaining[0].height * height;
+ } else {
+ canvas.width = self.width || 360;
+ canvas.height = self.height || 240;
+ }
+
+ if (fullcanvas && fullcanvas instanceof HTMLVideoElement) {
+ drawImage(fullcanvas);
+ }
+
+ remaining.forEach(function(video, idx) {
+ drawImage(video, idx);
+ });
+
+ setTimeout(drawVideosToCanvas, self.frameInterval);
+ }
+
+ function drawImage(video, idx) {
+ if (isStopDrawingFrames) {
+ return;
+ }
+
+ var x = 0;
+ var y = 0;
+ var width = video.width;
+ var height = video.height;
+
+ if (idx === 1) {
+ x = video.width;
+ }
+
+ if (idx === 2) {
+ y = video.height;
+ }
+
+ if (idx === 3) {
+ x = video.width;
+ y = video.height;
+ }
+
+ if (idx === 4) {
+ y = video.height * 2;
+ }
+
+ if (idx === 5) {
+ x = video.width;
+ y = video.height * 2;
+ }
+
+ if (idx === 6) {
+ y = video.height * 3;
+ }
+
+ if (idx === 7) {
+ x = video.width;
+ y = video.height * 3;
+ }
+
+ if (typeof video.stream.left !== 'undefined') {
+ x = video.stream.left;
+ }
+
+ if (typeof video.stream.top !== 'undefined') {
+ y = video.stream.top;
+ }
+
+ if (typeof video.stream.width !== 'undefined') {
+ width = video.stream.width;
+ }
+
+ if (typeof video.stream.height !== 'undefined') {
+ height = video.stream.height;
+ }
+
+ context.drawImage(video, x, y, width, height);
+
+ if (typeof video.stream.onRender === 'function') {
+ video.stream.onRender(context, x, y, width, height, idx);
+ }
+ }
+
+ function getMixedStream() {
+ isStopDrawingFrames = false;
+ var mixedVideoStream = getMixedVideoStream();
+
+ var mixedAudioStream = getMixedAudioStream();
+ if (mixedAudioStream) {
+ mixedAudioStream.getTracks().filter(function(t) {
+ return t.kind === 'audio';
+ }).forEach(function(track) {
+ mixedVideoStream.addTrack(track);
+ });
+ }
+
+ var fullcanvas;
+ arrayOfMediaStreams.forEach(function(stream) {
+ if (stream.fullcanvas) {
+ fullcanvas = true;
+ }
+ });
+
+ // mixedVideoStream.prototype.appendStreams = appendStreams;
+ // mixedVideoStream.prototype.resetVideoStreams = resetVideoStreams;
+ // mixedVideoStream.prototype.clearRecordedData = clearRecordedData;
+
+ return mixedVideoStream;
+ }
+
+ function getMixedVideoStream() {
+ resetVideoStreams();
+
+ var capturedStream;
+
+ if ('captureStream' in canvas) {
+ capturedStream = canvas.captureStream();
+ } else if ('mozCaptureStream' in canvas) {
+ capturedStream = canvas.mozCaptureStream();
+ } else if (!self.disableLogs) {
+ console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features');
+ }
+
+ var videoStream = new MediaStream();
+
+ capturedStream.getTracks().filter(function(t) {
+ return t.kind === 'video';
+ }).forEach(function(track) {
+ videoStream.addTrack(track);
+ });
+
+ canvas.stream = videoStream;
+
+ return videoStream;
+ }
+
+ function getMixedAudioStream() {
+ // via: @pehrsons
+ if (!Storage.AudioContextConstructor) {
+ Storage.AudioContextConstructor = new Storage.AudioContext();
+ }
+
+ self.audioContext = Storage.AudioContextConstructor;
+
+ self.audioSources = [];
+
+ if (self.useGainNode === true) {
+ self.gainNode = self.audioContext.createGain();
+ self.gainNode.connect(self.audioContext.destination);
+ self.gainNode.gain.value = 0; // don't hear self
+ }
+
+ var audioTracksLength = 0;
+ arrayOfMediaStreams.forEach(function(stream) {
+ if (!stream.getTracks().filter(function(t) {
+ return t.kind === 'audio';
+ }).length) {
+ return;
+ }
+
+ audioTracksLength++;
+
+ var audioSource = self.audioContext.createMediaStreamSource(stream);
+
+ if (self.useGainNode === true) {
+ audioSource.connect(self.gainNode);
+ }
+
+ self.audioSources.push(audioSource);
+ });
+
+ self.audioDestination = self.audioContext.createMediaStreamDestination();
+ self.audioSources.forEach(function(audioSource) {
+ audioSource.connect(self.audioDestination);
+ });
+ return self.audioDestination.stream;
+ }
+
+ function getVideo(stream) {
+ var video = document.createElement('video');
+
+ setSrcObject(stream, video);
+
+ video.className = elementClass;
+
+ video.muted = true;
+ video.volume = 0;
+
+ video.width = stream.width || self.width || 360;
+ video.height = stream.height || self.height || 240;
+
+ video.play();
+
+ return video;
+ }
+
+ this.appendStreams = function(streams) {
+ if (!streams) {
+ throw 'First parameter is required.';
+ }
+
+ if (!(streams instanceof Array)) {
+ streams = [streams];
+ }
+
+ streams.forEach(function(stream) {
+ arrayOfMediaStreams.push(stream);
+
+ var newStream = new MediaStream();
+
+ if (stream.getTracks().filter(function(t) {
+ return t.kind === 'video';
+ }).length) {
+ var video = getVideo(stream);
+ video.stream = stream;
+ videos.push(video);
+
+ newStream.addTrack(stream.getTracks().filter(function(t) {
+ return t.kind === 'video';
+ })[0]);
+ }
+
+ if (stream.getTracks().filter(function(t) {
+ return t.kind === 'audio';
+ }).length) {
+ var audioSource = self.audioContext.createMediaStreamSource(stream);
+ // self.audioDestination = self.audioContext.createMediaStreamDestination();
+ audioSource.connect(self.audioDestination);
+
+ newStream.addTrack(self.audioDestination.stream.getTracks().filter(function(t) {
+ return t.kind === 'audio';
+ })[0]);
+ }
+ });
+ };
+
+ this.releaseStreams = function() {
+ videos = [];
+ isStopDrawingFrames = true;
+
+ if (self.gainNode) {
+ self.gainNode.disconnect();
+ self.gainNode = null;
+ }
+
+ if (self.audioSources.length) {
+ self.audioSources.forEach(function(source) {
+ source.disconnect();
+ });
+ self.audioSources = [];
+ }
+
+ if (self.audioDestination) {
+ self.audioDestination.disconnect();
+ self.audioDestination = null;
+ }
+
+ if (self.audioContext) {
+ self.audioContext.close();
+ }
+
+ self.audioContext = null;
+
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (canvas.stream) {
+ canvas.stream.stop();
+ canvas.stream = null;
+ }
+ };
+
+ this.resetVideoStreams = function(streams) {
+ if (streams && !(streams instanceof Array)) {
+ streams = [streams];
+ }
+
+ resetVideoStreams(streams);
+ };
+
+ function resetVideoStreams(streams) {
+ videos = [];
+ streams = streams || arrayOfMediaStreams;
+
+ // via: @adrian-ber
+ streams.forEach(function(stream) {
+ if (!stream.getTracks().filter(function(t) {
+ return t.kind === 'video';
+ }).length) {
+ return;
+ }
+
+ var video = getVideo(stream);
+ video.stream = stream;
+ videos.push(video);
+ });
+ }
+
+ // for debugging
+ this.name = 'MultiStreamsMixer';
+ this.toString = function() {
+ return this.name;
+ };
+
+ this.getMixedStream = getMixedStream;
+
+}
+
+if (typeof RecordRTC === 'undefined') {
+ if (typeof module !== 'undefined' /* && !!module.exports*/ ) {
+ module.exports = MultiStreamsMixer;
+ }
+
+ if (typeof define === 'function' && define.amd) {
+ define('MultiStreamsMixer', [], function() {
+ return MultiStreamsMixer;
+ });
+ }
+}
diff --git a/dev/MultiStreamsMixer.min.js b/dev/MultiStreamsMixer.min.js
new file mode 100644
index 0000000..3b795b8
--- /dev/null
+++ b/dev/MultiStreamsMixer.min.js
@@ -0,0 +1,13 @@
+// Last time updated: 2019-06-24 6:39:10 PM UTC
+
+// ________________________
+// MultiStreamsMixer v1.2.3
+
+// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
+
+// --------------------------------------------------
+// Muaz Khan - www.MuazKhan.com
+// MIT License - www.WebRTC-Experiment.com/licence
+// --------------------------------------------------
+
+function MultiStreamsMixer(arrayOfMediaStreams,elementClass){function setSrcObject(stream,element){"srcObject"in element?element.srcObject=stream:"mozSrcObject"in element?element.mozSrcObject=stream:element.srcObject=stream}function drawVideosToCanvas(){if(!isStopDrawingFrames){var videosLength=videos.length,fullcanvas=!1,remaining=[];if(videos.forEach(function(video){video.stream||(video.stream={}),video.stream.fullcanvas?fullcanvas=video:remaining.push(video)}),fullcanvas)canvas.width=fullcanvas.stream.width,canvas.height=fullcanvas.stream.height;else if(remaining.length){canvas.width=videosLength>1?2*remaining[0].width:remaining[0].width;var height=1;3!==videosLength&&4!==videosLength||(height=2),5!==videosLength&&6!==videosLength||(height=3),7!==videosLength&&8!==videosLength||(height=4),9!==videosLength&&10!==videosLength||(height=5),canvas.height=remaining[0].height*height}else canvas.width=self.width||360,canvas.height=self.height||240;fullcanvas&&fullcanvas instanceof HTMLVideoElement&&drawImage(fullcanvas),remaining.forEach(function(video,idx){drawImage(video,idx)}),setTimeout(drawVideosToCanvas,self.frameInterval)}}function drawImage(video,idx){if(!isStopDrawingFrames){var x=0,y=0,width=video.width,height=video.height;1===idx&&(x=video.width),2===idx&&(y=video.height),3===idx&&(x=video.width,y=video.height),4===idx&&(y=2*video.height),5===idx&&(x=video.width,y=2*video.height),6===idx&&(y=3*video.height),7===idx&&(x=video.width,y=3*video.height),"undefined"!=typeof video.stream.left&&(x=video.stream.left),"undefined"!=typeof video.stream.top&&(y=video.stream.top),"undefined"!=typeof video.stream.width&&(width=video.stream.width),"undefined"!=typeof video.stream.height&&(height=video.stream.height),context.drawImage(video,x,y,width,height),"function"==typeof video.stream.onRender&&video.stream.onRender(context,x,y,width,height,idx)}}function getMixedStream(){isStopDrawingFrames=!1;var mixedVideoStream=getMixedVideoStream(),mixedAudioStream=getMixedAudioStream();mixedAudioStream&&mixedAudioStream.getTracks().filter(function(t){return"audio"===t.kind}).forEach(function(track){mixedVideoStream.addTrack(track)});var fullcanvas;return arrayOfMediaStreams.forEach(function(stream){stream.fullcanvas&&(fullcanvas=!0)}),mixedVideoStream}function getMixedVideoStream(){resetVideoStreams();var capturedStream;"captureStream"in canvas?capturedStream=canvas.captureStream():"mozCaptureStream"in canvas?capturedStream=canvas.mozCaptureStream():self.disableLogs||console.error("Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");var videoStream=new MediaStream;return capturedStream.getTracks().filter(function(t){return"video"===t.kind}).forEach(function(track){videoStream.addTrack(track)}),canvas.stream=videoStream,videoStream}function getMixedAudioStream(){Storage.AudioContextConstructor||(Storage.AudioContextConstructor=new Storage.AudioContext),self.audioContext=Storage.AudioContextConstructor,self.audioSources=[],self.useGainNode===!0&&(self.gainNode=self.audioContext.createGain(),self.gainNode.connect(self.audioContext.destination),self.gainNode.gain.value=0);var audioTracksLength=0;return arrayOfMediaStreams.forEach(function(stream){if(stream.getTracks().filter(function(t){return"audio"===t.kind}).length){audioTracksLength++;var audioSource=self.audioContext.createMediaStreamSource(stream);self.useGainNode===!0&&audioSource.connect(self.gainNode),self.audioSources.push(audioSource)}}),self.audioDestination=self.audioContext.createMediaStreamDestination(),self.audioSources.forEach(function(audioSource){audioSource.connect(self.audioDestination)}),self.audioDestination.stream}function getVideo(stream){var video=document.createElement("video");return setSrcObject(stream,video),video.className=elementClass,video.muted=!0,video.volume=0,video.width=stream.width||self.width||360,video.height=stream.height||self.height||240,video.play(),video}function resetVideoStreams(streams){videos=[],streams=streams||arrayOfMediaStreams,streams.forEach(function(stream){if(stream.getTracks().filter(function(t){return"video"===t.kind}).length){var video=getVideo(stream);video.stream=stream,videos.push(video)}})}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";!function(that){"undefined"==typeof RecordRTC&&that&&"undefined"==typeof window&&"undefined"!=typeof global&&(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},global.console||(global.console={}),"undefined"!=typeof global.console.log&&"undefined"!=typeof global.console.error||(global.console.error=global.console.log=global.console.log||function(){console.log(arguments)}),"undefined"==typeof document&&(that.document={documentElement:{appendChild:function(){return""}}},document.createElement=document.captureStream=document.mozCaptureStream=function(){var obj={getContext:function(){return obj},play:function(){},pause:function(){},drawImage:function(){},toDataURL:function(){return""},style:{}};return obj},that.HTMLVideoElement=function(){}),"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}),"undefined"==typeof URL&&(that.URL={createObjectURL:function(){return""},revokeObjectURL:function(){return""}}),that.window=global)}("undefined"!=typeof global?global:null),elementClass=elementClass||"multi-streams-mixer";var videos=[],isStopDrawingFrames=!1,canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.style.opacity=0,canvas.style.position="absolute",canvas.style.zIndex=-1,canvas.style.top="-1000em",canvas.style.left="-1000em",canvas.className=elementClass,(document.body||document.documentElement).appendChild(canvas),this.disableLogs=!1,this.frameInterval=10,this.width=360,this.height=240,this.useGainNode=!0;var self=this,AudioContext=window.AudioContext;"undefined"==typeof AudioContext&&("undefined"!=typeof webkitAudioContext&&(AudioContext=webkitAudioContext),"undefined"!=typeof mozAudioContext&&(AudioContext=mozAudioContext));var URL=window.URL;"undefined"==typeof URL&&"undefined"!=typeof webkitURL&&(URL=webkitURL),"undefined"!=typeof navigator&&"undefined"==typeof navigator.getUserMedia&&("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia));var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"undefined"==typeof MediaStream.prototype.stop&&(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})});var Storage={};"undefined"!=typeof AudioContext?Storage.AudioContext=AudioContext:"undefined"!=typeof webkitAudioContext&&(Storage.AudioContext=webkitAudioContext),this.startDrawingFrames=function(){drawVideosToCanvas()},this.appendStreams=function(streams){if(!streams)throw"First parameter is required.";streams instanceof Array||(streams=[streams]),streams.forEach(function(stream){arrayOfMediaStreams.push(stream);var newStream=new MediaStream;if(stream.getTracks().filter(function(t){return"video"===t.kind}).length){var video=getVideo(stream);video.stream=stream,videos.push(video),newStream.addTrack(stream.getTracks().filter(function(t){return"video"===t.kind})[0])}if(stream.getTracks().filter(function(t){return"audio"===t.kind}).length){var audioSource=self.audioContext.createMediaStreamSource(stream);audioSource.connect(self.audioDestination),newStream.addTrack(self.audioDestination.stream.getTracks().filter(function(t){return"audio"===t.kind})[0])}})},this.releaseStreams=function(){videos=[],isStopDrawingFrames=!0,self.gainNode&&(self.gainNode.disconnect(),self.gainNode=null),self.audioSources.length&&(self.audioSources.forEach(function(source){source.disconnect()}),self.audioSources=[]),self.audioDestination&&(self.audioDestination.disconnect(),self.audioDestination=null),self.audioContext&&self.audioContext.close(),self.audioContext=null,context.clearRect(0,0,canvas.width,canvas.height),canvas.stream&&(canvas.stream.stop(),canvas.stream=null)},this.resetVideoStreams=function(streams){!streams||streams instanceof Array||(streams=[streams]),resetVideoStreams(streams)},this.name="MultiStreamsMixer",this.toString=function(){return this.name},this.getMixedStream=getMixedStream}"undefined"==typeof RecordRTC&&("undefined"!=typeof module&&(module.exports=MultiStreamsMixer),"function"==typeof define&&define.amd&&define("MultiStreamsMixer",[],function(){return MultiStreamsMixer}));
\ No newline at end of file
diff --git a/dist/README.md b/dist/README.md
new file mode 100644
index 0000000..0a226a6
--- /dev/null
+++ b/dist/README.md
@@ -0,0 +1,25 @@
+## Link Script Files
+
+```html
+
+
+
+
+
+
+
+
+
+
+```
+
+If you're sharing files, you also need to link:
+
+```html
+
+
+
+
+```
+
+> You can link multiple files from `dev` directory. Order doesn't matters.
diff --git a/dist/RTCMultiConnection.js b/dist/RTCMultiConnection.js
new file mode 100644
index 0000000..31e88c8
--- /dev/null
+++ b/dist/RTCMultiConnection.js
@@ -0,0 +1,5910 @@
+'use strict';
+
+// Last time updated: 2019-06-15 4:26:11 PM UTC
+
+// _________________________
+// RTCMultiConnection v3.6.9
+
+// 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(callbcak) {
+ this.getAllParticipants().forEach(function(participant) {
+ callbcak(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: 2019-01-10 5:32:55 AM UTC
+
+ // ________________
+ // DetectRTC v1.3.9
+
+ // 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 = typeof window.InstallTrigger !== 'undefined';
+ 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;
+
+ // both and safri and chrome has same userAgent
+ if (isSafari && !isChrome && nAgt.indexOf('CriOS') !== -1) {
+ isSafari = false;
+ isChrome = true;
+ }
+
+ // 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) {
+ 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: '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);
+ 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) {
+ 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; // navigator.getDisplayMedia
+ } else if (DetectRTC.osName === 'Android' && DetectRTC.browser.isChrome) {
+ 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.3.9';
+
+ 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() {
+ 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 = '
0% ';
+
+ 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 = '
Download ' + file.name + ' ';
+ } else {
+ div.innerHTML = '
Download ' + file.name + ' ';
+ }
+ };
+
+ function updateLabel(progress, label) {
+ if (progress.position === -1) {
+ return;
+ }
+
+ var position = +progress.position.toFixed(2).split('.')[1] || 100;
+ label.innerHTML = position + '%';
+ }
+ }
+
+ return {
+ handle: handle
+ };
+ })();
+
+ // TranslationHandler.js
+
+ var TranslationHandler = (function() {
+ function handle(connection) {
+ connection.autoTranslateText = false;
+ connection.language = 'en';
+ connection.googKey = 'AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE';
+
+ // www.RTCMultiConnection.org/docs/Translator/
+ connection.Translator = {
+ TranslateText: function(text, callback) {
+ // if(location.protocol === 'https:') return callback(text);
+
+ var newScript = document.createElement('script');
+ newScript.type = 'text/javascript';
+
+ var sourceText = encodeURIComponent(text); // escape
+
+ var randomNumber = 'method' + connection.token();
+ window[randomNumber] = function(response) {
+ if (response.data && response.data.translations[0] && callback) {
+ callback(response.data.translations[0].translatedText);
+ return;
+ }
+
+ if (response.error && response.error.message === 'Daily Limit Exceeded') {
+ console.error('Text translation failed. Error message: "Daily Limit Exceeded."');
+ return;
+ }
+
+ if (response.error) {
+ console.error(response.error.message);
+ return;
+ }
+
+ console.error(response);
+ };
+
+ var source = 'https://www.googleapis.com/language/translate/v2?key=' + connection.googKey + '&target=' + (connection.language || 'en-US') + '&callback=window.' + randomNumber + '&q=' + sourceText;
+ newScript.src = source;
+ document.getElementsByTagName('head')[0].appendChild(newScript);
+ },
+ getListOfLanguages: function(callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == XMLHttpRequest.DONE) {
+ var response = JSON.parse(xhr.responseText);
+
+ if (response && response.data && response.data.languages) {
+ callback(response.data.languages);
+ return;
+ }
+
+ if (response.error && response.error.message === 'Daily Limit Exceeded') {
+ console.error('Text translation failed. Error message: "Daily Limit Exceeded."');
+ return;
+ }
+
+ if (response.error) {
+ console.error(response.error.message);
+ return;
+ }
+
+ console.error(response);
+ }
+ }
+ var url = 'https://www.googleapis.com/language/translate/v2/languages?key=' + connection.googKey + '&target=en';
+ xhr.open('GET', url, true);
+ xhr.send(null);
+ }
+ };
+ }
+
+ 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);
+ }
+
+ connection.streamEvents[stream.streamid] = {
+ stream: stream,
+ type: 'local',
+ mediaElement: mediaElement,
+ userid: connection.userid,
+ extra: connection.extra,
+ streamid: stream.streamid,
+ isAudioMuted: true
+ };
+
+ 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.6.9';
+
+ 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;
+ });
+}
diff --git a/dist/RTCMultiConnection.min.js b/dist/RTCMultiConnection.min.js
new file mode 100644
index 0000000..939445d
--- /dev/null
+++ b/dist/RTCMultiConnection.min.js
@@ -0,0 +1,18 @@
+'use strict';
+
+// Last time updated: 2019-06-15 4:26:11 PM UTC
+
+// _________________________
+// RTCMultiConnection v3.6.9
+
+// Open-Sourced: https://github.com/muaz-khan/RTCMultiConnection
+
+// --------------------------------------------------
+// Muaz Khan - www.MuazKhan.com
+// MIT License - www.WebRTC-Experiment.com/licence
+// --------------------------------------------------
+
+"use strict";var RTCMultiConnection=function(roomid,forceOptions){function SocketConnection(connection,connectCallback){function isData(session){return!session.audio&&!session.video&&!session.screen&&session.data}function updateExtraBackup(remoteUserId,extra){connection.peersBackup[remoteUserId]||(connection.peersBackup[remoteUserId]={userid:remoteUserId,extra:{}}),connection.peersBackup[remoteUserId].extra=extra}function onMessageEvent(message){if(message.remoteUserId==connection.userid){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)),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("ended"===action||"inactive"===action||"stream-removed"===action)return connection.peersBackup[stream.userid]&&(stream.extra=connection.peersBackup[stream.userid].extra),void connection.onstreamended(stream);var type="both"!=message.message.type?message.message.type:null;return void("function"==typeof stream.stream[action]&&stream.stream[action](type))}if("dropPeerConnection"===message.message)return void connection.deletePeer(message.sender);if(message.message.allParticipants)return message.message.allParticipants.indexOf(message.sender)===-1&&message.message.allParticipants.push(message.sender),void message.message.allParticipants.forEach(function(participant){mPeer[connection.peers[participant]?"renegotiatePeer":"createNewPeer"](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||"one-way"===connection.direction,isDataOnly:isData(connection.session)})});if(message.message.newParticipant){if(message.message.newParticipant==connection.userid)return;if(connection.peers[message.message.newParticipant])return;return void 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||"one-way"===connection.direction,isDataOnly:isData(connection.session)})}if(message.message.readyForOffer&&(connection.attachStreams.length&&(connection.waitingForLocalMedia=!1),connection.waitingForLocalMedia))return void setTimeout(function(){onMessageEvent(message)},1);if(message.message.newParticipationRequest&&message.sender!==connection.userid){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:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,isDataOnly:"undefined"!=typeof message.message.isDataOnly?message.message.isDataOnly:isData(connection.session),dontGetRemoteStream:"undefined"!=typeof message.message.isOneWay?message.message.isOneWay:!!connection.session.oneway||"one-way"===connection.direction,dontAttachLocalStream:!!message.message.dontGetRemoteStream,connectionDescription:message,successCallback:function(){}};return void connection.onNewParticipant(message.sender,userPreferences)}return message.message.changedUUID&&connection.peers[message.message.oldUUID]&&(connection.peers[message.message.newUUID]=connection.peers[message.message.oldUUID],delete connection.peers[message.message.oldUUID]),message.message.userLeft?(mPeer.onUserLeft(message.sender),void(message.message.autoCloseEntireSession&&connection.leave())):void mPeer.addNegotiatedMessage(message.message,message.sender)}}var parameters="";parameters+="?userid="+connection.userid,parameters+="&sessionid="+connection.sessionid,parameters+="&msgEvent="+connection.socketMessageEvent,parameters+="&socketCustomEvent="+connection.socketCustomEvent,parameters+="&autoCloseEntireSession="+!!connection.autoCloseEntireSession,connection.session.broadcast===!0&&(parameters+="&oneToMany=true"),parameters+="&maxParticipantsAllowed="+connection.maxParticipantsAllowed,connection.enableScalableBroadcast&&(parameters+="&enableScalableBroadcast=true",parameters+="&maxRelayLimitPerUser="+(connection.maxRelayLimitPerUser||2)),parameters+="&extra="+JSON.stringify(connection.extra||{}),connection.socketCustomParameters&&(parameters+=connection.socketCustomParameters);try{io.sockets={}}catch(e){}if(connection.socketURL||(connection.socketURL="/"),"/"!=connection.socketURL.substr(connection.socketURL.length-1,1))throw'"socketURL" MUST end with a slash.';connection.enableLogs&&("/"==connection.socketURL?console.info("socket.io url is: ",location.origin+"/"):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){connection.peers[remoteUserId]&&(connection.peers[remoteUserId].extra=extra,connection.onExtraDataUpdated({userid:remoteUserId,extra:extra}),updateExtraBackup(remoteUserId,extra))}),connection.socket.on(connection.socketMessageEvent,onMessageEvent);var alreadyConnected=!1;connection.socket.resetProps=function(){alreadyConnected=!1},connection.socket.on("connect",function(){alreadyConnected||(alreadyConnected=!0,connection.enableLogs&&console.info("socket.io connection is opened."),setTimeout(function(){connection.socket.emit("extra-data-updated",connection.extra)},1e3),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){remoteUserId!==connection.userid&&(connection.onUserStatusChanged({userid:remoteUserId,status:"offline",extra:connection.peers[remoteUserId]?connection.peers[remoteUserId].extra||{}:{}}),connection.deletePeer(remoteUserId))}),connection.socket.on("user-connected",function(userid){userid!==connection.userid&&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){connection.enableLogs&&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){sessionid==connection.sessionid&&(connection.isInitiator=!0)})}function MultiPeers(connection){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)}}var self=this,skipPeers=["getAllParticipants","getLength","selectFirst","streams","send","forEach"];connection.peers={getLength:function(){var numberOfPeers=0;for(var peer in this)skipPeers.indexOf(peer)==-1&&numberOfPeers++;return numberOfPeers},selectFirst:function(){var firstPeer;for(var peer in this)skipPeers.indexOf(peer)==-1&&(firstPeer=this[peer]);return firstPeer},getAllParticipants:function(sender){var allPeers=[];for(var peer in this)skipPeers.indexOf(peer)==-1&&peer!=sender&&allPeers.push(peer);return allPeers},forEach:function(callbcak){this.getAllParticipants().forEach(function(participant){callbcak(connection.peers[participant])})},send:function(data,remoteUserId){var that=this;if(!isNull(data.size)&&!isNull(data.type)){if(connection.enableFileSharing)return void self.shareFile(data,remoteUserId);"string"!=typeof data&&(data=JSON.stringify(data))}if(!("text"===data.type||data instanceof ArrayBuffer||data instanceof DataView))return void TextSender.send({text:data,channel:this,connection:connection,remoteUserId:remoteUserId});if("text"===data.type&&(data=JSON.stringify(data)),remoteUserId){var remoteUser=connection.peers[remoteUserId];if(remoteUser)return remoteUser.channels.length?void remoteUser.channels.forEach(function(channel){channel.send(data)}):(connection.peers[remoteUserId].createDataChannel(),connection.renegotiate(remoteUserId),void setTimeout(function(){that.send(data,remoteUserId)},3e3))}this.getAllParticipants().forEach(function(participant){return that[participant].channels.length?void that[participant].channels.forEach(function(channel){channel.send(data)}):(connection.peers[participant].createDataChannel(),connection.renegotiate(participant),void setTimeout(function(){that[participant].channels.forEach(function(channel){channel.send(data)})},3e3))})}},this.uuid=connection.userid,this.getLocalConfig=function(remoteSdp,remoteUserId,userPreferences){return userPreferences||(userPreferences={}),{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),localCandidate&&self.onNegotiationNeeded(localCandidate,remoteUserId)},remoteSdp:remoteSdp,onDataChannelMessage:function(message){if(!connection.fbr&&connection.enableFileSharing&&initFileBufferReader(),"string"==typeof message||!connection.enableFileSharing)return void self.onDataChannelMessage(message,remoteUserId);var that=this;return message instanceof ArrayBuffer||message instanceof DataView?void connection.fbr.convertToObject(message,function(object){that.onDataChannelMessage(object)}):message.readyForNextChunk?void connection.fbr.getNextChunk(message,function(nextChunk,isLastChunk){connection.peers[remoteUserId].channels.forEach(function(channel){channel.send(nextChunk)})},remoteUserId):message.chunkMissing?void connection.fbr.chunkMissing(message):void 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){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),"new"===states.iceConnectionState&&self.onNegotiationStarted(remoteUserId,states),"connected"===states.iceConnectionState&&self.onNegotiationCompleted(remoteUserId,states),states.iceConnectionState.search(/closed|failed/gi)!==-1&&(self.onUserLeft(remoteUserId),self.disconnectWith(remoteUserId))}}},this.createNewPeer=function(remoteUserId,userPreferences){if(!(connection.maxParticipantsAllowed<=connection.getAllParticipants().length)){if(userPreferences=userPreferences||{},connection.isInitiator&&connection.session.audio&&"two-way"===connection.session.audio&&!userPreferences.streamsToShare&&(userPreferences.isOneWay=!1,userPreferences.isDataOnly=!1,userPreferences.session=connection.session),!userPreferences.isOneWay&&!userPreferences.isDataOnly)return userPreferences.isOneWay=!0,void this.onNegotiationNeeded({enableMedia:!0,userPreferences:userPreferences},remoteUserId);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])return void(connection.enableLogs&&console.error("Peer ("+remoteUserId+") does not exist. Renegotiation skipped."));userPreferences||(userPreferences={}),userPreferences.renegotiatingPeer=!0,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;return peer.getSenders&&"function"==typeof peer.getSenders&&peer.getSenders().length?void peer.getSenders().forEach(function(rtpSender){isVideoTrack&&"video"===rtpSender.track.kind&&(connection.peers[remoteUserId].peer.lastVideoTrack=rtpSender.track,rtpSender.replaceTrack(track)),isVideoTrack||"audio"!==rtpSender.track.kind||(connection.peers[remoteUserId].peer.lastAudioTrack=rtpSender.track,rtpSender.replaceTrack(track))}):(console.warn("RTPSender.replaceTrack is NOT supported."),void this.renegotiatePeer(remoteUserId))},this.onNegotiationNeeded=function(message,remoteUserId){},this.addNegotiatedMessage=function(message,remoteUserId){if(message.type&&message.sdp)return"answer"==message.type&&connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteSdp(message),"offer"==message.type&&(message.renegotiatingPeer?this.renegotiatePeer(remoteUserId,null,message):this.createAnsweringPeer(message,remoteUserId)),void(connection.enableLogs&&console.log("Remote peer's sdp:",message.sdp));if(message.candidate)return connection.peers[remoteUserId]&&connection.peers[remoteUserId].addRemoteCandidate(message),void(connection.enableLogs&&console.log("Remote peer's candidate pairs:",message.candidate));if(message.enableMedia){connection.session=message.userPreferences.session||connection.session,connection.session.oneway&&connection.attachStreams.length&&(connection.attachStreams=[]),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:!0,userPreferences:message.userPreferences},remoteUserId)}message.readyForOffer&&connection.onReadyForOffer(remoteUserId,message.userPreferences)},this.onGettingRemoteMedia=function(stream,remoteUserId){},this.onRemovingRemoteMedia=function(stream,remoteUserId){},this.onGettingLocalMedia=function(localStream){},this.onLocalMediaError=function(error,constraints){connection.onMediaError(error,constraints)},this.shareFile=function(file,remoteUserId){initFileBufferReader(),connection.fbr.readAsArrayBuffer(file,function(uuid){var arrayOfUsers=connection.getAllParticipants();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,chunkSize:"Firefox"===DetectRTC.browser.name?15e3:connection.chunkSize||0})};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){return connection.peers[remoteUserId].channels.length?void(connection.peers[remoteUserId].channels=[channel]):(connection.peers[remoteUserId].channels.push(channel),void 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){return remoteUserId=remoteUserId||connection.peers.getAllParticipants()[0],connection.peers[remoteUserId]?connection.peers[remoteUserId].streams:[]}}function fireEvent(obj,eventName,args){if("undefined"!=typeof CustomEvent){var eventDetail={arguments:args,__exposedProps__:args},event=new CustomEvent(eventName,eventDetail);obj.dispatchEvent(event)}}function setHarkEvents(connection,streamEvent){if(streamEvent.stream&&getTracks(streamEvent.stream,"audio").length){if(!connection||!streamEvent)throw"Both arguments are required.";if(connection.onspeaking&&connection.onsilence){if("undefined"==typeof hark)throw"hark.js not found.";hark(streamEvent.stream,{onspeaking:function(){connection.onspeaking(streamEvent)},onsilence:function(){connection.onsilence(streamEvent)},onvolumechange:function(volume,threshold){connection.onvolumechange&&connection.onvolumechange(merge({volume:volume,threshold:threshold},streamEvent))}})}}}function setMuteHandlers(connection,streamEvent){streamEvent.stream&&streamEvent.stream&&streamEvent.stream.addEventListener&&(streamEvent.stream.addEventListener("mute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.muteType,video:"video"===event.muteType},connection.onmute(event)},!1),streamEvent.stream.addEventListener("unmute",function(event){event=connection.streamEvents[streamEvent.streamid],event.session={audio:"audio"===event.unmuteType,video:"video"===event.unmuteType},connection.onunmute(event)},!1))}function getRandomString(){if(window.crypto&&window.crypto.getRandomValues&&navigator.userAgent.indexOf("Safari")===-1){for(var a=window.crypto.getRandomValues(new Uint32Array(3)),token="",i=0,l=a.length;i
0?fullVersion=nAgt.substring(verOffset+3):(verOffset=nAgt.indexOf("MSIE"),fullVersion=nAgt.substring(verOffset+5)),browserName="IE"):isChrome?(verOffset=nAgt.indexOf("Chrome"),browserName="Chrome",fullVersion=nAgt.substring(verOffset+7)):isSafari?(verOffset=nAgt.indexOf("Safari"),browserName="Safari",fullVersion=nAgt.substring(verOffset+7),(verOffset=nAgt.indexOf("Version"))!==-1&&(fullVersion=nAgt.substring(verOffset+8)),navigator.userAgent.indexOf("Version/")!==-1&&(fullVersion=navigator.userAgent.split("Version/")[1].split(" ")[0])):isFirefox?(verOffset=nAgt.indexOf("Firefox"),browserName="Firefox",fullVersion=nAgt.substring(verOffset+8)):(nameOffset=nAgt.lastIndexOf(" ")+1)<(verOffset=nAgt.lastIndexOf("/"))&&(browserName=nAgt.substring(nameOffset,verOffset),fullVersion=nAgt.substring(verOffset+1),browserName.toLowerCase()===browserName.toUpperCase()&&(browserName=navigator.appName));return isEdge&&(browserName="Edge",fullVersion=navigator.userAgent.split("Edge/")[1]),(ix=fullVersion.search(/[; \)]/))!==-1&&(fullVersion=fullVersion.substring(0,ix)),majorVersion=parseInt(""+fullVersion,10),isNaN(majorVersion)&&(fullVersion=""+parseFloat(navigator.appVersion),majorVersion=parseInt(navigator.appVersion,10)),{fullVersion:fullVersion,version:majorVersion,name:browserName,isPrivateBrowsing:!1}}function retry(isDone,next){var currentTrial=0,maxRetry=50,isTimeout=!1,id=window.setInterval(function(){isDone()&&(window.clearInterval(id),next(isTimeout)),currentTrial++>maxRetry&&(window.clearInterval(id),isTimeout=!0,next(isTimeout))},10)}function isIE10OrLater(userAgent){var ua=userAgent.toLowerCase();if(0===ua.indexOf("msie")&&0===ua.indexOf("trident"))return!1;var match=/(?:msie|rv:)\s?([\d\.]+)/.exec(ua);return!!(match&&parseInt(match[1],10)>=10)}function detectPrivateMode(callback){var isPrivate;try{if(window.webkitRequestFileSystem)window.webkitRequestFileSystem(window.TEMPORARY,1,function(){isPrivate=!1},function(e){isPrivate=!0});else if(window.indexedDB&&/Firefox/.test(window.navigator.userAgent)){var db;try{db=window.indexedDB.open("test"),db.onerror=function(){return!0}}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&retry(function(){return"done"===db.readyState},function(isTimeout){isTimeout||(isPrivate=!db.result)})}else if(isIE10OrLater(window.navigator.userAgent)){isPrivate=!1;try{window.indexedDB||(isPrivate=!0)}catch(e){isPrivate=!0}}else if(window.localStorage&&/Safari/.test(window.navigator.userAgent)){try{window.localStorage.setItem("test",1)}catch(e){isPrivate=!0}"undefined"==typeof isPrivate&&(isPrivate=!1,window.localStorage.removeItem("test"))}}catch(e){isPrivate=!1}retry(function(){return"undefined"!=typeof isPrivate},function(isTimeout){callback(isPrivate)})}function detectDesktopOS(){for(var cs,unknown="-",nVer=navigator.appVersion,nAgt=navigator.userAgent,os=unknown,clientStrings=[{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)/}],i=0;cs=clientStrings[i];i++)if(cs.r.test(nAgt)){os=cs.s;break}var osVersion=unknown;switch(/Windows/.test(os)&&(/Windows (.*)/.test(os)&&(osVersion=/Windows (.*)/.exec(os)[1]),os="Windows"),os){case"Mac OS X":/Mac OS X (10[\.\_\d]+)/.test(nAgt)&&(osVersion=/Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);break;case"Android":/Android ([\.\_\d]+)/.test(nAgt)&&(osVersion=/Android ([\.\_\d]+)/.exec(nAgt)[1]);break;case"iOS":/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)&&(osVersion=/OS (\d+)_(\d+)_?(\d+)?/.exec(nVer),osVersion=osVersion[1]+"."+osVersion[2]+"."+(0|osVersion[3]))}return{osName:os,osVersion:osVersion}}function getAndroidVersion(ua){ua=(ua||navigator.userAgent).toLowerCase();var match=ua.match(/android\s([0-9\.]*)/);return!!match&&match[1]}function DetectLocalIPAddress(callback,stream){if(DetectRTC.isWebRTCSupported){var isPublic=!0,isIpv4=!0;getIPs(function(ip){ip?ip.match(regexIpv4Local)?(isPublic=!1,callback("Local: "+ip,isPublic,isIpv4)):ip.match(regexIpv6)?(isIpv4=!1,callback("Public: "+ip,isPublic,isIpv4)):callback("Public: "+ip,isPublic,isIpv4):callback()},stream)}}function getIPs(callback,stream){function handleCandidate(candidate){if(!candidate)return void callback();var match=regexIpv4.exec(candidate);if(match){var ipAddress=match[1],isPublic=candidate.match(regexIpv4Local),isIpv4=!0;void 0===ipDuplicates[ipAddress]&&callback(ipAddress,isPublic,isIpv4),ipDuplicates[ipAddress]=!0}}function afterCreateOffer(){var lines=pc.localDescription.sdp.split("\n");lines.forEach(function(line){line&&0===line.indexOf("a=candidate:")&&handleCandidate(line)})}if("undefined"!=typeof document&&"function"==typeof document.getElementById){var ipDuplicates={},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){var peerConfig=null;"Chrome"===DetectRTC.browser&&DetectRTC.browser.version<58&&(peerConfig={optional:[{RtpDataChannels:!0}]});var servers={iceServers:[{urls:"stun:stun.l.google.com:19302"}]},pc=new RTCPeerConnection(servers,peerConfig);if(stream&&(pc.addStream?pc.addStream(stream):pc.addTrack&&stream.getTracks()[0]&&pc.addTrack(stream.getTracks()[0],stream)),pc.onicecandidate=function(event){event.candidate&&event.candidate.candidate?handleCandidate(event.candidate.candidate):handleCandidate()},!stream)try{pc.createDataChannel("sctp",{})}catch(e){}DetectRTC.isPromisesSupported?pc.createOffer().then(function(result){pc.setLocalDescription(result).then(afterCreateOffer)}):pc.createOffer(function(result){pc.setLocalDescription(result,afterCreateOffer,function(){})},function(){})}}}function checkDeviceSupport(callback){if(!canEnumerate)return void(callback&&callback());if(!navigator.enumerateDevices&&window.MediaStreamTrack&&window.MediaStreamTrack.getSources&&(navigator.enumerateDevices=window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)),!navigator.enumerateDevices&&navigator.enumerateDevices&&(navigator.enumerateDevices=navigator.enumerateDevices.bind(navigator)),!navigator.enumerateDevices)return void(callback&&callback());MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[],hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1;var alreadyUsedDevices={};navigator.enumerateDevices(function(devices){devices.forEach(function(_device){var device={};for(var d in _device)try{"function"!=typeof _device[d]&&(device[d]=_device[d])}catch(e){}alreadyUsedDevices[device.deviceId+device.label+device.kind]||("audio"===device.kind&&(device.kind="audioinput"),"video"===device.kind&&(device.kind="videoinput"),device.deviceId||(device.deviceId=device.id),device.id||(device.id=device.deviceId),device.label?("videoinput"!==device.kind||isWebsiteHasWebcamPermissions||(isWebsiteHasWebcamPermissions=!0),"audioinput"!==device.kind||isWebsiteHasMicrophonePermissions||(isWebsiteHasMicrophonePermissions=!0)):(device.isCustomLabel=!0,"videoinput"===device.kind?device.label="Camera "+(videoInputDevices.length+1):"audioinput"===device.kind?device.label="Microphone "+(audioInputDevices.length+1):"audiooutput"===device.kind?device.label="Speaker "+(audioOutputDevices.length+1):device.label="Please invoke getUserMedia once.","undefined"!=typeof DetectRTC&&DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(device.label="HTTPs is required to get label of this "+device.kind+" device.")),"audioinput"===device.kind&&(hasMicrophone=!0,audioInputDevices.indexOf(device)===-1&&audioInputDevices.push(device)),"audiooutput"===device.kind&&(hasSpeakers=!0,audioOutputDevices.indexOf(device)===-1&&audioOutputDevices.push(device)),"videoinput"===device.kind&&(hasWebcam=!0,videoInputDevices.indexOf(device)===-1&&videoInputDevices.push(device)),MediaDevices.push(device),alreadyUsedDevices[device.deviceId+device.label+device.kind]=device)}),"undefined"!=typeof DetectRTC&&(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),callback&&callback()})}function getAspectRatio(w,h){function gcd(a,b){return 0==b?a:gcd(b,a%b)}var r=gcd(w,h);return w/r/(h/r)}var browserFakeUserAgent="Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node&&!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){"undefined"==typeof window&&("undefined"==typeof window&&"undefined"!=typeof global?(global.navigator={userAgent:browserFakeUserAgent,getUserMedia:function(){}},that.window=global):"undefined"==typeof window,"undefined"==typeof location&&(that.location={protocol:"file:",href:"",hash:""}),"undefined"==typeof screen&&(that.screen={width:0,height:0}))}("undefined"!=typeof global?global:window);var navigator=window.navigator;"undefined"!=typeof navigator?("undefined"!=typeof navigator.webkitGetUserMedia&&(navigator.getUserMedia=navigator.webkitGetUserMedia),"undefined"!=typeof navigator.mozGetUserMedia&&(navigator.getUserMedia=navigator.mozGetUserMedia)):navigator={getUserMedia:function(){},userAgent:browserFakeUserAgent};var isMobileDevice=!!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent||""),isEdge=!(navigator.userAgent.indexOf("Edge")===-1||!navigator.msSaveOrOpenBlob&&!navigator.msSaveBlob),isOpera=!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0,isFirefox="undefined"!=typeof window.InstallTrigger,isSafari=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),isChrome=!!window.chrome&&!isOpera,isIE="undefined"!=typeof document&&!!document.documentMode&&!isEdge,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";return isMobile.Android()&&(osName="Android"),isMobile.BlackBerry()&&(osName="BlackBerry"),isMobile.iOS()&&(osName="iOS"),isMobile.Opera()&&(osName="Opera Mini"),isMobile.Windows()&&(osName="Windows"),osName}},osName="Unknown OS",osVersion="Unknown OS Version",osInfo=detectDesktopOS();osInfo&&osInfo.osName&&"-"!=osInfo.osName?(osName=osInfo.osName,osVersion=osInfo.osVersion):isMobile.any()&&(osName=isMobile.getOsName(),"Android"==osName&&(osVersion=getAndroidVersion()));var isNodejs="object"==typeof process&&"object"==typeof process.versions&&process.versions.node;"Unknown OS"===osName&&isNodejs&&(osName="Nodejs",osVersion=process.versions.node.toString().replace("v",""));var isCanvasSupportsStreamCapturing=!1,isVideoSupportsStreamCapturing=!1;["captureStream","mozCaptureStream","webkitCaptureStream"].forEach(function(item){"undefined"!=typeof document&&"function"==typeof document.createElement&&(!isCanvasSupportsStreamCapturing&&item in document.createElement("canvas")&&(isCanvasSupportsStreamCapturing=!0),!isVideoSupportsStreamCapturing&&item in document.createElement("video")&&(isVideoSupportsStreamCapturing=!0))});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}/,MediaDevices=[],audioInputDevices=[],audioOutputDevices=[],videoInputDevices=[];navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(navigator.enumerateDevices=function(callback){var enumerateDevices=navigator.mediaDevices.enumerateDevices();enumerateDevices&&enumerateDevices.then?navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function(){callback([])}):callback([])});var canEnumerate=!1;"undefined"!=typeof MediaStreamTrack&&"getSources"in MediaStreamTrack?canEnumerate=!0:navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices&&(canEnumerate=!0);var hasMicrophone=!1,hasSpeakers=!1,hasWebcam=!1,isWebsiteHasMicrophonePermissions=!1,isWebsiteHasWebcamPermissions=!1,DetectRTC=window.DetectRTC||{};DetectRTC.browser=getBrowserInfo(),detectPrivateMode(function(isPrivateBrowsing){DetectRTC.browser.isPrivateBrowsing=!!isPrivateBrowsing}),DetectRTC.browser["is"+DetectRTC.browser.name]=!0,DetectRTC.osName=osName,DetectRTC.osVersion=osVersion;var isWebRTCSupported=("object"==typeof process&&"object"==typeof process.versions&&process.versions["node-webkit"],!1);["RTCPeerConnection","webkitRTCPeerConnection","mozRTCPeerConnection","RTCIceGatherer"].forEach(function(item){isWebRTCSupported||item in window&&(isWebRTCSupported=!0)}),DetectRTC.isWebRTCSupported=isWebRTCSupported,DetectRTC.isORTCSupported="undefined"!=typeof RTCIceGatherer;var isScreenCapturingSupported=!1;if(DetectRTC.browser.isChrome&&DetectRTC.browser.version>=35?isScreenCapturingSupported=!0:DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=34?isScreenCapturingSupported=!0:DetectRTC.browser.isEdge&&DetectRTC.browser.version>=17?isScreenCapturingSupported=!0:"Android"===DetectRTC.osName&&DetectRTC.browser.isChrome&&(isScreenCapturingSupported=!0),!/^(https:|chrome-extension:)$/g.test(location.protocol||"")){var isNonLocalHost="undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1;isNonLocalHost&&(DetectRTC.browser.isChrome||DetectRTC.browser.isEdge||DetectRTC.browser.isOpera)?isScreenCapturingSupported=!1:DetectRTC.browser.isFirefox&&(isScreenCapturingSupported=!1)}DetectRTC.isScreenCapturingSupported=isScreenCapturingSupported;var webAudio={isSupported:!1,isCreateMediaStreamSourceSupported:!1};["AudioContext","webkitAudioContext","mozAudioContext","msAudioContext"].forEach(function(item){webAudio.isSupported||item in window&&(webAudio.isSupported=!0,window[item]&&"createMediaStreamSource"in window[item].prototype&&(webAudio.isCreateMediaStreamSourceSupported=!0))}),DetectRTC.isAudioContextSupported=webAudio.isSupported,DetectRTC.isCreateMediaStreamSourceSupported=webAudio.isCreateMediaStreamSourceSupported;var isRtpDataChannelsSupported=!1;DetectRTC.browser.isChrome&&DetectRTC.browser.version>31&&(isRtpDataChannelsSupported=!0),DetectRTC.isRtpDataChannelsSupported=isRtpDataChannelsSupported;var isSCTPSupportd=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>28?isSCTPSupportd=!0:DetectRTC.browser.isChrome&&DetectRTC.browser.version>25?isSCTPSupportd=!0:DetectRTC.browser.isOpera&&DetectRTC.browser.version>=11&&(isSCTPSupportd=!0),DetectRTC.isSctpDataChannelsSupported=isSCTPSupportd,DetectRTC.isMobileDevice=isMobileDevice;var isGetUserMediaSupported=!1;navigator.getUserMedia?isGetUserMediaSupported=!0:navigator.mediaDevices&&navigator.mediaDevices.getUserMedia&&(isGetUserMediaSupported=!0),DetectRTC.browser.isChrome&&DetectRTC.browser.version>=46&&!/^(https:|chrome-extension:)$/g.test(location.protocol||"")&&"undefined"!=typeof document&&"string"==typeof document.domain&&document.domain.search&&document.domain.search(/localhost|127.0./g)===-1&&(isGetUserMediaSupported="Requires HTTPs"),"Nodejs"===DetectRTC.osName&&(isGetUserMediaSupported=!1),DetectRTC.isGetUserMediaSupported=isGetUserMediaSupported;var displayResolution="";if(screen.width){var width=screen.width?screen.width:"",height=screen.height?screen.height:"";displayResolution+=""+width+" x "+height}DetectRTC.displayResolution=displayResolution,DetectRTC.displayAspectRatio=getAspectRatio(screen.width,screen.height).toFixed(2),DetectRTC.isCanvasSupportsStreamCapturing=isCanvasSupportsStreamCapturing,DetectRTC.isVideoSupportsStreamCapturing=isVideoSupportsStreamCapturing,"Chrome"==DetectRTC.browser.name&&DetectRTC.browser.version>=53&&(DetectRTC.isCanvasSupportsStreamCapturing||(DetectRTC.isCanvasSupportsStreamCapturing="Requires chrome flag: enable-experimental-web-platform-features"),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,"Nodejs"===DetectRTC.osName&&(DetectRTC.isWebSocketsSupported=!0,DetectRTC.isWebSocketsBlocked=!1),DetectRTC.checkWebSocketsSupport=function(callback){callback=callback||function(){};try{var starttime,websocket=new WebSocket("wss://echo.websocket.org:443/");websocket.onopen=function(){DetectRTC.isWebSocketsBlocked=!1,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=!0,callback()}}catch(e){DetectRTC.isWebSocketsBlocked=!0,callback()}},DetectRTC.load=function(callback){callback=callback||function(){},checkDeviceSupport(callback)},"undefined"!=typeof MediaDevices?DetectRTC.MediaDevices=MediaDevices: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=!1;"undefined"!=typeof document&&"function"==typeof document.createElement&&"setSinkId"in document.createElement("video")&&(isSetSinkIdSupported=!0),DetectRTC.isSetSinkIdSupported=isSetSinkIdSupported;var isRTPSenderReplaceTracksSupported=!1;DetectRTC.browser.isFirefox&&"undefined"!=typeof mozRTCPeerConnection?"getSenders"in mozRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0):DetectRTC.browser.isChrome&&"undefined"!=typeof webkitRTCPeerConnection&&"getSenders"in webkitRTCPeerConnection.prototype&&(isRTPSenderReplaceTracksSupported=!0),DetectRTC.isRTPSenderReplaceTracksSupported=isRTPSenderReplaceTracksSupported;var isRemoteStreamProcessingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>38&&(isRemoteStreamProcessingSupported=!0),DetectRTC.isRemoteStreamProcessingSupported=isRemoteStreamProcessingSupported;var isApplyConstraintsSupported=!1;"undefined"!=typeof MediaStreamTrack&&"applyConstraints"in MediaStreamTrack.prototype&&(isApplyConstraintsSupported=!0),DetectRTC.isApplyConstraintsSupported=isApplyConstraintsSupported;var isMultiMonitorScreenCapturingSupported=!1;DetectRTC.browser.isFirefox&&DetectRTC.browser.version>=43&&(isMultiMonitorScreenCapturingSupported=!0),DetectRTC.isMultiMonitorScreenCapturingSupported=isMultiMonitorScreenCapturingSupported,DetectRTC.isPromisesSupported=!!("Promise"in window),DetectRTC.version="1.3.9","undefined"==typeof DetectRTC&&(window.DetectRTC={});var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&"function"==typeof MediaStream?DetectRTC.MediaStream=Object.keys(MediaStream.prototype):DetectRTC.MediaStream=!1,"undefined"!=typeof MediaStreamTrack?DetectRTC.MediaStreamTrack=Object.keys(MediaStreamTrack.prototype):DetectRTC.MediaStreamTrack=!1;var RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection;"undefined"!=typeof RTCPeerConnection?DetectRTC.RTCPeerConnection=Object.keys(RTCPeerConnection.prototype):DetectRTC.RTCPeerConnection=!1,window.DetectRTC=DetectRTC,"undefined"!=typeof module&&(module.exports=DetectRTC),"function"==typeof define&&define.amd&&define("DetectRTC",[],function(){return DetectRTC})}(),"undefined"!=typeof cordova&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Crosswalk")!==-1&&(DetectRTC.isMobileDevice=!0,DetectRTC.browser.name="Chrome"),window.addEventListener||(window.addEventListener=function(el,eventName,eventHandler){
+el.attachEvent&&el.attachEvent("on"+eventName,eventHandler)}),window.attachEventListener=function(video,type,listener,useCapture){video.addEventListener(type,listener,useCapture)};var MediaStream=window.MediaStream;"undefined"==typeof MediaStream&&"undefined"!=typeof webkitMediaStream&&(MediaStream=webkitMediaStream),"undefined"!=typeof MediaStream&&("stop"in MediaStream.prototype||(MediaStream.prototype.stop=function(){this.getTracks().forEach(function(track){track.stop()})})),window.iOSDefaultAudioOutputDevice=window.iOSDefaultAudioOutputDevice||"speaker",document.addEventListener("deviceready",setCordovaAPIs,!1),setCordovaAPIs();var RTCPeerConnection,defaults={};"undefined"!=typeof window.RTCPeerConnection?RTCPeerConnection=window.RTCPeerConnection:"undefined"!=typeof mozRTCPeerConnection?RTCPeerConnection=mozRTCPeerConnection:"undefined"!=typeof webkitRTCPeerConnection&&(RTCPeerConnection=webkitRTCPeerConnection);var RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate,MediaStreamTrack=window.MediaStreamTrack,CodecsHandler=function(){function preferCodec(sdp,codecName){var info=splitLines(sdp);return info.videoCodecNumbers?"vp8"===codecName&&info.vp8LineNumber===info.videoCodecNumbers[0]?sdp:"vp9"===codecName&&info.vp9LineNumber===info.videoCodecNumbers[0]?sdp:"h264"===codecName&&info.h264LineNumber===info.videoCodecNumbers[0]?sdp:sdp=preferCodecHelper(sdp,codecName,info):sdp}function preferCodecHelper(sdp,codec,info,ignore){var preferCodecNumber="";if("vp8"===codec){if(!info.vp8LineNumber)return sdp;preferCodecNumber=info.vp8LineNumber}if("vp9"===codec){if(!info.vp9LineNumber)return sdp;preferCodecNumber=info.vp9LineNumber}if("h264"===codec){if(!info.h264LineNumber)return sdp;preferCodecNumber=info.h264LineNumber}var newLine=info.videoCodecNumbersOriginal.split("SAVPF")[0]+"SAVPF ",newOrder=[preferCodecNumber];return ignore&&(newOrder=[]),info.videoCodecNumbers.forEach(function(codecNumber){codecNumber!==preferCodecNumber&&newOrder.push(codecNumber)}),newLine+=newOrder.join(" "),sdp=sdp.replace(info.videoCodecNumbersOriginal,newLine)}function splitLines(sdp){var info={};return sdp.split("\n").forEach(function(line){0===line.indexOf("m=video")&&(info.videoCodecNumbers=[],line.split("SAVPF")[1].split(" ").forEach(function(codecNumber){codecNumber=codecNumber.trim(),codecNumber&&codecNumber.length&&(info.videoCodecNumbers.push(codecNumber),info.videoCodecNumbersOriginal=line)})),line.indexOf("VP8/90000")===-1||info.vp8LineNumber||(info.vp8LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("VP9/90000")===-1||info.vp9LineNumber||(info.vp9LineNumber=line.replace("a=rtpmap:","").split(" ")[0]),line.indexOf("H264/90000")===-1||info.h264LineNumber||(info.h264LineNumber=line.replace("a=rtpmap:","").split(" ")[0])}),info}function removeVPX(sdp){var info=splitLines(sdp);return sdp=preferCodecHelper(sdp,"vp9",info,!0),sdp=preferCodecHelper(sdp,"vp8",info,!0)}function disableNACK(sdp){if(!sdp||"string"!=typeof sdp)throw"Invalid arguments.";return 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")}function prioritize(codecMimeType,peer){if(peer&&peer.getSenders&&peer.getSenders().length){if(!codecMimeType||"string"!=typeof codecMimeType)throw"Invalid arguments.";peer.getSenders().forEach(function(sender){for(var params=sender.getParameters(),i=0;i=numberOfTimes||setTimeout(function(){callback(),afterEach(setTimeoutInteval,numberOfTimes,callback,startedTimes)},setTimeoutInteval)}return{setHandlers:setHandlers,onSyncNeeded:function(streamid,action,type){}}}(),TextSender={send:function(config){function sendText(textMessage,text){var data={type:"text",uuid:uuid,sendingTime:sendingTime};textMessage&&(text=textMessage,data.packets=parseInt(text.length/packetSize)),text.length>packetSize?data.message=text.slice(0,packetSize):(data.message=text,data.last=!0,data.isobject=isobject),channel.send(data,remoteUserId),textToTransfer=text.slice(data.message.length),textToTransfer.length&&setTimeout(function(){sendText(null,textToTransfer)},connection.chunkInterval||100)}var connection=config.connection,channel=config.channel,remoteUserId=config.remoteUserId,initialText=config.text,packetSize=connection.chunkSize||1e3,textToTransfer="",isobject=!1;isString(initialText)||(isobject=!0,initialText=JSON.stringify(initialText));var uuid=getRandomString(),sendingTime=(new Date).getTime();sendText(initialText)}},FileProgressBarHandler=function(){function handle(connection){function updateLabel(progress,label){if(progress.position!==-1){var position=+progress.position.toFixed(2).split(".")[1]||100;label.innerHTML=position+"%"}}var progressHelper={};connection.onFileStart=function(file){var div=document.createElement("div");return div.title=file.name,div.innerHTML="0% ",file.remoteUserId&&(div.innerHTML+=" (Sharing with:"+file.remoteUserId+")"),connection.filesContainer||(connection.filesContainer=document.body||document.documentElement),connection.filesContainer.insertBefore(div,connection.filesContainer.firstChild),file.remoteUserId?(progressHelper[file.uuid]||(progressHelper[file.uuid]={}),progressHelper[file.uuid][file.remoteUserId]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid][file.remoteUserId].progress.max=file.maxChunks)):(progressHelper[file.uuid]={div:div,progress:div.querySelector("progress"),label:div.querySelector("label")},void(progressHelper[file.uuid].progress.max=file.maxChunks))},connection.onFileProgress=function(chunk){var helper=progressHelper[chunk.uuid];helper&&(chunk.remoteUserId&&!(helper=progressHelper[chunk.uuid][chunk.remoteUserId])||(helper.progress.value=chunk.currentPosition||chunk.maxChunks||helper.progress.max,updateLabel(helper.progress,helper.label)))},connection.onFileEnd=function(file){var helper=progressHelper[file.uuid];if(!helper)return void console.error("No such progress-helper element exist.",file);if(!file.remoteUserId||(helper=progressHelper[file.uuid][file.remoteUserId])){var div=helper.div;file.type.indexOf("image")!=-1?div.innerHTML='Download '+file.name+' ':div.innerHTML='Download '+file.name+' '}}}return{handle:handle}}(),TranslationHandler=function(){function handle(connection){connection.autoTranslateText=!1,connection.language="en",connection.googKey="AIzaSyCgB5hmFY74WYB-EoWkhr9cAGr6TiTHrEE",connection.Translator={TranslateText:function(text,callback){var newScript=document.createElement("script");newScript.type="text/javascript";var sourceText=encodeURIComponent(text),randomNumber="method"+connection.token();window[randomNumber]=function(response){return response.data&&response.data.translations[0]&&callback?void callback(response.data.translations[0].translatedText):response.error&&"Daily Limit Exceeded"===response.error.message?void console.error('Text translation failed. Error message: "Daily Limit Exceeded."'):response.error?void console.error(response.error.message):void console.error(response)};var source="https://www.googleapis.com/language/translate/v2?key="+connection.googKey+"&target="+(connection.language||"en-US")+"&callback=window."+randomNumber+"&q="+sourceText;newScript.src=source,document.getElementsByTagName("head")[0].appendChild(newScript)},getListOfLanguages:function(callback){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState==XMLHttpRequest.DONE){var response=JSON.parse(xhr.responseText);if(response&&response.data&&response.data.languages)return void callback(response.data.languages);if(response.error&&"Daily Limit Exceeded"===response.error.message)return void console.error('Text translation failed. Error message: "Daily Limit Exceeded."');if(response.error)return void console.error(response.error.message);console.error(response)}};var url="https://www.googleapis.com/language/translate/v2/languages?key="+connection.googKey+"&target=en";xhr.open("GET",url,!0),xhr.send(null)}}}return{handle:handle}}();!function(connection){function onUserLeft(remoteUserId){connection.deletePeer(remoteUserId)}function connectSocket(connectCallback){if(connection.socketAutoReConnect=!0,connection.socket)return void(connectCallback&&connectCallback(connection.socket));if("undefined"==typeof SocketConnection)if("undefined"!=typeof FirebaseConnection)window.SocketConnection=FirebaseConnection;else{if("undefined"==typeof PubNubConnection)throw"SocketConnection.js seems missed.";window.SocketConnection=PubNubConnection}new SocketConnection(connection,function(s){connectCallback&&connectCallback(connection.socket)})}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:"undefined"!=typeof connection.password&&"object"!=typeof connection.password?connection.password:""},function(isRoomJoined,error){if(isRoomJoined===!0){if(connection.enableLogs&&console.log("isRoomJoined: ",isRoomJoined," roomid: ",connection.sessionid),connection.peers[connection.sessionid])return;mPeer.onNegotiationNeeded(connectionDescription)}isRoomJoined===!1&&connection.enableLogs&&console.warn("isRoomJoined: ",error," roomid: ",connection.sessionid),cb(isRoomJoined,connection.sessionid,error)})}function openRoom(callback){connection.enableLogs&&console.log("Sending open-room signal to socket.io"),connection.waitingForLocalMedia=!1,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:"undefined"!=typeof connection.password&&"object"!=typeof connection.password?connection.password:""},function(isRoomOpened,error){isRoomOpened===!0&&(connection.enableLogs&&console.log("isRoomOpened: ",isRoomOpened," roomid: ",connection.sessionid),callback(isRoomOpened,connection.sessionid)),isRoomOpened===!1&&(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)return void callback();var localMediaConstraints={};userPreferences.localPeerSdpConstraints.OfferToReceiveAudio&&(localMediaConstraints.audio=connection.mediaConstraints.audio),userPreferences.localPeerSdpConstraints.OfferToReceiveVideo&&(localMediaConstraints.video=connection.mediaConstraints.video);var session=userPreferences.session||connection.session;return session.oneway&&"two-way"!==session.audio&&"two-way"!==session.video&&"two-way"!==session.screen?void callback():(session.oneway&&session.audio&&"two-way"===session.audio&&(session={audio:!0}),void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?callback(screen):connection.invokeGetUserMedia(null,callback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.invokeGetUserMedia({audio:isAudioPlusTab(connection),video:!0,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?callback:connection.invokeGetUserMedia(null,callback)):(session.audio||session.video)&&connection.invokeGetUserMedia(null,callback,session))))}function applyConstraints(stream,mediaConstraints){return stream?(mediaConstraints.audio&&getTracks(stream,"audio").forEach(function(track){track.applyConstraints(mediaConstraints.audio)}),void(mediaConstraints.video&&getTracks(stream,"video").forEach(function(track){track.applyConstraints(mediaConstraints.video)}))):void(connection.enableLogs&&console.error("No stream to applyConstraints."))}function replaceTrack(track,remoteUserId,isVideoTrack){return remoteUserId?void mPeer.replaceTrack(track,remoteUserId,isVideoTrack):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.replaceTrack(track,participant,isVideoTrack)})}forceOptions=forceOptions||{useDefaultDevices:!0},connection.channel=connection.sessionid=(roomid||location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g,"").split("\n").join("").split("\r").join(""))+"";var mPeer=new MultiPeers(connection),preventDuplicateOnStreamEvents={};mPeer.onGettingLocalMedia=function(stream,callback){if(callback=callback||function(){},preventDuplicateOnStreamEvents[stream.streamid])return void callback();preventDuplicateOnStreamEvents[stream.streamid]=!0;try{stream.type="local"}catch(e){}connection.setStreamEndHandler(stream),getRMCMediaElement(stream,function(mediaElement){mediaElement.id=stream.streamid,mediaElement.muted=!0,mediaElement.volume=0,connection.attachStreams.indexOf(stream)===-1&&connection.attachStreams.push(stream),"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!0,connection),connection.streamEvents[stream.streamid]={stream:stream,type:"local",mediaElement:mediaElement,userid:connection.userid,extra:connection.extra,streamid:stream.streamid,isAudioMuted:!0};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,"undefined"!=typeof StreamsHandler&&StreamsHandler.setHandlers(stream,!1,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];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}),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||"";var messageToDeliver={remoteUserId:remoteUserId,message:message,sender:connection.userid};message.remoteUserId&&message.message&&message.sender&&(messageToDeliver=message),connectSocket(function(){connection.socket.emit(connection.socketMessageEvent,messageToDeliver,callback)})},mPeer.onUserLeft=onUserLeft,mPeer.disconnectWith=function(remoteUserId,callback){connection.socket&&connection.socket.emit("disconnect-with",remoteUserId,callback||function(){}),connection.deletePeer(remoteUserId)},connection.socketOptions={transport:"polling"},connection.openOrJoin=function(roomid,callback){callback=callback||function(){},connection.checkPresence(roomid,function(isRoomExist,roomid){if(isRoomExist){connection.sessionid=roomid;var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!!connection.session.oneway,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:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid};return void beforeJoin(connectionDescription.message,function(){joinRoom(connectionDescription,callback)})}return connection.waitingForLocalMedia=!0,connection.isInitiator=!0,connection.sessionid=roomid||connection.sessionid,isData(connection.session)?void openRoom(callback):void connection.captureUserMedia(function(){openRoom(callback)})})},connection.waitingForLocalMedia=!1,connection.open=function(roomid,callback){callback=callback||function(){},connection.waitingForLocalMedia=!0,connection.isInitiator=!0,connection.sessionid=roomid||connection.sessionid,connectSocket(function(){return isData(connection.session)?void openRoom(callback):void connection.captureUserMedia(function(){openRoom(callback)})})},connection.peersBackup={},connection.deletePeer=function(remoteUserId){if(remoteUserId&&connection.peers[remoteUserId]){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),connection.peers[remoteUserId]){connection.peers[remoteUserId].streams.forEach(function(stream){stream.stop()});var peer=connection.peers[remoteUserId].peer;if(peer&&"closed"!==peer.iceConnectionState)try{peer.close()}catch(e){}connection.peers[remoteUserId]&&(connection.peers[remoteUserId].peer=null,delete connection.peers[remoteUserId])}}},connection.rejoin=function(connectionDescription){if(!connection.isInitiator&&connectionDescription&&Object.keys(connectionDescription).length){var extra={};connection.peers[connectionDescription.remoteUserId]&&(extra=connection.peers[connectionDescription.remoteUserId].extra,connection.deletePeer(connectionDescription.remoteUserId)),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)||connection.sessionid,connection.sessionid+="";var localPeerSdpConstraints=!1,remotePeerSdpConstraints=!1,isOneWay=!1,isDataOnly=!1;if(remoteUserId&&remoteUserId.session||!remoteUserId||"string"==typeof remoteUserId){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(){};"function"==typeof options&&(cb=options,options={}),"undefined"!=typeof options.localPeerSdpConstraints&&(localPeerSdpConstraints=options.localPeerSdpConstraints),"undefined"!=typeof options.remotePeerSdpConstraints&&(remotePeerSdpConstraints=options.remotePeerSdpConstraints),"undefined"!=typeof options.isOneWay&&(isOneWay=options.isOneWay),"undefined"!=typeof options.isDataOnly&&(isDataOnly=options.isDataOnly);var connectionDescription={remoteUserId:connection.sessionid,message:{newParticipationRequest:!0,isOneWay:isOneWay,isDataOnly:isDataOnly,localPeerSdpConstraints:localPeerSdpConstraints,remotePeerSdpConstraints:remotePeerSdpConstraints},sender:connection.userid};return beforeJoin(connectionDescription.message,function(){connectSocket(function(){joinRoom(connectionDescription,cb)})}),connectionDescription},connection.publicRoomIdentifier="",connection.getUserMedia=connection.captureUserMedia=function(callback,sessionForced){callback=callback||function(){};var session=sessionForced||connection.session;return connection.dontCaptureUserMedia||isData(session)?void callback():void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){if(screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),(session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(screen)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.invokeGetUserMedia({audio:isAudioPlusTab(connection),video:!0,isScreen:!0},function(stream){if((session.audio||session.video)&&!isAudioPlusTab(connection)){var nonScreenSession={};for(var s in session)"screen"!==s&&(nonScreenSession[s]=session[s]);return void connection.invokeGetUserMedia(sessionForced,callback,nonScreenSession)}callback(stream)}):(session.audio||session.video)&&connection.invokeGetUserMedia(sessionForced,callback,session)))},connection.onbeforeunload=function(arg1,dontCloseSocket){connection.closeBeforeUnload&&(connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({userLeft:!0},participant),connection.peers[participant]&&connection.peers[participant].peer&&connection.peers[participant].peer.close(),delete connection.peers[participant]}),dontCloseSocket||connection.closeSocket(),connection.isInitiator=!1)},window.ignoreBeforeUnload?connection.closeBeforeUnload=!1:(connection.closeBeforeUnload=!0,
+window.addEventListener("beforeunload",connection.onbeforeunload,!1)),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:!0,video:!0},connection.enableFileSharing=!1,connection.bandwidth={screen:!1,audio:!1,video:!1},connection.codecs={audio:"opus",video:"VP9"},connection.processSdp=function(sdp){return isUnifiedPlanSupportedDefault()?sdp:"Safari"===DetectRTC.browser.name?sdp:("VP8"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp8")),"VP9"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"vp9")),"H264"===connection.codecs.video.toUpperCase()&&(sdp=CodecsHandler.preferCodec(sdp,"h264")),"G722"===connection.codecs.audio&&(sdp=CodecsHandler.removeNonG722(sdp)),"Firefox"===DetectRTC.browser.name?sdp:((connection.bandwidth.video||connection.bandwidth.screen)&&(sdp=CodecsHandler.setApplicationSpecificBandwidth(sdp,connection.bandwidth,!!connection.session.screen)),connection.bandwidth.video&&(sdp=CodecsHandler.setVideoBitrates(sdp,{min:8*connection.bandwidth.video*1024,max:8*connection.bandwidth.video*1024})),connection.bandwidth.audio&&(sdp=CodecsHandler.setOpusAttributes(sdp,{maxaveragebitrate:8*connection.bandwidth.audio*1024,maxplaybackrate:8*connection.bandwidth.audio*1024,stereo:1,maxptime:3})),sdp))},"undefined"!=typeof CodecsHandler&&(connection.BandwidthHandler=connection.CodecsHandler=CodecsHandler),connection.mediaConstraints={audio:{mandatory:{},optional:connection.bandwidth.audio?[{bandwidth:8*connection.bandwidth.audio*1024||1048576}]:[]},video:{mandatory:{},optional:connection.bandwidth.video?[{bandwidth:8*connection.bandwidth.video*1024||1048576},{facingMode:"user"}]:[{facingMode:"user"}]}},"Firefox"===DetectRTC.browser.name&&(connection.mediaConstraints={audio:!0,video:!0}),forceOptions.useDefaultDevices||DetectRTC.isMobileDevice||DetectRTC.load(function(){var lastAudioDevice,lastVideoDevice;if(DetectRTC.MediaDevices.forEach(function(device){"audioinput"===device.kind&&connection.mediaConstraints.audio!==!1&&(lastAudioDevice=device),"videoinput"===device.kind&&connection.mediaConstraints.video!==!1&&(lastVideoDevice=device)}),lastAudioDevice){if("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.audio!==!0?connection.mediaConstraints.audio.deviceId=lastAudioDevice.id:connection.mediaConstraints.audio={deviceId:lastAudioDevice.id});1==connection.mediaConstraints.audio&&(connection.mediaConstraints.audio={mandatory:{},optional:[]}),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("Firefox"===DetectRTC.browser.name)return void(connection.mediaConstraints.video!==!0?connection.mediaConstraints.video.deviceId=lastVideoDevice.id:connection.mediaConstraints.video={deviceId:lastVideoDevice.id});1==connection.mediaConstraints.video&&(connection.mediaConstraints.video={mandatory:{},optional:[]}),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:!0,OfferToReceiveVideo:!0},optional:[{VoiceActivityDetection:!1}]},connection.sdpSemantics=null,connection.iceCandidatePoolSize=null,connection.bundlePolicy=null,connection.rtcpMuxPolicy=null,connection.iceTransportPolicy=null,connection.optionalArgument={optional:[{DtlsSrtpKeyAgreement:!0},{googImprovedWifiBwe:!0},{googScreencastMinBitrate:300},{googIPv6:!0},{googDscp:!0},{googCpuUnderuseThreshold:55},{googCpuOveruseThreshold:85},{googSuspendBelowMinBitrate:!0},{googCpuOveruseDetection:!0}],mandatory:{}},connection.iceServers=IceServersHandler.getIceServers(connection),connection.candidates={host:!0,stun:!0,turn:!0},connection.iceProtocols={tcp:!0,udp:!0},connection.onopen=function(event){connection.enableLogs&&console.info("Data connection has been opened between you & ",event.userid)},connection.onclose=function(event){connection.enableLogs&&console.warn("Data connection has been closed between you & ",event.userid)},connection.onerror=function(error){connection.enableLogs&&console.error(error.userid,"data-error",error)},connection.onmessage=function(event){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(!1,!0)},connection.closeEntireSession=function(callback){callback=callback||function(){},connection.socket.emit("close-entire-session",function looper(){return connection.getAllParticipants().length?void setTimeout(looper,100):(connection.onEntireSessionClosed({sessionid:connection.sessionid,userid:connection.userid,extra:connection.extra}),void connection.changeUserId(null,function(){connection.close(),callback()}))})},connection.onEntireSessionClosed=function(event){connection.enableLogs&&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();return"undefined"!=typeof played?void played["catch"](function(){}).then(function(){setTimeout(function(){e.mediaElement.play()},2e3)}):void setTimeout(function(){e.mediaElement.play()},2e3)},connection.onstreamended=function(e){e.mediaElement||(e.mediaElement=document.getElementById(e.streamid)),e.mediaElement&&e.mediaElement.parentNode&&e.mediaElement.parentNode.removeChild(e.mediaElement)},connection.direction="many-to-many",connection.removeStream=function(streamid,remoteUserId){var stream;return connection.attachStreams.forEach(function(localStream){localStream.id===streamid&&(stream=localStream)}),stream?(connection.peers.getAllParticipants().forEach(function(participant){if(!remoteUserId||participant===remoteUserId){var user=connection.peers[participant];try{user.peer.removeStream(stream)}catch(e){}}}),void connection.renegotiate()):void console.warn("No such stream exist.",streamid)},connection.addStream=function(session,remoteUserId){function gumCallback(stream){session.streamCallback&&session.streamCallback(stream),connection.renegotiate(remoteUserId)}return session.getTracks?(connection.attachStreams.indexOf(session)===-1&&(session.streamid||(session.streamid=session.id),connection.attachStreams.push(session)),void connection.renegotiate(remoteUserId)):isData(session)?void connection.renegotiate(remoteUserId):void((session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.invokeGetUserMedia({audio:isAudioPlusTab(connection),video:!0,isScreen:!0},function(stream){!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(stream):connection.invokeGetUserMedia(null,function(stream){gumCallback(stream)})}):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback)))},connection.invokeGetUserMedia=function(localMediaConstraints,callback,session){session||(session=connection.session),localMediaConstraints||(localMediaConstraints=connection.mediaConstraints),getUserMediaHandler({onGettingLocalMedia:function(stream){var videoConstraints=localMediaConstraints.video;videoConstraints&&(videoConstraints.mediaSource||videoConstraints.mozMediaSource?stream.isScreen=!0:videoConstraints.mandatory&&videoConstraints.mandatory.chromeMediaSource&&(stream.isScreen=!0)),stream.isScreen||(stream.isVideo=!!getTracks(stream,"video").length,stream.isAudio=!stream.isVideo&&getTracks(stream,"audio").length),mPeer.onGettingLocalMedia(stream,function(){"function"==typeof callback&&callback(stream)})},onLocalMediaError:function(error,constraints){mPeer.onLocalMediaError(error,constraints)},localMediaConstraints:localMediaConstraints||{audio:!!session.audio&&localMediaConstraints.audio,video:!!session.video&&localMediaConstraints.video}})},connection.applyConstraints=function(mediaConstraints,streamid){if(!MediaStreamTrack||!MediaStreamTrack.prototype.applyConstraints)return void alert("track.applyConstraints is NOT supported in your browser.");if(streamid){var stream;return connection.streamEvents[streamid]&&(stream=connection.streamEvents[streamid].stream),void applyConstraints(stream,mediaConstraints)}connection.attachStreams.forEach(function(stream){applyConstraints(stream,mediaConstraints)})},connection.replaceTrack=function(session,remoteUserId,isVideoTrack){function gumCallback(stream){connection.replaceTrack(stream,remoteUserId,isVideoTrack||session.video||session.screen)}if(session=session||{},!RTCPeerConnection.prototype.getSenders)return void connection.addStream(session);if(session instanceof MediaStreamTrack)return void replaceTrack(session,remoteUserId,isVideoTrack);if(session instanceof MediaStream)return getTracks(session,"video").length&&replaceTrack(getTracks(session,"video")[0],remoteUserId,!0),void(getTracks(session,"audio").length&&replaceTrack(getTracks(session,"audio")[0],remoteUserId,!1));if(isData(session))throw"connection.replaceTrack requires audio and/or video and/or screen.";(session.audio||session.video||session.screen)&&(session.screen?"Edge"===DetectRTC.browser.name?navigator.getDisplayMedia({video:!0,audio:isAudioPlusTab(connection)}).then(function(screen){screen.isScreen=!0,mPeer.onGettingLocalMedia(screen),!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback(screen):connection.invokeGetUserMedia(null,gumCallback)},function(error){console.error("Unable to capture screen on Edge. HTTPs and version 17+ is required.")}):connection.invokeGetUserMedia({audio:isAudioPlusTab(connection),video:!0,isScreen:!0},!session.audio&&!session.video||isAudioPlusTab(connection)?gumCallback:connection.invokeGetUserMedia(null,gumCallback)):(session.audio||session.video)&&connection.invokeGetUserMedia(null,gumCallback))},connection.resetTrack=function(remoteUsersIds,isVideoTrack){remoteUsersIds||(remoteUsersIds=connection.getAllParticipants()),"string"==typeof remoteUsersIds&&(remoteUsersIds=[remoteUsersIds]),remoteUsersIds.forEach(function(participant){var peer=connection.peers[participant].peer;"undefined"!=typeof isVideoTrack&&isVideoTrack!==!0||!peer.lastVideoTrack||connection.replaceTrack(peer.lastVideoTrack,participant,!0),"undefined"!=typeof isVideoTrack&&isVideoTrack!==!1||!peer.lastAudioTrack||connection.replaceTrack(peer.lastAudioTrack,participant,!1)})},connection.renegotiate=function(remoteUserId){return remoteUserId?void mPeer.renegotiatePeer(remoteUserId):void connection.peers.getAllParticipants().forEach(function(participant){mPeer.renegotiatePeer(participant)})},connection.setStreamEndHandler=function(stream,isRemote){if(stream&&stream.addEventListener&&(isRemote=!!isRemote,!stream.alreadySetEndHandler)){stream.alreadySetEndHandler=!0;var streamEndedEvent="ended";"oninactive"in stream&&(streamEndedEvent="inactive"),stream.addEventListener(streamEndedEvent,function(){if(stream.idInstance&¤tUserMediaRequest.remove(stream.idInstance),!isRemote){var streams=[];connection.attachStreams.forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.attachStreams=streams}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}),isRemote&&connection.peers[streamEvent.userid]){var peer=connection.peers[streamEvent.userid].peer,streams=[];peer.getRemoteStreams().forEach(function(s){s.id!=stream.id&&streams.push(s)}),connection.peers[streamEvent.userid].streams=streams}streamEvent.userid===connection.userid&&"remote"===streamEvent.type||(connection.peersBackup[streamEvent.userid]&&(streamEvent.extra=connection.peersBackup[streamEvent.userid].extra),connection.onstreamended(streamEvent),delete connection.streamEvents[stream.streamid])},!1)}},connection.onMediaError=function(error,constraints){connection.enableLogs&&console.error(error,constraints)},connection.autoCloseEntireSession=!1,connection.filesContainer=connection.videosContainer=document.body||document.documentElement,connection.isInitiator=!1,connection.shareFile=mPeer.shareFile,"undefined"!=typeof FileProgressBarHandler&&FileProgressBarHandler.handle(connection),"undefined"!=typeof TranslationHandler&&TranslationHandler.handle(connection),connection.token=getRandomString,connection.onNewParticipant=function(participantId,userPreferences){connection.acceptParticipationRequest(participantId,userPreferences)},connection.acceptParticipationRequest=function(participantId,userPreferences){userPreferences.successCallback&&(userPreferences.successCallback(),delete userPreferences.successCallback),mPeer.createNewPeer(participantId,userPreferences)},"undefined"!=typeof StreamsHandler&&(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)if("both"===e.muteType||"video"===e.muteType){e.mediaElement.src=null;var paused=e.mediaElement.pause();"undefined"!=typeof paused?paused.then(function(){e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}):e.mediaElement.poster=e.snapshot||"https://cdn.webrtc-experiment.com/images/muted.png"}else"audio"===e.muteType&&(e.mediaElement.muted=!0)},connection.onunmute=function(e){e&&e.mediaElement&&e.stream&&("both"===e.unmuteType||"video"===e.unmuteType?(e.mediaElement.poster=null,e.mediaElement.srcObject=e.stream,e.mediaElement.play()):"audio"===e.unmuteType&&(e.mediaElement.muted=!1))},connection.onExtraDataUpdated=function(event){event.status="online",connection.onUserStatusChanged(event,!0)},connection.getAllParticipants=function(sender){return connection.peers.getAllParticipants(sender)},"undefined"!=typeof StreamsHandler&&(StreamsHandler.onSyncNeeded=function(streamid,action,type){connection.peers.getAllParticipants().forEach(function(participant){mPeer.onNegotiationNeeded({streamid:streamid,action:action,streamSyncNeeded:!0,type:type||"both"},participant)})}),connection.connectSocket=function(callback){connectSocket(callback)},connection.closeSocket=function(){try{io.sockets={}}catch(e){}connection.socket&&("function"==typeof connection.socket.disconnect&&connection.socket.disconnect(),"function"==typeof connection.socket.resetProps&&connection.socket.resetProps(),connection.socket=null)},connection.getSocket=function(callback){return!callback&&connection.enableLogs&&console.warn("getSocket.callback paramter is required."),callback=callback||function(){},connection.socket?callback(connection.socket):connectSocket(function(){callback(connection.socket)}),connection.socket},connection.getRemoteStreams=mPeer.getRemoteStreams;var skipStreams=["selectFirst","selectAll","forEach"];if(connection.streamEvents={selectFirst:function(options){return connection.streamEvents.selectAll(options)[0]},selectAll:function(options){options||(options={local:!0,remote:!0,isScreen:!0,isAudio:!0,isVideo:!0}),"local"==options&&(options={local:!0}),"remote"==options&&(options={remote:!0}),"screen"==options&&(options={isScreen:!0}),"audio"==options&&(options={isAudio:!0}),"video"==options&&(options={isVideo:!0});var streams=[];return Object.keys(connection.streamEvents).forEach(function(key){var event=connection.streamEvents[key];if(skipStreams.indexOf(key)===-1){var ignore=!0;options.local&&"local"===event.type&&(ignore=!1),options.remote&&"remote"===event.type&&(ignore=!1),options.isScreen&&event.stream.isScreen&&(ignore=!1),options.isVideo&&event.stream.isVideo&&(ignore=!1),options.isAudio&&event.stream.isAudio&&(ignore=!1),options.userid&&event.userid===options.userid&&(ignore=!1),ignore===!1&&streams.push(event)}}),streams}},connection.socketURL="/",connection.socketMessageEvent="RTCMultiConnection-Message",connection.socketCustomEvent="RTCMultiConnection-Custom-Message",connection.DetectRTC=DetectRTC,connection.setCustomSocketEvent=function(customEvent){customEvent&&(connection.socketCustomEvent=customEvent),connection.socket&&connection.socket.emit("set-custom-socket-event-listener",connection.socketCustomEvent)},connection.getNumberOfBroadcastViewers=function(broadcastId,callback){connection.socket&&broadcastId&&callback&&connection.socket.emit("get-number-of-users-in-specific-broadcast",broadcastId,callback)},connection.onNumberOfBroadcastViewersUpdated=function(event){connection.enableLogs&&connection.isInitiator&&console.info("Number of broadcast (",event.broadcastId,") viewers",event.numberOfBroadcastViewers)},connection.onUserStatusChanged=function(event,dontWriteLogs){connection.enableLogs&&!dontWriteLogs&&console.info(event.userid,event.status)},connection.getUserMediaHandler=getUserMediaHandler,connection.multiPeersHandler=mPeer,connection.enableLogs=!0,connection.setCustomSocketHandler=function(customSocketHandler){"undefined"!=typeof SocketConnection&&(SocketConnection=customSocketHandler)},connection.chunkSize=4e4,connection.maxParticipantsAllowed=1e3,connection.disconnectWith=mPeer.disconnectWith,connection.checkPresence=function(roomid,callback){return roomid=roomid||connection.sessionid,"SSEConnection"===SocketConnection.name?void SSEConnection.checkPresence(roomid,function(isRoomExist,_roomid,extra){return connection.socket?void callback(isRoomExist,_roomid):(isRoomExist||(connection.userid=_roomid),void connection.connectSocket(function(){callback(isRoomExist,_roomid,extra)}))}):connection.socket?void connection.socket.emit("check-presence",roomid+"",function(isRoomExist,_roomid,extra){connection.enableLogs&&console.log("checkPresence.isRoomExist: ",isRoomExist," roomid: ",_roomid),callback(isRoomExist,_roomid,extra)}):void connection.connectSocket(function(){connection.checkPresence(roomid,callback)})},connection.onReadyForOffer=function(remoteUserId,userPreferences){connection.multiPeersHandler.createNewPeer(remoteUserId,userPreferences)},connection.setUserPreferences=function(userPreferences){return connection.dontAttachStream&&(userPreferences.dontAttachLocalStream=!0),connection.dontGetRemoteStream&&(userPreferences.dontGetRemoteStream=!0),userPreferences},connection.updateExtraData=function(){connection.socket.emit("extra-data-updated",connection.extra)},connection.enableScalableBroadcast=!1,connection.maxRelayLimitPerUser=3,connection.dontCaptureUserMedia=!1,connection.dontAttachStream=!1,connection.dontGetRemoteStream=!1,connection.onReConnecting=function(event){connection.enableLogs&&console.info("ReConnecting with",event.userid,"...")},connection.beforeAddingStream=function(stream){return stream},connection.beforeRemovingStream=function(stream){return stream},"undefined"!=typeof isChromeExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isChromeExtensionAvailable),"undefined"!=typeof isFirefoxExtensionAvailable&&(connection.checkIfChromeExtensionAvailable=isFirefoxExtensionAvailable),"undefined"!=typeof getChromeExtensionStatus&&(connection.getChromeExtensionStatus=getChromeExtensionStatus),connection.modifyScreenConstraints=function(screen_constraints){return screen_constraints},connection.onPeerStateChanged=function(state){connection.enableLogs&&state.iceConnectionState.search(/closed|failed/gi)!==-1&&console.error("Peer connection is closed between you & ",state.userid,state.extra,"state:",state.iceConnectionState)},connection.isOnline=!0,listenEventHandler("online",function(){connection.isOnline=!0}),listenEventHandler("offline",function(){connection.isOnline=!1}),connection.isLowBandwidth=!1,navigator&&navigator.connection&&navigator.connection.type&&(connection.isLowBandwidth=navigator.connection.type.toString().toLowerCase().search(/wifi|cell/g)!==-1,connection.isLowBandwidth)){if(connection.bandwidth={audio:!1,video:!1,screen:!1},connection.mediaConstraints.audio&&connection.mediaConstraints.audio.optional&&connection.mediaConstraints.audio.optional.length){var newArray=[];connection.mediaConstraints.audio.optional.forEach(function(opt){"undefined"==typeof opt.bandwidth&&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){"undefined"==typeof opt.bandwidth&&newArray.push(opt)}),connection.mediaConstraints.video.optional=newArray}}connection.getExtraData=function(remoteUserId,callback){if(!remoteUserId)throw"remoteUserId is required.";return"function"==typeof callback?void connection.socket.emit("get-remote-user-extra-data",remoteUserId,function(extra,remoteUserId,error){callback(extra,remoteUserId,error)}):connection.peers[remoteUserId]?connection.peers[remoteUserId].extra:connection.peersBackup[remoteUserId]?connection.peersBackup[remoteUserId].extra:{}},forceOptions.autoOpenOrJoin&&connection.openOrJoin(connection.sessionid),connection.onUserIdAlreadyTaken=function(useridAlreadyTaken,yourNewUserId){connection.close(),connection.closeSocket(),connection.isInitiator=!1,connection.userid=connection.token(),connection.join(connection.sessionid),connection.enableLogs&&console.warn("Userid already taken.",useridAlreadyTaken,"Your new userid:",connection.userid)},connection.trickleIce=!0,connection.version="3.6.9",connection.onSettingLocalDescription=function(event){connection.enableLogs&&console.info("Set local description for remote user",event.userid)},connection.resetScreen=function(){sourceId=null,DetectRTC&&DetectRTC.screen&&delete DetectRTC.screen.sourceId,currentUserMediaRequest={streams:[],mutex:!1,queueRequests:[]}},connection.autoCreateMediaElement=!0,connection.password=null,connection.setPassword=function(password,callback){callback=callback||function(){},connection.socket?connection.socket.emit("set-password",password,callback):(connection.password=password,callback(!0,connection.sessionid,null))},connection.onSocketDisconnect=function(event){connection.enableLogs&&console.warn("socket.io connection is closed")},connection.onSocketError=function(event){connection.enableLogs&&console.warn("socket.io connection is failed")},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)};"undefined"!=typeof module&&(module.exports=exports=RTCMultiConnection),"function"==typeof define&&define.amd&&define("RTCMultiConnection",[],function(){return RTCMultiConnection});
\ No newline at end of file
diff --git a/home.html b/home.html
new file mode 100644
index 0000000..5c3dbd1
--- /dev/null
+++ b/home.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EarthLoop About
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EarthLoop is a collaborative platform for sound and music experimentation
+ using real-time communication
+
+
+
+
+ Go to "Play"... and open or join a new session
+
+
+
+ © Laurent Di Biase - 2020
+
+
+
\ No newline at end of file
diff --git a/imgs/favicon.ico b/imgs/favicon.ico
new file mode 100644
index 0000000..304dab4
Binary files /dev/null and b/imgs/favicon.ico differ
diff --git a/imgs/logo.png b/imgs/logo.png
new file mode 100644
index 0000000..b210e8f
Binary files /dev/null and b/imgs/logo.png differ
diff --git a/imgs/menu-icon.png b/imgs/menu-icon.png
new file mode 100644
index 0000000..a2eac03
Binary files /dev/null and b/imgs/menu-icon.png differ
diff --git a/imgs/visual.png b/imgs/visual.png
new file mode 100644
index 0000000..cdff1db
Binary files /dev/null and b/imgs/visual.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..bb7b758
--- /dev/null
+++ b/index.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+ EarthLoop
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EarthLoop
+
+
+ Laurent Di Biase - 2020
+
+
+
+
+
\ No newline at end of file
diff --git a/js/bootstrap.min.js b/js/bootstrap.min.js
new file mode 100644
index 0000000..00c895f
--- /dev/null
+++ b/js/bootstrap.min.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v4.1.3 (https://getbootstrap.com/)
+ * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,h){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)P(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!(Ie={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"}),selector:!(Se={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"}),placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},we="out",Ne={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:(De="show")+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Oe="fade",ke="show",Pe=".tooltip-inner",je=".arrow",He="hover",Le="focus",Re="click",xe="manual",We=function(){function i(t,e){if("undefined"==typeof h)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=pe(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(pe(this.getTipElement()).hasClass(ke))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),pe.removeData(this.element,this.constructor.DATA_KEY),pe(this.element).off(this.constructor.EVENT_KEY),pe(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&pe(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===pe(this.element).css("display"))throw new Error("Please use show on visible elements");var t=pe.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){pe(this.element).trigger(t);var n=pe.contains(this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!n)return;var i=this.getTipElement(),r=Fn.getUID(this.constructor.NAME);i.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&pe(i).addClass(Oe);var o="function"==typeof this.config.placement?this.config.placement.call(this,i,this.element):this.config.placement,s=this._getAttachment(o);this.addAttachmentClass(s);var a=!1===this.config.container?document.body:pe(document).find(this.config.container);pe(i).data(this.constructor.DATA_KEY,this),pe.contains(this.element.ownerDocument.documentElement,this.tip)||pe(i).appendTo(a),pe(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new h(this.element,i,{placement:s,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:je},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),pe(i).addClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().on("mouseover",null,pe.noop);var l=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,pe(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(pe(this.tip).hasClass(Oe)){var c=Fn.getTransitionDurationFromElement(this.tip);pe(this.tip).one(Fn.TRANSITION_END,l).emulateTransitionEnd(c)}else l()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=pe.Event(this.constructor.Event.HIDE),r=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),pe(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(pe(this.element).trigger(i),!i.isDefaultPrevented()){if(pe(n).removeClass(ke),"ontouchstart"in document.documentElement&&pe(document.body).children().off("mouseover",null,pe.noop),this._activeTrigger[Re]=!1,this._activeTrigger[Le]=!1,this._activeTrigger[He]=!1,pe(this.tip).hasClass(Oe)){var o=Fn.getTransitionDurationFromElement(n);pe(n).one(Fn.TRANSITION_END,r).emulateTransitionEnd(o)}else r();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){pe(this.getTipElement()).addClass(Te+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||pe(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(pe(t.querySelectorAll(Pe)),this.getTitle()),pe(t).removeClass(Oe+" "+ke)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?pe(e).parent().is(t)||t.empty().append(e):t.text(pe(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getAttachment=function(t){return Ie[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)pe(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==xe){var e=t===He?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===He?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;pe(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}pe(i.element).closest(".modal").on("hide.bs.modal",function(){return i.hide()})}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Le:He]=!0),pe(e.getTipElement()).hasClass(ke)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||pe(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),pe(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Le:He]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,pe(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),Fn.typeCheckConfig(ve,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=pe(this.getTipElement()),e=t.attr("class").match(be);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(pe(t).removeClass(Oe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=pe(this).data(ye),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),pe(this).data(ye,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.1.3"}},{key:"Default",get:function(){return Ae}},{key:"NAME",get:function(){return ve}},{key:"DATA_KEY",get:function(){return ye}},{key:"Event",get:function(){return Ne}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}(),pe.fn[ve]=We._jQueryInterface,pe.fn[ve].Constructor=We,pe.fn[ve].noConflict=function(){return pe.fn[ve]=Ce,We._jQueryInterface},We),Jn=(qe="popover",Ke="."+(Fe="bs.popover"),Me=(Ue=e).fn[qe],Qe="bs-popover",Be=new RegExp("(^|\\s)"+Qe+"\\S+","g"),Ve=l({},zn.Default,{placement:"right",trigger:"click",content:"",template:'