diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000..5ca1a0a --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1,48 @@ + + +EARTHLOOP PROJECT + +From Laurent Di Biase + +2020 + + +Collaboratif web platform for networked Music + + + +This project use WebRTC API from the RTCMulticonnection Library of Muaz Khan +https://github.com/muaz-khan. +The audio plugins come form Faust code and are embeded in a Web Audio API + + +//To launch the program file from terminal : + +node server.js + + +////////////////////////// + RTCMultiConnection +////////////////////////// + +from last Github repository + + +https://github.com/muaz-khan/RTCMultiConnection/blob/master/docs/how-to-use.md + + +/* + +The idea is to receive into the "listen" page, the opening connection data form the "Play" page at the moment of the opening session by a player. + + +*/ + + + + + + + + + diff --git a/about.html b/about.html new file mode 100644 index 0000000..16a6145 --- /dev/null +++ b/about.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + EarthLoop About + + + + + + + + + + + + + + + + + + + + + +
+

+ EarthLoop +

+

Connected People for Networked Music

+
+ + + +
+

+

Links:

+

+
+

+ +

+
+ +
+ +
+ + \ 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 = ' '; + + 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;i0?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=" ",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 +

+

Connected People for Networked Music

+
+ + + +
+ +
+

+

+ 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 + +

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:''}),Ye=l({},zn.DefaultType,{content:"(string|element|function)"}),ze="fade",Ze=".popover-header",Ge=".popover-body",$e={HIDE:"hide"+Ke,HIDDEN:"hidden"+Ke,SHOW:(Je="show")+Ke,SHOWN:"shown"+Ke,INSERTED:"inserted"+Ke,CLICK:"click"+Ke,FOCUSIN:"focusin"+Ke,FOCUSOUT:"focusout"+Ke,MOUSEENTER:"mouseenter"+Ke,MOUSELEAVE:"mouseleave"+Ke},Xe=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var r=i.prototype;return r.isWithContent=function(){return this.getTitle()||this._getContent()},r.addAttachmentClass=function(t){Ue(this.getTipElement()).addClass(Qe+"-"+t)},r.getTipElement=function(){return this.tip=this.tip||Ue(this.config.template)[0],this.tip},r.setContent=function(){var t=Ue(this.getTipElement());this.setElementContent(t.find(Ze),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(Ge),e),t.removeClass(ze+" "+Je)},r._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},r._cleanTipClass=function(){var t=Ue(this.getTipElement()),e=t.attr("class").match(Be);null!==e&&0=this._offsets[r]&&("undefined"==typeof this._offsets[r+1]||t=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,u,s,l,c,f,d,p,h,g,v,y,m,b,x="sizzle"+1*new Date,w=e.document,C=0,T=0,E=ae(),N=ae(),k=ae(),A=function(e,t){return e===t&&(f=!0),0},D={}.hasOwnProperty,S=[],L=S.pop,j=S.push,q=S.push,O=S.slice,P=function(e,t){for(var n=0,r=e.length;n+~]|"+I+")"+I+"*"),_=new RegExp("="+I+"*([^\\]'\"]*?)"+I+"*\\]","g"),U=new RegExp(M),V=new RegExp("^"+R+"$"),X={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+B),PSEUDO:new RegExp("^"+M),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+I+"*(even|odd|(([+-]|)(\\d*)n|)"+I+"*(?:([+-]|)"+I+"*(\\d+)|))"+I+"*\\)|)","i"),bool:new RegExp("^(?:"+H+")$","i"),needsContext:new RegExp("^"+I+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+I+"*((?:-\\d)?\\d*)"+I+"*\\)|)(?=[^-]|$)","i")},Q=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,J=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+I+"?|("+I+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){d()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{q.apply(S=O.call(w.childNodes),w.childNodes),S[w.childNodes.length].nodeType}catch(e){q={apply:S.length?function(e,t){j.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,u,l,c,f,h,y,m=t&&t.ownerDocument,C=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==C&&9!==C&&11!==C)return r;if(!i&&((t?t.ownerDocument||t:w)!==p&&d(t),t=t||p,g)){if(11!==C&&(f=K.exec(e)))if(o=f[1]){if(9===C){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&b(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return q.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return q.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!k[e+" "]&&(!v||!v.test(e))){if(1!==C)m=t,y=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=x),u=(h=a(e)).length;while(u--)h[u]="#"+c+" "+ye(h[u]);y=h.join(","),m=J.test(e)&&ge(t.parentNode)||t}if(y)try{return q.apply(r,m.querySelectorAll(y)),r}catch(e){}finally{c===x&&t.removeAttribute("id")}}}return s(e.replace($,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function ue(e){return e[x]=!0,e}function se(e){var t=p.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function de(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pe(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return ue(function(t){return t=+t,ue(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},d=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==p&&9===a.nodeType&&a.documentElement?(p=a,h=p.documentElement,g=!o(p),w!==p&&(i=p.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=se(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=se(function(e){return e.appendChild(p.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=G.test(p.getElementsByClassName),n.getById=se(function(e){return h.appendChild(e).id=x,!p.getElementsByName||!p.getElementsByName(x).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},y=[],v=[],(n.qsa=G.test(p.querySelectorAll))&&(se(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+I+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+I+"*(?:value|"+H+")"),e.querySelectorAll("[id~="+x+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+x+"+*").length||v.push(".#.+[+~]")}),se(function(e){e.innerHTML="";var t=p.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+I+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(n.matchesSelector=G.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&se(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),y.push("!=",M)}),v=v.length&&new RegExp(v.join("|")),y=y.length&&new RegExp(y.join("|")),t=G.test(h.compareDocumentPosition),b=t||G.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===p||e.ownerDocument===w&&b(w,e)?-1:t===p||t.ownerDocument===w&&b(w,t)?1:c?P(c,e)-P(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],u=[t];if(!i||!o)return e===p?-1:t===p?1:i?-1:o?1:c?P(c,e)-P(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)u.unshift(n);while(a[r]===u[r])r++;return r?ce(a[r],u[r]):a[r]===w?-1:u[r]===w?1:0},p):p},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&d(e),t=t.replace(_,"='$1']"),n.matchesSelector&&g&&!k[t+" "]&&(!y||!y.test(t))&&(!v||!v.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,p,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==p&&d(e),b(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==p&&d(e);var i=r.attrHandle[t.toLowerCase()],o=i&&D.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(A),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:ue,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return X.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&U.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+I+")"+e+"("+I+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace(W," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),u="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,s){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",v=t.parentNode,y=u&&t.nodeName.toLowerCase(),m=!s&&!u,b=!1;if(v){if(o){while(g){d=t;while(d=d[g])if(u?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?v.firstChild:v.lastChild],a&&m){b=(p=(l=(c=(f=(d=v)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1])&&l[2],d=p&&v.childNodes[p];while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if(1===d.nodeType&&++b&&d===t){c[e]=[C,p,b];break}}else if(m&&(b=p=(l=(c=(f=(d=t)[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]||[])[0]===C&&l[1]),!1===b)while(d=++p&&d&&d[g]||(b=p=0)||h.pop())if((u?d.nodeName.toLowerCase()===y:1===d.nodeType)&&++b&&(m&&((c=(f=d[x]||(d[x]={}))[d.uniqueID]||(f[d.uniqueID]={}))[e]=[C,b]),d===t))break;return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[x]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?ue(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=P(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:ue(function(e){var t=[],n=[],r=u(e.replace($,"$1"));return r[x]?ue(function(e,t,n,i){var o,a=r(e,null,i,[]),u=e.length;while(u--)(o=a[u])&&(e[u]=!(t[u]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:ue(function(e){return function(t){return oe(e,t).length>0}}),contains:ue(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:ue(function(e){return V.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:pe(!1),disabled:pe(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xe(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else y=we(y===a?y.splice(h,y.length):y),i?i(null,a,y,s):q.apply(a,y)})}function Te(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],u=a||r.relative[" "],s=a?1:0,c=me(function(e){return e===t},u,!0),f=me(function(e){return P(t,e)>-1},u,!0),d=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];s1&&be(d),s>1&&ye(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),n,s0,i=e.length>0,o=function(o,a,u,s,c){var f,h,v,y=0,m="0",b=o&&[],x=[],w=l,T=o||i&&r.find.TAG("*",c),E=C+=null==w?1:Math.random()||.1,N=T.length;for(c&&(l=a===p||a||c);m!==N&&null!=(f=T[m]);m++){if(i&&f){h=0,a||f.ownerDocument===p||(d(f),u=!g);while(v=e[h++])if(v(f,a||p,u)){s.push(f);break}c&&(C=E)}n&&((f=!v&&f)&&y--,o&&b.push(f))}if(y+=m,n&&m!==y){h=0;while(v=t[h++])v(b,x,a,u);if(o){if(y>0)while(m--)b[m]||x[m]||(x[m]=L.call(s));x=we(x)}q.apply(s,x),c&&!o&&x.length>0&&y+t.length>1&&oe.uniqueSort(s)}return c&&(C=E,l=w),b};return n?ue(o):o}return u=oe.compile=function(e,t){var n,r=[],i=[],o=k[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Te(t[n]))[x]?r.push(o):i.push(o);(o=k(e,Ee(i,r))).selector=e}return o},s=oe.select=function(e,t,n,i){var o,s,l,c,f,d="function"==typeof e&&e,p=!i&&a(e=d.selector||e);if(n=n||[],1===p.length){if((s=p[0]=p[0].slice(0)).length>2&&"ID"===(l=s[0]).type&&9===t.nodeType&&g&&r.relative[s[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;d&&(t=t.parentNode),e=e.slice(s.shift().value.length)}o=X.needsContext.test(e)?0:s.length;while(o--){if(l=s[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),J.test(s[0].type)&&ge(t.parentNode)||t))){if(s.splice(o,1),!(e=i.length&&ye(s)))return q.apply(n,i),n;break}}}return(d||u(e,p))(i,t,!g,n,!t||J.test(e)&&ge(t.parentNode)||t),n},n.sortStable=x.split("").sort(A).join("")===x,n.detectDuplicates=!!f,d(),n.sortDetached=se(function(e){return 1&e.compareDocumentPosition(p.createElement("fieldset"))}),se(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&se(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),se(function(e){return null==e.getAttribute("disabled")})||le(H,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var N=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},k=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},A=w.expr.match.needsContext;function D(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var S=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function L(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return s.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(L(this,e||[],!1))},not:function(e){return this.pushStack(L(this,e||[],!0))},is:function(e){return!!L(this,"string"==typeof e&&A.test(e)?w(e):e||[],!1).length}});var j,q=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:q.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),S.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,j=w(r);var O=/^(?:parents|prev(?:Until|All))/,P={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?s.call(w(e),this[0]):s.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function H(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return N(e,"parentNode")},parentsUntil:function(e,t,n){return N(e,"parentNode",n)},next:function(e){return H(e,"nextSibling")},prev:function(e){return H(e,"previousSibling")},nextAll:function(e){return N(e,"nextSibling")},prevAll:function(e){return N(e,"previousSibling")},nextUntil:function(e,t,n){return N(e,"nextSibling",n)},prevUntil:function(e,t,n){return N(e,"previousSibling",n)},siblings:function(e){return k((e.parentNode||{}).firstChild,e)},children:function(e){return k(e.firstChild)},contents:function(e){return D(e,"iframe")?e.contentDocument:(D(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(P[e]||w.uniqueSort(i),O.test(e)&&i.reverse()),this.pushStack(i)}});var I=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(I)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],u=-1,s=function(){for(i=i||e.once,r=t=!0;a.length;u=-1){n=a.shift();while(++u-1)o.splice(n,1),n<=u&&u--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||s()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function B(e){return e}function M(e){throw e}function W(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var u=this,s=arguments,l=function(){var e,l;if(!(t=o&&(r!==M&&(u=void 0,s=[e]),n.rejectWith(u,s))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:B,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:B)),n[2][3].add(a(0,e,g(r)?r:M))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],u=t[5];i[t[1]]=a.add,u&&a.add(function(){r=u},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),u=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&(W(e,a.done(u(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)W(i[n],u(n),a.reject);return a.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&$.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function z(){r.removeEventListener("DOMContentLoaded",z),e.removeEventListener("load",z),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",z),e.addEventListener("load",z));var _=function(e,t,n,r,i,o,a){var u=0,s=e.length,l=null==n;if("object"===b(n)){i=!0;for(u in n)_(e,t,u,n[u],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;u1,null,!0)},removeData:function(e){return this.each(function(){J.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=K.get(e,t),n&&(!r||Array.isArray(n)?r=K.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return K.get(e,n)||K.access(e,n,{empty:w.Callbacks("once memory").add(function(){K.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&D(e,t)?w.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ve(f.appendChild(o),"script"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var xe=r.documentElement,we=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Te=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function Ne(){return!1}function ke(){try{return r.activeElement}catch(e){}}function Ae(e,t,n,r,i,o){var a,u;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(u in t)Ae(e,u,n,r,t[u],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Ne;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.get(e);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(xe,i),n.guid||(n.guid=w.guid++),(s=v.events)||(s=v.events={}),(a=v.handle)||(a=v.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(I)||[""]).length;while(l--)p=g=(u=Te.exec(t[l])||[])[1],h=(u[2]||"").split(".").sort(),p&&(f=w.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=w.event.special[p]||{},c=w.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=s[p])||((d=s[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(p,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),w.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,u,s,l,c,f,d,p,h,g,v=K.hasData(e)&&K.get(e);if(v&&(s=v.events)){l=(t=(t||"").match(I)||[""]).length;while(l--)if(u=Te.exec(t[l])||[],p=g=u[1],h=(u[2]||"").split(".").sort(),p){f=w.event.special[p]||{},d=s[p=(r?f.delegateType:f.bindType)||p]||[],u=u[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||u&&!u.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||w.removeEvent(e,p,v.handle),delete s[p])}else for(p in s)w.event.remove(e,p+t[l],n,r,!0);w.isEmptyObject(s)&&K.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,u,s=new Array(arguments.length),l=(K.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(s[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&u.push({elem:l,handlers:o})}return l=this,s\x20\t\r\n\f]*)[^>]*)\/>/gi,Se=/\s*$/g;function qe(e,t){return D(e,"table")&&D(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function Oe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Pe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function He(e,t){var n,r,i,o,a,u,s,l;if(1===t.nodeType){if(K.hasData(e)&&(o=K.access(e),a=K.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof v&&!h.checkClone&&Le.test(v))return e.each(function(i){var o=e.eq(i);y&&(t[0]=v.call(this,i,o.html())),Re(o,t,n,r)});if(d&&(i=be(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(u=w.map(ve(i,"script"),Oe)).length;f")},clone:function(e,t,n){var r,i,o,a,u=e.cloneNode(!0),s=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ve(u),r=0,i=(o=ve(e)).length;r0&&ye(a,!s&&ve(e,"script")),u},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[K.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[K.expando]=void 0}n[J.expando]&&(n[J.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return _(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||qe(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=qe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ve(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Se.test(e)&&!ge[(pe.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(s+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-s-u-.5))),s}function et(e,t,n){var r=We(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(Me.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,u=Q(t),s=Ue.test(t),l=e.style;if(s||(t=Ke(u)),a=w.cssHooks[t]||w.cssHooks[u],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=se(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[u]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(s?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,u=Q(t);return Ue.test(t)||(t=Ke(u)),(a=w.cssHooks[t]||w.cssHooks[u])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Xe&&(i=Xe[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!_e.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):ue(e,Ve,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=We(e),a="border-box"===w.css(e,"boxSizing",!1,o),u=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(u-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),u&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Je(e,n,u)}}}),w.cssHooks.marginLeft=ze(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Je)}),w.fn.extend({css:function(e,t){return _(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=We(e),i=t.length;a1)}}),w.fn.delay=function(t,n){return t=w.fx?w.fx.speeds[t]||t:t,n=n||"fx",this.queue(n,function(n,r){var i=e.setTimeout(n,t);r.stop=function(){e.clearTimeout(i)}})},function(){var e=r.createElement("input"),t=r.createElement("select").appendChild(r.createElement("option"));e.type="checkbox",h.checkOn=""!==e.value,h.optSelected=t.selected,(e=r.createElement("input")).value="t",e.type="radio",h.radioValue="t"===e.value}();var tt,nt=w.expr.attrHandle;w.fn.extend({attr:function(e,t){return _(this,w.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?tt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&D(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(I);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),tt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=nt[t]||w.find.attr;nt[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=nt[a],nt[a]=i,i=null!=n(e,t,r)?a:null,nt[a]=o),i}});var rt=/^(?:input|select|textarea|button)$/i,it=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return _(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):rt.test(e.nodeName)||it.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function ot(e){return(e.match(I)||[]).join(" ")}function at(e){return e.getAttribute&&e.getAttribute("class")||""}function ut(e){return Array.isArray(e)?e:"string"==typeof e?e.match(I)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,at(this)))});if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},removeClass:function(e){var t,n,r,i,o,a,u,s=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,at(this)))});if(!arguments.length)return this.attr("class","");if((t=ut(e)).length)while(n=this[s++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(u=ot(r))&&n.setAttribute("class",u)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,at(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=ut(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=at(this))&&K.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":K.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+ot(at(n))+" ").indexOf(t)>-1)return!0;return!1}});var st=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(st,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:ot(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,u=a?null:[],s=a?o+1:i.length;for(r=o<0?s:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var lt=/^(?:focusinfocus|focusoutblur)$/,ct=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,u,s,l,c,d,p,h,y=[i||r],m=f.call(t,"type")?t.type:t,b=f.call(t,"namespace")?t.namespace.split("."):[];if(u=h=s=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!lt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(b=m.split(".")).shift(),b.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=b.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),p=w.event.special[m]||{},o||!p.trigger||!1!==p.trigger.apply(i,n))){if(!o&&!p.noBubble&&!v(i)){for(l=p.delegateType||m,lt.test(l+m)||(u=u.parentNode);u;u=u.parentNode)y.push(u),s=u;s===(i.ownerDocument||r)&&y.push(s.defaultView||s.parentWindow||e)}a=0;while((u=y[a++])&&!t.isPropagationStopped())h=u,t.type=a>1?l:p.bindType||m,(d=(K.get(u,"events")||{})[t.type]&&K.get(u,"handle"))&&d.apply(u,n),(d=c&&u[c])&&d.apply&&Y(u)&&(t.result=d.apply(u,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(y.pop(),n)||!Y(i)||c&&g(i[m])&&!v(i)&&((s=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,ct),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,ct),w.event.triggered=void 0,s&&(i[c]=s)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=K.access(r,t);i||r.addEventListener(e,n,!0),K.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=K.access(r,t)-1;i?K.access(r,t,i):(r.removeEventListener(e,n,!0),K.remove(r,t))}}});var ft=/\[\]$/,dt=/\r?\n/g,pt=/^(?:submit|button|image|reset|file)$/i,ht=/^(?:input|select|textarea|keygen)/i;function gt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||ft.test(e)?r(e,i):gt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==b(t))r(e,t);else for(i in t)gt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)gt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&ht.test(this.nodeName)&&!pt.test(e)&&(this.checked||!de.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(dt,"\r\n")}}):{name:t.name,value:n.replace(dt,"\r\n")}}).get()}}),w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},h.createHTMLDocument=function(){var e=r.implementation.createHTMLDocument("").body;return e.innerHTML="
",2===e.childNodes.length}(),w.parseHTML=function(e,t,n){if("string"!=typeof e)return[];"boolean"==typeof t&&(n=t,t=!1);var i,o,a;return t||(h.createHTMLDocument?((i=(t=r.implementation.createHTMLDocument("")).createElement("base")).href=r.location.href,t.head.appendChild(i)):t=r),o=S.exec(e),a=!n&&[],o?[t.createElement(o[1])]:(o=be([e],t,a),a&&a.length&&w(a).remove(),w.merge([],o.childNodes))},w.offset={setOffset:function(e,t,n){var r,i,o,a,u,s,l,c=w.css(e,"position"),f=w(e),d={};"static"===c&&(e.style.position="relative"),u=f.offset(),o=w.css(e,"top"),s=w.css(e,"left"),(l=("absolute"===c||"fixed"===c)&&(o+s).indexOf("auto")>-1)?(a=(r=f.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(s)||0),g(t)&&(t=t.call(e,n,w.extend({},u))),null!=t.top&&(d.top=t.top-u.top+a),null!=t.left&&(d.left=t.left-u.left+i),"using"in t?t.using.call(e,d):f.css(d)}},w.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){w.offset.setOffset(this,e,t)});var t,n,r=this[0];if(r)return r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===w.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===w.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=w(e).offset()).top+=w.css(e,"borderTopWidth",!0),i.left+=w.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-w.css(r,"marginTop",!0),left:t.left-i.left-w.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===w.css(e,"position"))e=e.offsetParent;return e||xe})}}),w.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n="pageYOffset"===t;w.fn[e]=function(r){return _(this,function(e,r,i){var o;if(v(e)?o=e:9===e.nodeType&&(o=e.defaultView),void 0===i)return o?o[t]:e[r];o?o.scrollTo(n?o.pageXOffset:i,n?i:o.pageYOffset):e[r]=i},e,r,arguments.length)}}),w.each(["top","left"],function(e,t){w.cssHooks[t]=ze(h.pixelPosition,function(e,n){if(n)return n=Fe(e,t),Me.test(n)?w(e).position()[t]+"px":n})}),w.each({Height:"height",Width:"width"},function(e,t){w.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){w.fn[r]=function(i,o){var a=arguments.length&&(n||"boolean"!=typeof i),u=n||(!0===i||!0===o?"margin":"border");return _(this,function(t,n,i){var o;return v(t)?0===r.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(o=t.documentElement,Math.max(t.body["scroll"+e],o["scroll"+e],t.body["offset"+e],o["offset"+e],o["client"+e])):void 0===i?w.css(t,n,u):w.style(t,n,i,u)},t,a?i:void 0,a)}})}),w.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){w.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}}),w.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=D,w.isFunction=g,w.isWindow=v,w.camelCase=Q,w.type=b,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},"function"==typeof define&&define.amd&&define("jquery",[],function(){return w});var vt=e.jQuery,yt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=yt),t&&e.jQuery===w&&(e.jQuery=vt),w},t||(e.jQuery=e.$=w),w}); diff --git a/js/jquery.min.js b/js/jquery.min.js new file mode 100644 index 0000000..3883779 --- /dev/null +++ b/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/listen.html b/listen.html new file mode 100644 index 0000000..f5f5671 --- /dev/null +++ b/listen.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + EarthLoop audio connection + + + + + + + + + + + + + + + + + + + + +
+

+ EarthLoop +

+

Connected People for Networked Music

+
+ + +

+

+ Multi-user Auditorium Platform +

+

+ +

+ Listen to the current session here ! + +

+
+ + +
+ + + +
+ + + +
+ + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f5484f0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1492 @@ +{ + "name": "earthloop", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==" + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==" + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "browserify": { + "version": "16.5.1", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.1.tgz", + "integrity": "sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A==", + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.0.0", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "canvas-designer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/canvas-designer/-/canvas-designer-1.3.0.tgz", + "integrity": "sha512-LCOmIGgJAiPcJyo20Iyi/4xcfP+OS11TY6Vi0xli40MlbwNLwDZ/tF2ZPGlJQRmi5rkfqViLZXnHKKlQd94IfA==" + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "deps-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", + "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", + "requires": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "detectrtc": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/detectrtc/-/detectrtc-1.4.0.tgz", + "integrity": "sha512-VQWrlpttdKCog05BhdRmlRzWa4QaJDHEoyvn1EoQvqUWz1DfneKXwmBO1sizVHRUyhu54mu6LSPG6bDQk1VrsA==" + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "engine.io": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.0.tgz", + "integrity": "sha512-XCyYVWzcHnK5cMz7G4VTu2W7zJS7SM1QkcelghyIk/FmobWBtXE7fwhBusEKvCSqc3bMh8fNFMlUkCKTFRxH2w==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "0.3.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" + } + }, + "engine.io-client": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.0.tgz", + "integrity": "sha512-a4J5QO2k99CM2a0b12IznnyQndoEvtA4UAldhGzKqnHf42I3Qs2W5SPnDvatZRcMaNZs4IevVicBPayxYt6FwA==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "fbr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/fbr/-/fbr-2.0.8.tgz", + "integrity": "sha512-/0wWNI4C/iaQ9ntmP2qSUTspMA6UqZ5FFSwyY8/BfVaq2e5k7AVY42czPApyfjviYMdyrotjfTtBpl+r5D92Cw==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" + }, + "getstats": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/getstats/-/getstats-1.2.0.tgz", + "integrity": "sha512-fdvGnPIaevnfrmL1qB78SeaqW4Pnm2f8j8nhRiWUZS7N1p377fee0ie76PO4uGkIjiuig9hMv4MVhVVBSLGBjA==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=" + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "requires": { + "source-map": "~0.5.3" + } + }, + "insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "requires": { + "JSONStream": "^1.0.3", + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, + "labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp-classic": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz", + "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==" + }, + "module-deps": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.2.tgz", + "integrity": "sha512-a9y6yDv5u5I4A+IPHTnqFxcaKr4p50/zxTjcQJaX2ws9tN/W6J6YXnEKhqRyPhl494dkcxx951onSKVezmI+3w==", + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multistreamsmixer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/multistreamsmixer/-/multistreamsmixer-1.2.2.tgz", + "integrity": "sha512-5UGMODV/vDFLs8CaYEgFgo2sg+kI6hzXy4e02C8nvqfp7hfCikzAC65p+nNPVRqvBhtMoA2HwAz6qN8iMvoaoA==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=" + }, + "pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "recordrtc": { + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/recordrtc/-/recordrtc-5.5.9.tgz", + "integrity": "sha512-6T1tn80nRfsXrAoYWINwz+hBKk+pB5Im4I0BS52Og1ruQvrEdV3IUcm+AKwLMHNyBAhJ2DaVa+9pukTRxMF/uA==" + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rtcmulticonnection": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/rtcmulticonnection/-/rtcmulticonnection-3.6.9.tgz", + "integrity": "sha512-7TEjH4AVJXkoQZnK/3XLrR9M6Hi7xhiWGLELvFtpMC9d8nOsHb1p5lol89Hgs978NueoC2f6sF55qNAMtdLbzw==", + "requires": { + "canvas-designer": "^1.3.0", + "detectrtc": "^1.4.0", + "fbr": "^2.0.8", + "getstats": "^1.2.0", + "multistreamsmixer": "^1.2.2", + "recordrtc": "^5.5.9", + "rtcmulticonnection-server": "^1.3.1", + "socket.io": "^2.3.0", + "webrtc-adapter": "^7.5.1" + } + }, + "rtcmulticonnection-server": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rtcmulticonnection-server/-/rtcmulticonnection-server-1.3.1.tgz", + "integrity": "sha512-NsAKCYJalNBjphPHNe5h/hAvt9nUeY3U9ZHqa/zHFsKFqCX0YS2a5Uwhgphnz4WVEeKDUuOz3YOKJZxYJgdRxA==", + "requires": { + "socket.io": "^2.3.0" + } + }, + "rtcpeerconnection-shim": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", + "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", + "requires": { + "sdp": "^2.6.0" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "sdp": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", + "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shasum-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.0.tgz", + "integrity": "sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==", + "requires": { + "fast-safe-stringify": "^2.0.7" + } + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "socket.io": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.0.tgz", + "integrity": "sha512-/G/VOI+3DBp0+DJKW4KesGnQkQPFmUCbA/oO2QGT6CWxU7hLGWqU3tyuzeSK/dqcyeHsQg1vTe9jiZI8GU9SCQ==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.0.tgz", + "integrity": "sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "requires": { + "minimist": "^1.1.0" + } + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "requires": { + "acorn-node": "^1.2.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "requires": { + "process": "~0.11.0" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==" + }, + "undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "requires": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + }, + "webrtc-adapter": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.1.tgz", + "integrity": "sha512-R5LkIR/APjODkstSXFOztOmINXQ0nqIGfUoKTtCzjyiDXHNgwhkqZ9vi8UzGyjfUBibuZ0ZzVyV10qtuLGW3CQ==", + "requires": { + "rtcpeerconnection-shim": "^1.2.15", + "sdp": "^2.12.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", + "integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==" + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4c9fbe6 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "earthloop", + "version": "1.0.0", + "description": "EarthLoop Web Platform for Networked Music", + "main": "index.html", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "Laurent Di Biase", + "license": "ISC", + "dependencies": { + "browserify": "^16.5.1", + "rtcmulticonnection": "^3.6.9", + "socket.io": "^2.3.0", + "socket.io-client": "^2.3.0" + } +} diff --git a/play.html b/play.html new file mode 100644 index 0000000..f967a69 --- /dev/null +++ b/play.html @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + EarthLoop audio connection + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ EarthLoop +

+

Connected People for Networked Music

+
+ + + + +
+
+ +
+ +
+ + +
+ + +
+

+ + + + +
+
+
+
+ +

You:

+
+ +

Partners:

+
+ + +
+
+ + + + + + + + + diff --git a/server-original.js b/server-original.js new file mode 100644 index 0000000..5edc9f7 --- /dev/null +++ b/server-original.js @@ -0,0 +1,85 @@ +// Muaz Khan - www.MuazKhan.com +// MIT License - www.WebRTC-Experiment.com/licence +// Documentation - github.com/muaz-khan/getScreenId + +var port = process.env.PORT || 8080; + +var server = require('http'), + url = require('url'), + path = require('path'), + fs = require('fs'); + +function serverHandler(request, response) { + try { + var uri = url.parse(request.url).pathname, + filename = path.join(process.cwd(), uri); + + if (filename && filename.search(/server.js/g) !== -1) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + var stats; + + try { + stats = fs.lstatSync(filename); + } catch (e) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) { + response.writeHead(404, { + 'Content-Type': 'text/html' + }); + + filename += '/index.html'; + } + + + fs.readFile(filename, 'utf8', function(err, file) { + if (err) { + response.writeHead(500, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, 'utf8'); + response.end(); + }); + } catch (e) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('

Unexpected error:



' + e.stack || e.message || JSON.stringify(e)); + response.end(); + } +} + +var app = server.createServer(serverHandler); + +function runServer() { + app = app.listen(port, process.env.IP || '0.0.0.0', function() { + var addr = app.address(); + + if (addr.address === '0.0.0.0') { + addr.address = 'localhost'; + } + + console.log('Server listening at http://' + addr.address + ':' + addr.port); + }); +} + +runServer(); diff --git a/server.js b/server.js new file mode 100644 index 0000000..66f32a4 --- /dev/null +++ b/server.js @@ -0,0 +1,207 @@ +// http://127.0.0.1:8080 +// http://localhost:8080 + +const fs = require('fs'); +const path = require('path'); +const url = require('url'); + +var events = require('events'); +var httpServer = require('http'); +var ioServer = require('socket.io'); +var socket = require('socket.io-client'); + +const RTCMultiConnectionServer = require('rtcmulticonnection-server'); + + +var PORT = 8080; +var isUseHTTPs = false; + +const jsonPath = { + config: 'config.json', + logs: 'logs.json' +}; + +const BASH_COLORS_HELPER = RTCMultiConnectionServer.BASH_COLORS_HELPER; +const getValuesFromConfigJson = RTCMultiConnectionServer.getValuesFromConfigJson; +const getBashParameters = RTCMultiConnectionServer.getBashParameters; + +var config = getValuesFromConfigJson(jsonPath); +config = getBashParameters(config, BASH_COLORS_HELPER); + + + +// if user didn't modifed "PORT" object +// then read value from "config.json" +if(PORT === 8080) { + PORT = config.port; +} +if(isUseHTTPs === false) { + isUseHTTPs = config.isUseHTTPs; +} + +/* +var fs = require('fs'); +require('http').createServer(function(req, res) { + if (req.url === '/page.html') { + res.end(fs.readFileSync('./page.html').toString()); + } + else if (req.url === '/unautrepage.html') { + res.end(fs.readFileSync('./unautrepage.html').toString()); + } + else { + res.end('not found'); + } +}).listen(3000); +*/ + +function serverHandler(request, response) { + try { + var uri = url.parse(request.url).pathname, + filename = path.join(process.cwd(), uri); + + var routes = ['/play.html', '/listen.html', '/assets/play.js', '/assets/listen.js']; + + if (routes.indexOf(page) !== -1) { + res.writeHead(200); + fs.readFile(page, 'utf-8', function(error, content) { + res.end(content); + }); + + if (filename && filename.search(/server.js/g) !== -1) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + var stats; + + try { + stats = fs.lstatSync(filename); + } catch (e) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) { + response.writeHead(404, { + 'Content-Type': 'text/html' + }); + + filename += '/index.html'; + } + + + fs.readFile(filename, 'utf8', function(err, file) { + if (err) { + response.writeHead(500, { + 'Content-Type': 'text/plain' + }); + response.write('404 Not Found: ' + path.join('/', uri) + '\n'); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, 'utf8'); + response.end(); + }); + } catch (e) { + response.writeHead(404, { + 'Content-Type': 'text/plain' + }); + response.write('

Unexpected error:



' + e.stack || e.message || JSON.stringify(e)); + response.end(); + } +} + +var httpApp; + +if (isUseHTTPs) { + httpServer = require('https'); + + // See how to use a valid certificate: + // https://github.com/muaz-khan/WebRTC-Experiment/issues/62 + var options = { + key: null, + cert: null, + ca: null + }; + + var pfx = false; + + if (!fs.existsSync(config.sslKey)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslKey:\t ' + config.sslKey + ' does not exist.'); + } else { + pfx = config.sslKey.indexOf('.pfx') !== -1; + options.key = fs.readFileSync(config.sslKey); + } + + if (!fs.existsSync(config.sslCert)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCert:\t ' + config.sslCert + ' does not exist.'); + } else { + options.cert = fs.readFileSync(config.sslCert); + } + + if (config.sslCabundle) { + if (!fs.existsSync(config.sslCabundle)) { + console.log(BASH_COLORS_HELPER.getRedFG(), 'sslCabundle:\t ' + config.sslCabundle + ' does not exist.'); + } + + options.ca = fs.readFileSync(config.sslCabundle); + } + + if (pfx === true) { + options = { + pfx: sslKey + }; + } + + httpApp = httpServer.createServer(options, serverHandler); +} else { + httpApp = httpServer.createServer(serverHandler); +} + +RTCMultiConnectionServer.beforeHttpListen(httpApp, config); +httpApp = httpApp.listen(process.env.PORT || PORT, process.env.IP || "0.0.0.0", function() { + RTCMultiConnectionServer.afterHttpListen(httpApp, config); +}); + +/* + +// -------------------------- +// socket.io codes goes below + +ioServer(httpApp).on('connection', function(socket) { + RTCMultiConnectionServer.addSocket(socket, { config: config }); + + + + + socket.on('clickedSend', function(roomId) { + console.log(roomId); + //send a message to ALL connected clients + socket.broadcast.emit('clickedReceived', roomId); + }); + + + // ---------------------- + // below code is optional + + const params = socket.handshake.query; + + if (!params.socketCustomEvent) { + params.socketCustomEvent = 'custom-message'; + } + + socket.on(params.socketCustomEvent, function(message) { + socket.broadcast.emit(params.socketCustomEvent, message); + }); +}); +*/