diff --git a/DetectRTC.js b/DetectRTC.js
new file mode 100644
index 0000000..10938db
--- /dev/null
+++ b/DetectRTC.js
@@ -0,0 +1,953 @@
+
+(function() {
+
+ 'use strict';
+
+ 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: 'Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45'
+ };
+ }
+
+ var isMobileDevice = !!(/Android|webOS|iPhone|iPad|iPod|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 = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
+ var isChrome = !!window.chrome && !isOpera;
+ var isIE = !!document.documentMode && !isEdge;
+
+ // this one can also be used:
+ // https://www.websocket.org/js/stuff.js (DetectBrowser.js)
+
+ function getBrowserInfo() {
+ var nVer = navigator.appVersion;
+ var nAgt = navigator.userAgent;
+ var browserName = navigator.appName;
+ var fullVersion = '' + parseFloat(navigator.appVersion);
+ var majorVersion = parseInt(navigator.appVersion, 10);
+ var nameOffset, verOffset, ix;
+
+ // In Opera, the true version is after 'Opera' or after 'Version'
+ if (isOpera) {
+ browserName = 'Opera';
+ try {
+ fullVersion = navigator.userAgent.split('OPR/')[1].split(' ')[0];
+ majorVersion = fullVersion.split('.')[0];
+ } catch (e) {
+ fullVersion = '0.0.0.0';
+ majorVersion = 0;
+ }
+ }
+ // In MSIE, the true version is after 'MSIE' in userAgent
+ else if (isIE) {
+ verOffset = nAgt.indexOf('MSIE');
+ browserName = 'IE';
+ fullVersion = nAgt.substring(verOffset + 5);
+ }
+ // 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);
+ }
+ }
+ // 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 if present
+ if ((ix = fullVersion.indexOf(';')) !== -1) {
+ fullVersion = fullVersion.substring(0, ix);
+ }
+
+ if ((ix = fullVersion.indexOf(' ')) !== -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;
+
+ if (window.webkitRequestFileSystem) {
+ window.webkitRequestFileSystem(
+ window.TEMPORARY, 1,
+ function() {
+ isPrivate = false;
+ },
+ function(e) {
+ console.log(e);
+ isPrivate = true;
+ }
+ );
+ } else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) {
+ var db;
+ try {
+ db = window.indexedDB.open('test');
+ } 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');
+ }
+ }
+
+ 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/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 id in clientStrings) {
+ var cs = clientStrings[id];
+ 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';
+
+ if (isMobile.any()) {
+ osName = isMobile.getOsName();
+ } else {
+ var osInfo = detectDesktopOS();
+ osName = osInfo.osName;
+ osVersion = osInfo.osVersion;
+ }
+
+ var isCanvasSupportsStreamCapturing = false;
+ var isVideoSupportsStreamCapturing = false;
+ ['captureStream', 'mozCaptureStream', 'webkitCaptureStream'].forEach(function(item) {
+ if (!isCanvasSupportsStreamCapturing && item in document.createElement('canvas')) {
+ isCanvasSupportsStreamCapturing = true;
+ }
+
+ if (!isVideoSupportsStreamCapturing && item in document.createElement('video')) {
+ isVideoSupportsStreamCapturing = true;
+ }
+ });
+
+ // via: https://github.com/diafygi/webrtc-ips
+ function DetectLocalIPAddress(callback) {
+ if (!DetectRTC.isWebRTCSupported) {
+ return;
+ }
+
+ if (DetectRTC.isORTCSupported) {
+ return;
+ }
+
+ getIPs(function(ip) {
+ //local IPs
+ if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
+ callback('Local: ' + ip);
+ }
+
+ //assume the rest are public IPs
+ else {
+ callback('Public: ' + ip);
+ }
+ });
+ }
+
+ //get the IP addresses associated with an account
+ function getIPs(callback) {
+ var ipDuplicates = {};
+
+ //compatibility for firefox and chrome
+ var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
+ var useWebKit = !!window.webkitRTCPeerConnection;
+
+ // bypass naive webrtc blocking using an iframe
+ if (!RTCPeerConnection) {
+ var iframe = document.getElementById('iframe');
+ if (!iframe) {
+ //
+ throw 'NOTE: you need to have an iframe in the page right above the script tag.';
+ }
+ var win = iframe.contentWindow;
+ RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
+ useWebKit = !!win.webkitRTCPeerConnection;
+ }
+
+ // if still no RTCPeerConnection then it is not supported by the browser so just return
+ if (!RTCPeerConnection) {
+ return;
+ }
+
+ //minimal requirements for data connection
+ var mediaConstraints = {
+ optional: [{
+ RtpDataChannels: true
+ }]
+ };
+
+ //firefox already has a default stun server in about:config
+ // media.peerconnection.default_iceservers =
+ // [{"url": "stun:stun.services.mozilla.com"}]
+ var servers;
+
+ //add same stun server for chrome
+ if (useWebKit) {
+ servers = {
+ iceServers: [{
+ urls: 'stun:stun.services.mozilla.com'
+ }]
+ };
+
+ if (typeof DetectRTC !== 'undefined' && DetectRTC.browser.isFirefox && DetectRTC.browser.version <= 38) {
+ servers[0] = {
+ url: servers[0].urls
+ };
+ }
+ }
+
+ //construct a new RTCPeerConnection
+ var pc = new RTCPeerConnection(servers, mediaConstraints);
+
+ function handleCandidate(candidate) {
+ //match just the IP address
+ var ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/;
+ var match = ipRegex.exec(candidate);
+ if (!match) {
+ console.warn('Could not match IP address in', candidate);
+ return;
+ }
+ var ipAddress = match[1];
+
+ //remove duplicates
+ if (ipDuplicates[ipAddress] === undefined) {
+ callback(ipAddress);
+ }
+
+ ipDuplicates[ipAddress] = true;
+ }
+
+ //listen for candidate events
+ pc.onicecandidate = function(ice) {
+ //skip non-candidate events
+ if (ice.candidate) {
+ handleCandidate(ice.candidate.candidate);
+ }
+ };
+
+ //create a bogus data channel
+ pc.createDataChannel('');
+
+ //create an offer sdp
+ pc.createOffer(function(result) {
+
+ //trigger the stun server request
+ pc.setLocalDescription(result, function() {}, function() {});
+
+ }, function() {});
+
+ //wait for a while to let everything done
+ setTimeout(function() {
+ //read candidate info from local description
+ var lines = pc.localDescription.sdp.split('\n');
+
+ lines.forEach(function(line) {
+ if (line.indexOf('a=candidate:') === 0) {
+ handleCandidate(line);
+ }
+ });
+ }, 1000);
+ }
+
+ 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) {
+ navigator.mediaDevices.enumerateDevices().then(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
+ // todo: switch to enumerateDevices when landed in canary.
+ function checkDeviceSupport(callback) {
+ if (!canEnumerate) {
+ return;
+ }
+
+ // This method is useful only for Chrome!
+
+ 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 = [];
+
+ navigator.enumerateDevices(function(devices) {
+ devices.forEach(function(_device) {
+ var device = {};
+ for (var d in _device) {
+ device[d] = _device[d];
+ }
+
+ // if it is MediaStreamTrack.getSources
+ if (device.kind === 'audio') {
+ device.kind = 'audioinput';
+ }
+
+ if (device.kind === 'video') {
+ device.kind = 'videoinput';
+ }
+
+ var skip;
+ MediaDevices.forEach(function(d) {
+ if (d.id === device.id && d.kind === device.kind) {
+ skip = true;
+ }
+ });
+
+ if (skip) {
+ return;
+ }
+
+ if (!device.deviceId) {
+ device.deviceId = device.id;
+ }
+
+ if (!device.id) {
+ device.id = device.deviceId;
+ }
+
+ if (!device.label) {
+ device.label = 'Please invoke getUserMedia once.';
+ if (location.protocol !== 'https:') {
+ if (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 {
+ 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.
+
+ if (MediaDevices.indexOf(device) === -1) {
+ MediaDevices.push(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();
+ }
+ });
+ }
+
+ // check for microphone/camera support!
+ checkDeviceSupport();
+
+ 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;
+
+ var isNodeWebkit = !!(window.process && (typeof window.process === 'object') && window.process.versions && window.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;
+ }
+
+ if (location.protocol !== 'https:') {
+ 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 ('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 && location.protocol !== 'https:') {
+ DetectRTC.isGetUserMediaSupported = 'Requires HTTPs';
+ }
+ DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported;
+
+ // -----------
+ DetectRTC.osName = osName;
+ DetectRTC.osVersion = osVersion;
+
+ var displayResolution = '';
+ if (screen.width) {
+ var width = (screen.width) ? screen.width : '';
+ var height = (screen.height) ? screen.height : '';
+ displayResolution += '' + width + ' x ' + height;
+ }
+ DetectRTC.displayResolution = displayResolution;
+
+ // ----------
+ DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing;
+ DetectRTC.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing;
+
+ // ------
+ DetectRTC.DetectLocalIPAddress = DetectLocalIPAddress;
+
+ DetectRTC.isWebSocketsSupported = 'WebSocket' in window && 2 === window.WebSocket.CLOSING;
+ DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported;
+
+ DetectRTC.checkWebSocketsSupport = function(callback) {
+ callback = callback || function() {};
+ try {
+ var websocket = new WebSocket('wss://echo.websocket.org:443/');
+ websocket.onopen = function() {
+ DetectRTC.isWebSocketsBlocked = false;
+ 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);
+ };
+
+ 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;
+
+ // ------
+ var isSetSinkIdSupported = false;
+ if ('setSinkId' in document.createElement('video')) {
+ isSetSinkIdSupported = true;
+ }
+ DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported;
+
+ // -----
+ var isRTPSenderReplaceTracksSupported = false;
+ if (DetectRTC.browser.isFirefox /*&& 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);
+
+ if (typeof DetectRTC === 'undefined') {
+ window.DetectRTC = {};
+ }
+
+ var MediaStream = window.MediaStream;
+
+ if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
+ MediaStream = webkitMediaStream;
+ }
+
+ if (typeof MediaStream !== 'undefined') {
+ 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;
+
+})();
diff --git a/RTCPeerConnection.js b/RTCPeerConnection.js
new file mode 100644
index 0000000..980f625
--- /dev/null
+++ b/RTCPeerConnection.js
@@ -0,0 +1,310 @@
+
+window.moz = !!navigator.mozGetUserMedia;
+var chromeVersion = !!navigator.mozGetUserMedia ? 0 : parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]);
+
+(function() {
+ window.RTCPeerConnection = function(options) {
+ var w = window,
+ PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection,
+ SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription,
+ IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate;
+
+ var iceServers = {
+ iceServers: RTCPeerConnection.iceServers
+ };
+
+ console.debug('ice-servers', JSON.stringify(iceServers.iceServers, null, '\t'));
+
+ var optional = {
+ optional: []
+ };
+
+ console.debug('optional-arguments', JSON.stringify(optional.optional, null, '\t'));
+
+ var peer = new PeerConnection(iceServers, optional);
+
+ peer.onicecandidate = function(event) {
+ if (event.candidate) {
+ options.onICE(event.candidate);
+ }
+ };
+
+ // attachStream = MediaStream;
+ if (options.attachStream) peer.addStream(options.attachStream);
+
+ // attachStreams[0] = audio-stream;
+ // attachStreams[1] = video-stream;
+ // attachStreams[2] = screen-capturing-stream;
+ if (options.attachStreams && options.attachStream.length) {
+ var streams = options.attachStreams;
+ for (var i = 0; i < streams.length; i++) {
+ peer.addStream(streams[i]);
+ }
+ }
+
+ peer.onaddstream = function(event) {
+ setTimeout(function() {
+ var remoteMediaStream = event.stream;
+
+ // onRemoteStreamEnded(MediaStream)
+ remoteMediaStream.onended = function() {
+ if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
+ };
+
+ // onRemoteStream(MediaStream)
+ if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
+
+ console.debug('on:add:stream', remoteMediaStream);
+ }, 2000);
+ };
+
+ var OfferToReceiveAudio = false;
+ var OfferToReceiveVideo = false;
+ if(peer.getLocalStreams()[0] && peer.getLocalStreams()[0].getAudioTracks().length) {
+ OfferToReceiveAudio = true;
+ }
+
+ if(peer.getLocalStreams()[0] && peer.getLocalStreams()[0].getVideoTracks().length) {
+ OfferToReceiveVideo = true;
+ }
+
+ var firefoxVersion = 50;
+ matchArray = navigator.userAgent.match(/Firefox\/(.*)/);
+ if (moz && matchArray && matchArray[1]) {
+ firefoxVersion = parseInt(matchArray[1], 10);
+ }
+
+ var sdpConstraints = options.constraints || {
+ optional: [],
+ mandatory: {
+ OfferToReceiveAudio: OfferToReceiveAudio,
+ OfferToReceiveVideo: OfferToReceiveVideo
+ }
+ };
+
+ if(moz && firefoxVersion > 34) {
+ sdpConstraints = {
+ OfferToReceiveAudio: OfferToReceiveAudio,
+ OfferToReceiveVideo: OfferToReceiveVideo
+ };
+ }
+
+ console.debug('sdp-constraints', JSON.stringify(sdpConstraints, null, '\t'));
+
+ // onOfferSDP(RTCSessionDescription)
+
+ function createOffer() {
+ if (!options.onOfferSDP) return;
+
+ peer.createOffer(function(sessionDescription) {
+ sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
+ peer.setLocalDescription(sessionDescription);
+ options.onOfferSDP(sessionDescription);
+
+ console.debug('offer-sdp', sessionDescription.sdp);
+ }, onSdpError, sdpConstraints);
+ }
+
+ // onAnswerSDP(RTCSessionDescription)
+
+ function createAnswer() {
+ if (!options.onAnswerSDP) return;
+
+ //options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
+ console.debug('offer-sdp', options.offerSDP.sdp);
+ peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError);
+ peer.createAnswer(function(sessionDescription) {
+ sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
+ peer.setLocalDescription(sessionDescription);
+ options.onAnswerSDP(sessionDescription);
+ console.debug('answer-sdp', sessionDescription.sdp);
+ }, onSdpError, sdpConstraints);
+ }
+
+ // options.bandwidth = { audio: 50, video: 256, data: 30 * 1000 * 1000 }
+ var bandwidth = options.bandwidth;
+
+ function setBandwidth(sdp) {
+ if (moz || !bandwidth /* || navigator.userAgent.match( /Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i ) */ ) return sdp;
+
+ // remove existing bandwidth lines
+ 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.video) {
+ sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.video + '\r\n');
+ }
+
+ if (bandwidth.data) {
+ sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:' + bandwidth.data + '\r\n');
+ }
+
+ return sdp;
+ }
+
+ // DataChannel management
+ var channel;
+
+ if (!!options.onChannelMessage) {
+ peer.ondatachannel = function(event) {
+ channel = event.channel;
+ setChannelEvents();
+ };
+ openOffererChannel();
+ }
+
+ createOffer();
+ createAnswer();
+
+ function openOffererChannel() {
+ channel = peer.createDataChannel(options.channel || 'RTCDataChannel', {});
+ setChannelEvents();
+ }
+
+ function setChannelEvents() {
+ channel.onmessage = function(event) {
+ if (options.onChannelMessage) options.onChannelMessage(event);
+ };
+
+ channel.onopen = function() {
+ if (options.onChannelOpened && !options.onChannelOpenInvoked) {
+ options.onChannelOpenInvoked = true;
+ options.onChannelOpened(channel);
+ }
+ };
+ channel.onclose = function(event) {
+ if (options.onChannelClosed) options.onChannelClosed(event);
+
+ console.warn('WebRTC DataChannel closed', event);
+ };
+ channel.onerror = function(event) {
+ if (options.onChannelError) options.onChannelError(event);
+
+ console.error('WebRTC DataChannel error', event);
+ };
+ }
+
+ function onSdpSuccess() {}
+
+ function onSdpError(e) {
+ console.error('onSdpError:', JSON.stringify(e, null, '\t'));
+ }
+
+ return {
+ addAnswerSDP: function(sdp) {
+ console.debug('adding answer-sdp', sdp.sdp);
+ peer.setRemoteDescription(new SessionDescription(sdp), onSdpSuccess, onSdpError);
+ },
+ addICE: function(candidate) {
+ peer.addIceCandidate(new IceCandidate({
+ sdpMLineIndex: candidate.sdpMLineIndex,
+ candidate: candidate.candidate
+ }));
+
+ console.debug('adding-ice', candidate.candidate);
+ },
+
+ peer: peer,
+ channel: channel,
+ sendData: function(message) {
+ channel && channel.send(message);
+ }
+ };
+ }
+
+ // getUserMedia
+ var video_constraints = {
+ mandatory: {},
+ optional: []
+ };
+
+ window.getUserMedia = function(options) {
+ var n = navigator,
+ media;
+ n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
+ n.getMedia(options.constraints || {
+ audio: true,
+ video: video_constraints
+ }, streaming, options.onerror || function(e) {
+ console.error(e);
+ });
+
+ function streaming(stream) {
+ var video = options.video;
+ if (video) {
+ video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream);
+ video.play();
+ }
+ options.onsuccess && options.onsuccess(stream);
+ media = stream;
+ }
+
+ return media;
+ }
+})();
+
+function listenEventHandler(eventName, eventHandler) {
+ window.removeEventListener(eventName, eventHandler);
+ window.addEventListener(eventName, eventHandler, false);
+}
+
+var loadedIceFrame;
+
+function loadIceFrame(callback) {
+ if (loadedIceFrame) {
+ return;
+ }
+
+ loadedIceFrame = true;
+
+ var iframe = document.createElement('iframe');
+ iframe.onload = function() {
+ iframe.isLoaded = true;
+
+ listenEventHandler('message', iFrameLoaderCallback);
+
+ function iFrameLoaderCallback(event) {
+ if (!event.data || !event.data.iceServers) {
+ return;
+ }
+ callback(event.data.iceServers);
+
+ // this event listener is no more needed
+ window.removeEventListener('message', iFrameLoaderCallback);
+ }
+
+ iframe.contentWindow.postMessage('get-ice-servers', '*');
+ };
+ iframe.src = 'https://cdn.webrtc-experiment.com/getIceServers/';
+ iframe.style.display = 'none';
+ (document.body || document.documentElement).appendChild(iframe);
+}
+
+RTCPeerConnection.iceServers = [];
+
+RTCPeerConnection.iceServers.push({
+ url: 'stun:stun.l.google.com:19302'
+});
+
+RTCPeerConnection.iceServers.push({
+ url: 'stun:stun.anyfirewall.com:3478'
+});
+
+RTCPeerConnection.iceServers.push({
+ url: 'turn:turn.bistri.com:80',
+ credential: 'homeo',
+ username: 'homeo'
+});
+
+RTCPeerConnection.iceServers.push({
+ url: 'turn:turn.anyfirewall.com:443?transport=tcp',
+ credential: 'webrtc',
+ username: 'webrtc'
+});
+
+loadIceFrame(function(servers) {
+ RTCPeerConnection.iceServers = RTCPeerConnection.iceServers.concat(servers);
+});
diff --git a/broadcast.js b/broadcast.js
new file mode 100644
index 0000000..7406bb8
--- /dev/null
+++ b/broadcast.js
@@ -0,0 +1,238 @@
+
+
+var broadcast = function(config) {
+ var self = {
+ userToken: uniqueToken()
+ },
+ channels = '--',
+ isbroadcaster,
+ isGetNewRoom = true,
+ participants = 1,
+ defaultSocket = { };
+
+ function openDefaultSocket() {
+ defaultSocket = config.openSocket({
+ onmessage: onDefaultSocketResponse,
+ callback: function(socket) {
+ defaultSocket = socket;
+ }
+ });
+ }
+
+ function onDefaultSocketResponse(response) {
+ if (response.userToken == self.userToken) return;
+
+ if (isGetNewRoom && response.roomToken && response.broadcaster) config.onRoomFound(response);
+
+ if (response.userToken && response.joinUser == self.userToken && response.participant && channels.indexOf(response.userToken) == -1) {
+ channels += response.userToken + '--';
+ openSubSocket({
+ isofferer: true,
+ channel: response.channel || response.userToken,
+ closeSocket: true
+ });
+ }
+ }
+
+ function openSubSocket(_config) {
+ if (!_config.channel) return;
+ var socketConfig = {
+ channel: _config.channel,
+ onmessage: socketResponse,
+ onopen: function() {
+ if (isofferer && !peer) initPeer();
+ }
+ };
+
+ socketConfig.callback = function(_socket) {
+ socket = _socket;
+ this.onopen();
+ };
+
+ var socket = config.openSocket(socketConfig),
+ isofferer = _config.isofferer,
+ gotstream,
+ htmlElement = document.createElement(self.isAudio ? 'audio' : 'video'),
+ inner = { },
+ peer;
+
+ var peerConfig = {
+ constraints: {
+ mandatory: {
+ OfferToReceiveAudio: true,
+ OfferToReceiveVideo: true
+ },
+ optional: []
+ },
+ attachStream: config.attachStream,
+ onICE: function(candidate) {
+ socket.send({
+ userToken: self.userToken,
+ candidate: {
+ sdpMLineIndex: candidate.sdpMLineIndex,
+ candidate: JSON.stringify(candidate.candidate)
+ }
+ });
+ },
+ onRemoteStream: function(stream) {
+ if (!stream) return;
+
+ htmlElement[moz ? 'mozSrcObject' : 'src'] = moz ? stream : webkitURL.createObjectURL(stream);
+ htmlElement.play();
+
+ _config.stream = stream;
+ if (self.isAudio) {
+ htmlElement.addEventListener('play', function() {
+ this.muted = false;
+ this.volume = 1;
+ afterRemoteStreamStartedFlowing();
+ }, false);
+ } else onRemoteStreamStartsFlowing();
+ }
+ };
+
+ function initPeer(offerSDP) {
+ if (!offerSDP) {
+ peerConfig.onOfferSDP = sendsdp;
+ } else {
+ peerConfig.offerSDP = offerSDP;
+ peerConfig.onAnswerSDP = sendsdp;
+ }
+
+ peer = RTCPeerConnection(peerConfig);
+ }
+
+ function onRemoteStreamStartsFlowing() {
+ if(navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|IEMobile/i)) {
+ // if mobile device
+ return afterRemoteStreamStartedFlowing();
+ }
+
+ if (!(htmlElement.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA || htmlElement.paused || htmlElement.currentTime <= 0)) {
+ afterRemoteStreamStartedFlowing();
+ } else setTimeout(onRemoteStreamStartsFlowing, 50);
+ }
+
+ function afterRemoteStreamStartedFlowing() {
+ gotstream = true;
+ config.onRemoteStream(htmlElement);
+
+ /* closing subsocket here on the offerer side */
+ if (_config.closeSocket) socket = null;
+ }
+
+ function sendsdp(sdp) {
+ sdp = JSON.stringify(sdp);
+ var part = parseInt(sdp.length / 3);
+
+ var firstPart = sdp.slice(0, part),
+ secondPart = sdp.slice(part, sdp.length - 1),
+ thirdPart = '';
+
+ if (sdp.length > part + part) {
+ secondPart = sdp.slice(part, part + part);
+ thirdPart = sdp.slice(part + part, sdp.length);
+ }
+
+ socket.send({
+ userToken: self.userToken,
+ firstPart: firstPart
+ });
+
+ socket.send({
+ userToken: self.userToken,
+ secondPart: secondPart
+ });
+
+ socket.send({
+ userToken: self.userToken,
+ thirdPart: thirdPart
+ });
+ }
+
+ function socketResponse(response) {
+ if (response.userToken == self.userToken) return;
+ if (response.firstPart || response.secondPart || response.thirdPart) {
+ if (response.firstPart) {
+ inner.firstPart = response.firstPart;
+ if (inner.secondPart && inner.thirdPart) selfInvoker();
+ }
+ if (response.secondPart) {
+ inner.secondPart = response.secondPart;
+ if (inner.firstPart && inner.thirdPart) selfInvoker();
+ }
+
+ if (response.thirdPart) {
+ inner.thirdPart = response.thirdPart;
+ if (inner.firstPart && inner.secondPart) selfInvoker();
+ }
+ }
+
+ if (response.candidate && !gotstream) {
+ peer && peer.addICE({
+ sdpMLineIndex: response.candidate.sdpMLineIndex,
+ candidate: JSON.parse(response.candidate.candidate)
+ });
+ }
+ }
+
+ var invokedOnce = false;
+
+ function selfInvoker() {
+ if (invokedOnce) return;
+
+ invokedOnce = true;
+
+ inner.sdp = JSON.parse(inner.firstPart + inner.secondPart + inner.thirdPart);
+ if (isofferer) {
+ peer.addAnswerSDP(inner.sdp);
+ if (config.onNewParticipant) config.onNewParticipant(participants++);
+ } else initPeer(inner.sdp);
+ }
+ }
+
+ function startBroadcasting() {
+ defaultSocket && defaultSocket.send({
+ roomToken: self.roomToken,
+ roomName: self.roomName,
+ broadcaster: self.userToken,
+ isAudio: self.isAudio
+ });
+ setTimeout(startBroadcasting, 3000);
+ }
+
+ function uniqueToken() {
+ var s4 = function() {
+ return Math.floor(Math.random() * 0x10000).toString(16);
+ };
+ return s4() + s4() + "-" + s4() + "-" + s4() + "-" + s4() + "-" + s4() + s4() + s4();
+ }
+
+ openDefaultSocket();
+ return {
+ createRoom: function(_config) {
+ self.roomName = _config.roomName || 'Anonymous';
+ self.isAudio = _config.isAudio;
+ self.roomToken = uniqueToken();
+
+ isbroadcaster = true;
+ isGetNewRoom = false;
+ startBroadcasting();
+ },
+ joinRoom: function(_config) {
+ self.roomToken = _config.roomToken;
+ self.isAudio = _config.isAudio;
+ isGetNewRoom = false;
+
+ openSubSocket({
+ channel: self.userToken
+ });
+
+ defaultSocket.send({
+ participant: true,
+ userToken: self.userToken,
+ joinUser: _config.joinUser
+ });
+ }
+ };
+};
diff --git a/firebase.js b/firebase.js
new file mode 100644
index 0000000..107a15b
--- /dev/null
+++ b/firebase.js
@@ -0,0 +1,128 @@
+(function() {function g(a){throw a;}var j=void 0,k=!0,l=null,o=!1;function aa(a){return function(){return this[a]}}function p(a){return function(){return a}}var r,ba=this;function ca(a,b){var c=a.split("."),d=ba;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)!c.length&&s(b)?d[e]=b:d=d[e]?d[e]:d[e]={}}function da(){}
+function ea(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function s(a){return a!==j}function fa(a){var b=ea(a);return"array"==b||"object"==b&&"number"==typeof a.length}function t(a){return"string"==typeof a}function ga(a){return"number"==typeof a}function ha(a){var b=typeof a;return"object"==b&&a!=l||"function"==b}Math.floor(2147483648*Math.random()).toString(36);function ia(a,b,c){return a.call.apply(a.bind,arguments)}
+function ja(a,b,c){a||g(Error());if(2b?e+="000":256>b?e+="00":4096>b&&(e+="0");return pa[a]=e+b.toString(16)}),'"')};function y(a){if("undefined"!==typeof JSON&&s(JSON.stringify))a=JSON.stringify(a);else{var b=[];na(new ma,a,b);a=b.join("")}return a};function ra(a){for(var b=[],c=0,d=0;d=e&&(e-=55296,d++,z(de?b[c++]=e:(2048>e?b[c++]=e>>6|192:(65536>e?b[c++]=e>>12|224:(b[c++]=e>>18|240,b[c++]=e>>12&63|128),b[c++]=e>>6&63|128),b[c++]=e&63|128)}return b};function A(a,b,c,d){var e;dc&&(e=0===c?"none":"no more than "+c);e&&g(Error(a+" failed: Was called with "+d+(1===d?" argument.":" arguments.")+" Expects "+e+"."))}function B(a,b,c){var d="";switch(b){case 1:d=c?"first":"First";break;case 2:d=c?"second":"Second";break;case 3:d=c?"third":"Third";break;case 4:d=c?"fourth":"Fourth";break;default:sa.assert(o,"errorPrefix_ called with argumentNumber > 4. Need to update it?")}return a+" failed: "+(d+" argument ")}
+function C(a,b,c,d){(!d||s(c))&&"function"!=ea(c)&&g(Error(B(a,b,d)+"must be a valid function."))}function ta(a,b,c){s(c)&&(!ha(c)||c===l)&&g(Error(B(a,b,k)+"must be a valid context object."))};function D(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function ua(a,b){if(Object.prototype.hasOwnProperty.call(a,b))return a[b]};var va={},sa={},wa=/[\[\].#$\/]/,xa=/[\[\].#$]/;function ya(a){return t(a)&&0!==a.length&&!wa.test(a)}function za(a,b,c){(!c||s(b))&&Aa(B(a,1,c),b)}
+function Aa(a,b,c,d){c||(c=0);d||(d=[]);s(b)||g(Error(a+"contains undefined"+Ba(d)));"function"==ea(b)&&g(Error(a+"contains a function"+Ba(d)));Ca(b)&&g(Error(a+"contains "+b.toString()+Ba(d)));1E310485760/3&&10485760=a)&&g("Query.limit: First argument must be a positive integer.");return new G(this.o,this.path,a,this.Z,this.la,this.ra,this.Pa)};G.prototype.limit=G.prototype.rd;G.prototype.Ed=function(a,b){A("Query.startAt",0,2,arguments.length);Ea("Query.startAt",1,a,k);Ga("Query.startAt",b);s(a)||(b=a=l);return new G(this.o,this.path,this.ta,a,b,this.ra,this.Pa)};G.prototype.startAt=G.prototype.Ed;
+G.prototype.ld=function(a,b){A("Query.endAt",0,2,arguments.length);Ea("Query.endAt",1,a,k);Ga("Query.endAt",b);return new G(this.o,this.path,this.ta,this.Z,this.la,a,b)};G.prototype.endAt=G.prototype.ld;function Ja(a){var b={};s(a.Z)&&(b.sp=a.Z);s(a.la)&&(b.sn=a.la);s(a.ra)&&(b.ep=a.ra);s(a.Pa)&&(b.en=a.Pa);s(a.ta)&&(b.l=a.ta);s(a.Z)&&(s(a.la)&&a.Z===l&&a.la===l)&&(b.vf="l");return b}G.prototype.Ia=function(){var a=Ka(Ja(this));return"{}"===a?"default":a};
+function Ia(a,b,c){var d={};b&&c?(d.cancel=b,C(a,3,d.cancel,k),d.W=c,ta(a,4,d.W)):b&&("object"===typeof b&&b!==l?d.W=b:"function"===typeof b?d.cancel=b:g(Error(B(a,3,k)+"must either be a cancel callback or a context object.")));return d};function I(a){if(a instanceof I)return a;if(1==arguments.length){this.m=a.split("/");for(var b=0,c=0;c=a.m.length?l:a.m[a.X]}function La(a){var b=a.X;b=this.m.length)return l;for(var a=[],b=this.X;b=this.m.length};function Na(a,b){var c=F(a);if(c===l)return b;if(c===F(b))return Na(La(a),La(b));g("INTERNAL ERROR: innerPath ("+b+") is not within outerPath ("+a+")")}r.contains=function(a){var b=0;if(this.m.length>a.m.length)return o;for(;bb?1:0}r=Va.prototype;r.ia=function(a,b){return new Va(this.Ma,this.Y.ia(a,b,this.Ma).copy(l,l,o,l,l))};r.remove=function(a){return new Va(this.Ma,this.Y.remove(a,this.Ma).copy(l,l,o,l,l))};r.get=function(a){for(var b,c=this.Y;!c.f();){b=this.Ma(a,c.key);if(0===b)return c.value;0>b?c=c.left:0c?d=d.left:0d?e.copy(l,l,l,e.left.ia(a,b,c),l):0===d?e.copy(l,b,l,l,l):e.copy(l,l,l,l,e.right.ia(a,b,c));return cb(e)};function db(a){if(a.left.f())return Xa;!a.left.H()&&!a.left.left.H()&&(a=eb(a));a=a.copy(l,l,l,db(a.left),l);return cb(a)}
+r.remove=function(a,b){var c,d;c=this;if(0>b(a,c.key))!c.left.f()&&(!c.left.H()&&!c.left.left.H())&&(c=eb(c)),c=c.copy(l,l,l,c.left.remove(a,b),l);else{c.left.H()&&(c=gb(c));!c.right.f()&&(!c.right.H()&&!c.right.left.H())&&(c=hb(c),c.left.left.H()&&(c=gb(c),c=hb(c)));if(0===b(a,c.key)){if(c.right.f())return Xa;d=bb(c.right);c=c.copy(d.key,d.value,l,l,db(c.right))}c=c.copy(l,l,l,l,c.right.remove(a,b))}return cb(c)};r.H=aa("color");
+function cb(a){a.right.H()&&!a.left.H()&&(a=ib(a));a.left.H()&&a.left.left.H()&&(a=gb(a));a.left.H()&&a.right.H()&&(a=hb(a));return a}function eb(a){a=hb(a);a.right.left.H()&&(a=a.copy(l,l,l,l,gb(a.right)),a=ib(a),a=hb(a));return a}function ib(a){var b;b=a.copy(l,l,k,l,a.right.left);return a.right.copy(l,l,a.color,b,l)}function gb(a){var b;b=a.copy(l,l,k,a.left.right,l);return a.left.copy(l,l,a.color,l,b)}
+function hb(a){var b,c;b=a.left.copy(l,l,!a.left.color,l,l);c=a.right.copy(l,l,!a.right.color,l,l);return a.copy(l,l,!a.color,b,c)}function jb(){}r=jb.prototype;r.copy=function(){return this};r.ia=function(a,b){return new ab(a,b,j,j,j)};r.remove=function(){return this};r.get=p(l);r.count=p(0);r.f=p(k);r.sa=p(o);r.Ja=p(o);r.ib=p(l);r.Sa=p(l);r.H=p(o);var Xa=new jb;var kb=Array.prototype,lb=kb.forEach?function(a,b,c){kb.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=t(a)?a.split(""):a,f=0;fa;++a)this.Ob[a]=0;this.reset()}ka(ob,nb);ob.prototype.reset=function(){this.z[0]=1732584193;this.z[1]=4023233417;this.z[2]=2562383102;this.z[3]=271733878;this.z[4]=3285377520;this.Ac=this.eb=0};
+function pb(a,b){var c;c||(c=0);for(var d=a.hd,e=c;ee;e++){var f=d[e-3]^d[e-8]^d[e-14]^d[e-16];d[e]=(f<<1|f>>>31)&4294967295}c=a.z[0];for(var h=a.z[1],i=a.z[2],m=a.z[3],n=a.z[4],q,e=0;80>e;e++)40>e?20>e?(f=m^h&(i^m),q=1518500249):(f=h^i^m,q=1859775393):60>e?(f=h&i|m&(h|i),q=2400959708):(f=h^i^m,q=3395469782),f=(c<<5|c>>>27)+f+n+q+d[e]&4294967295,n=m,m=i,i=(h<<30|h>>>2)&4294967295,h=c,c=f;a.z[0]=a.z[0]+c&4294967295;a.z[1]=a.z[1]+h&
+4294967295;a.z[2]=a.z[2]+i&4294967295;a.z[3]=a.z[3]+m&4294967295;a.z[4]=a.z[4]+n&4294967295}ob.prototype.update=function(a,b){s(b)||(b=a.length);var c=this.gc,d=this.eb,e=0;if(t(a))for(;ec;c++)Gb[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(c),Hb[c]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.".charAt(c)}for(var c=b?Hb:Gb,d=[],e=0;e>2,f=(f&3)<<4|i>>4,i=(i&15)<<2|n>>6,n=n&63;m||(n=64,h||(i=64));d.push(c[q],c[f],c[i],c[n])}return d.join("")}
+;var Jb,Kb=1;Jb=function(){return Kb++};function z(a,b){a||g(Error("Firebase INTERNAL ASSERT FAILED:"+b))}function Lb(a){var b=ra(a),a=new ob;a.update(b);var b=[],c=8*a.Ac;56>a.eb?a.update(a.Ob,56-a.eb):a.update(a.Ob,64-(a.eb-56));for(var d=63;56<=d;d--)a.gc[d]=c&255,c/=256;pb(a,a.gc);for(d=c=0;5>d;d++)for(var e=24;0<=e;e-=8)b[c++]=a.z[d]>>e&255;return Ib(b)}
+function Mb(){for(var a="",b=0;bb?1:-1:0}function Xb(a,b){if(b&&a in b)return b[a];g(Error("Missing required key ("+a+") in object: "+y(b)))}var Yb=0;function Ka(a){if("object"!==typeof a||a===l)return y(a);var b=[],c;for(c in a)b.push(c);b.sort();c="{";for(var d=0;da?c.push(a.substring(d,a.length)):c.push(a.substring(d,d+b));return c}
+function $b(a){z(!Ca(a));var b,c,d,e;0===a?(d=c=0,b=-Infinity===1/a?1:0):(b=0>a,a=Math.abs(a),a>=Math.pow(2,-1022)?(d=Math.min(Math.floor(Math.log(a)/Math.LN2),1023),c=d+1023,d=Math.round(a*Math.pow(2,52-d)-Math.pow(2,52))):(c=0,d=Math.round(a/Math.pow(2,-1074))));e=[];for(a=52;a;a-=1)e.push(d%2?1:0),d=Math.floor(d/2);for(a=11;a;a-=1)e.push(c%2?1:0),c=Math.floor(c/2);e.push(b?1:0);e.reverse();b=e.join("");c="";for(a=0;64>a;a+=8)d=parseInt(b.substr(a,8),2).toString(16),1===d.length&&(d="0"+d),c+=d;
+return c.toLowerCase()};function ac(a,b){this.oa=a;z(this.oa!==l,"LeafNode shouldn't be created with null value.");this.Ua="undefined"!==typeof b?b:l}r=ac.prototype;r.J=p(k);r.k=aa("Ua");r.ec=function(a){return new ac(this.oa,a)};r.N=function(){return O};r.F=function(a){return F(a)===l?this:O};r.T=p(l);r.D=function(a,b){return(new P(new Va,this.Ua)).D(a,b)};r.Ya=function(a,b){var c=F(a);return c===l?b:this.D(c,O.Ya(La(a),b))};r.f=p(o);r.Hb=p(0);
+r.P=function(a){return a&&this.k()!==l?{".value":this.j(),".priority":this.k()}:this.j()};r.hash=function(){var a="";this.k()!==l&&(a+="priority:"+bc(this.k())+":");var b=typeof this.oa,a=a+(b+":"),a="number"===b?a+$b(this.oa):a+this.oa;return Lb(a)};r.j=aa("oa");r.toString=function(){return"string"===typeof this.oa?'"'+this.oa+'"':this.oa};function P(a,b){this.R=a||new Va;this.Ua="undefined"!==typeof b?b:l}r=P.prototype;r.J=p(o);r.k=aa("Ua");r.ec=function(a){return new P(this.R,a)};r.D=function(a,b){var c=this.R.remove(a);b&&b.f()&&(b=l);b!==l&&(c=c.ia(a,b));return b&&b.k()!==l?new cc(c,l,this.Ua):new P(c,this.Ua)};r.Ya=function(a,b){var c=F(a);if(c===l)return b;var d=this.N(c).Ya(La(a),b);return this.D(c,d)};r.f=function(){return this.R.f()};r.Hb=function(){return this.R.count()};var dc=/^\d+$/;r=P.prototype;
+r.P=function(a){if(this.f())return l;var b={},c=0,d=0,e=k;this.B(function(f,h){b[f]=h.P(a);c++;e&&dc.test(f)?d=Math.max(d,Number(f)):e=o});if(!a&&e&&d<2*c){var f=[],h;for(h in b)f[h]=b[h];return f}a&&this.k()!==l&&(b[".priority"]=this.k());return b};r.hash=function(){var a="";this.k()!==l&&(a+="priority:"+bc(this.k())+":");this.B(function(b,c){var d=c.hash();""!==d&&(a+=":"+b+":"+d)});return""===a?"":Lb(a)};r.N=function(a){a=this.R.get(a);return a===l?O:a};
+r.F=function(a){var b=F(a);return b===l?this:this.N(b).F(La(a))};r.T=function(a){return Ya(this.R,a)};r.Kc=function(){return this.R.ib()};r.Lc=function(){return this.R.Sa()};r.B=function(a){return this.R.sa(a)};r.lc=function(a){return this.R.Ja(a)};r.Qa=function(){return this.R.Qa()};r.toString=function(){var a="{",b=k;this.B(function(c,d){b?b=o:a+=", ";a+='"'+c+'" : '+d.toString()});return a+="}"};var O=new P(new Va);function cc(a,b,c){P.call(this,a,c);b===l&&(b=new Va(ec),a.sa(function(a,c){b=b.ia({name:a,wa:c.k()},c)}));this.ka=b}ka(cc,P);r=cc.prototype;r.D=function(a,b){var c=this.N(a),d=this.R,e=this.ka;c!==l&&(d=d.remove(a),e=e.remove({name:a,wa:c.k()}));b&&b.f()&&(b=l);b!==l&&(d=d.ia(a,b),e=e.ia({name:a,wa:b.k()},b));return new cc(d,e,this.k())};r.T=function(a,b){var c=Ya(this.ka,{name:a,wa:b.k()});return c?c.name:l};r.B=function(a){return this.ka.sa(function(b,c){return a(b.name,c)})};
+r.lc=function(a){return this.ka.Ja(function(b,c){return a(b.name,c)})};r.Qa=function(){return this.ka.Qa(function(a,b){return{key:a.name,value:b}})};r.Kc=function(){return this.ka.f()?l:this.ka.ib().name};r.Lc=function(){return this.ka.f()?l:this.ka.Sa().name};function Q(a,b){if("object"!==typeof a)return new ac(a,b);if(a===l)return O;var c=l;".priority"in a?c=a[".priority"]:"undefined"!==typeof b&&(c=b);z(c===l||"string"===typeof c||"number"===typeof c);if(".value"in a&&a[".value"]!==l)return new ac(a[".value"],c);var c=new P(new Va,c),d;for(d in a)if(D(a,d)&&"."!==d.substring(0,1)){var e=Q(a[d]);if(e.J()||!e.f())c=c.D(d,e)}return c}function ec(a,b){return Wb(a.wa,b.wa)||(a.name!==b.name?a.name=a.length){var b=Number(a);if(!isNaN(b)){c.zc=
+b;c.frames=[];a=l;break a}}c.zc=1;c.frames=[]}a!==l&&sc(c,a)}};this.U.onerror=function(){c.e("WebSocket error. Closing connection.");c.Ha()}};pc.prototype.start=function(){};pc.isAvailable=function(){return!("undefined"!==typeof navigator&&"Opera"===navigator.appName)&&oc!==l&&!qc};function sc(a,b){a.frames.push(b);if(a.frames.length==a.zc){var c=a.frames.join("");a.frames=l;c="undefined"!==typeof JSON&&s(JSON.parse)?JSON.parse(c):la(c);a.Mb(c)}}
+pc.prototype.send=function(a){rc(this);a=y(a);ic(this.$,"bytes_sent",a.length);a=Zb(a,16384);1