init project

This commit is contained in:
Artyom Abubakirov 2018-05-28 23:27:12 +05:00
commit 077a7d127e
26 changed files with 10230 additions and 0 deletions

361
CodecsHandler.js Executable file
View File

@ -0,0 +1,361 @@
// 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;

41
IceServersHandler.js Executable file
View File

@ -0,0 +1,41 @@
// IceServersHandler.js
var IceServersHandler = (function() {
function getIceServers(connection) {
// resiprocate: 3344+4433
var iceServers = [{
'urls': [
'turn:webrtcweb.com:7788', // coTURN 7788+8877
'turn:webrtcweb.com:4455', // restund udp
'turn:webrtcweb.com:7788?transport=udp', // coTURN udp
'turn:webrtcweb.com:7788?transport=tcp', // coTURN tcp
'turn:webrtcweb.com:4455?transport=udp', // restund udp
'turn:webrtcweb.com:5544?transport=tcp', // restund tcp
'turn:webrtcweb.com:7575?transport=udp', // pions/turn
],
'username': 'muazkh',
'credential': 'muazkh'
},
{
'urls': [
'stun:stun.l.google.com:19302',
'stun:stun.l.google.com:19302?transport=udp'
]
}
];
if (typeof window.InstallTrigger !== 'undefined') {
iceServers[0].urls = iceServers[0].urls.pop();
iceServers[1].urls = iceServers[1].urls.pop();
}
return iceServers;
}
return {
getIceServers: getIceServers
};
})();

468
MultiStreamsMixer.js Executable file
View File

@ -0,0 +1,468 @@
// Last time updated: 2018-03-02 2:56:28 AM UTC
// ________________________
// MultiStreamsMixer v1.0.5
// Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
function MultiStreamsMixer(arrayOfMediaStreams) {
// requires: chrome://flags/#enable-experimental-web-platform-features
var videos = [];
var isStopDrawingFrames = false;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;';
(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') {
if (!('getVideoTracks' in MediaStream.prototype)) {
MediaStream.prototype.getVideoTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('video') !== -1) {
tracks.push(track);
}
});
return tracks;
};
MediaStream.prototype.getAudioTracks = function() {
if (!this.getTracks) {
return [];
}
var tracks = [];
this.getTracks.forEach(function(track) {
if (track.kind.toString().indexOf('audio') !== -1) {
tracks.push(track);
}
});
return tracks;
};
}
// 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, ignoreCreateObjectURL) {
if ('createObjectURL' in URL && !ignoreCreateObjectURL) {
try {
element.src = URL.createObjectURL(stream);
} catch (e) {
setSrcObject(stream, element, true);
return;
}
} else if ('srcObject' in element) {
element.srcObject = stream;
} else if ('mozSrcObject' in element) {
element.mozSrcObject = stream;
} else {
alert('createObjectURL/srcObject both are not supported.');
}
}
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 {
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.getAudioTracks().forEach(function(track) {
mixedVideoStream.addTrack(track);
});
}
var fullcanvas;
arrayOfMediaStreams.forEach(function(stream) {
if (stream.fullcanvas) {
fullcanvas = true;
}
});
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.getVideoTracks().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.getAudioTracks().length) {
return;
}
audioTracksLength++;
var audioSource = self.audioContext.createMediaStreamSource(stream);
if (self.useGainNode === true) {
audioSource.connect(self.gainNode);
}
self.audioSources.push(audioSource);
});
if (!audioTracksLength) {
return;
}
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.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];
}
arrayOfMediaStreams.concat(streams);
streams.forEach(function(stream) {
if (stream.getVideoTracks().length) {
var video = getVideo(stream);
video.stream = stream;
videos.push(video);
}
if (stream.getAudioTracks().length && self.audioContext) {
var audioSource = self.audioContext.createMediaStreamSource(stream);
audioSource.connect(self.audioDestination);
self.audioSources.push(audioSource);
}
});
};
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.getVideoTracks().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;
}

68
README.md Executable file
View File

@ -0,0 +1,68 @@
# Chrome extension for WebRTC Screen Sharing
<a target="_blank" href="https://chrome.google.com/webstore/detail/webrtc-desktop-sharing/nkemblooioekjnpfekmjhpgkackcajhg"><img alt="WebRTC Screen Sharing" src="https://lh3.googleusercontent.com/Jpi56T9fBfBXJGsDJchpAvW-PvZrysL99GLibfUKMVon8mk0KnBZtZU3W08IbkeYIAgyRvz9Lg=w640-h400-e365" title="WebRTC Screen Sharing"></img></a>
## How to install?
<a target="_blank" href="https://chrome.google.com/webstore/detail/webrtc-desktop-sharing/nkemblooioekjnpfekmjhpgkackcajhg"><img alt="Install Dessktop Sharing Extension" src="https://raw.github.com/GoogleChrome/chrome-app-samples/master/tryitnowbutton_small.png" title="Click here to install this sample from the Chrome Web Store"></img></a>
* https://chrome.google.com/webstore/detail/webrtc-desktop-sharing/nkemblooioekjnpfekmjhpgkackcajhg
## How to view screen?
Try any of the below URL. Replace `your_room_id` with real room-id:
```
https://webrtcweb.com/screen?s=your_room_id
https://cdn.rawgit.com/muaz-khan/Chrome-Extensions/master/desktopCapture-p2p/index.html
```
## Developer Notes
1. Chrome extension can share your screen, tab, any application's window, camera, microphone and speakers.
2. Clicking extension icon will generate a unique random room URL. You can share that URL with multiple users and all of them can view your screen.
3. [RTCMultiConnection](https://github.com/muaz-khan/RTCMultiConnection) is a WebRTC library that is used for peer-to-peer WebRTC streaming.
4. PubNub is used as a signaling method for handshake. However you can use [any WebRTC signaing option](https://github.com/muaz-khan/WebRTC-Experiment/blob/master/Signaling.md).
5. You can replace or include your own STUN+TURN servers in the [IceServersHandler.js](https://github.com/muaz-khan/Chrome-Extensions/blob/master/desktopCapture-p2p/IceServersHandler.js) file.
6. VP8 is currently default video codecs. However VP9 is recommended. You can always change codecs using options page.
7. [getStats](https://github.com/muaz-khan/getStats) is a WebRTC library that is used for bandwidth & codecs detection. This library is optional. You can always remove it.
## Before publishing it for your own business
> This step is optional. You can keep using `webrtcweb.com` URL as a screen viewer.
Open [desktop-capturing.js](https://github.com/muaz-khan/Chrome-Extensions/blob/master/desktopCapture-p2p/desktop-capturing.js) and find following line:
```javascript
var resultingURL = 'https://webrtcweb.com/screen?s=' + connection.sessionid;
```
Replace above line with your own server/website:
```javascript
var resultingURL = 'https://yourWebSite.com/index.html?s=' + connection.sessionid;
```
You can find `index.html` here:
* [desktopCapture-p2p/index.html](https://github.com/muaz-khan/Chrome-Extensions/blob/master/desktopCapture-p2p/index.html)
## How to publish it for your own business?
Make ZIP of the directory. Then navigate to [Chrome WebStore Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard) and click **Add New Item** blue button.
To learn more about how to publish a chrome extension in Google App Store:
* https://developer.chrome.com/webstore/publish
## For more information
For additional information, click [this link](https://github.com/muaz-khan/WebRTC-Experiment/blob/7cd04a81b30cdca2db159eb746e2714307640767/Chrome-Extensions/desktopCapture/README.md).
## It is Open-Sourced!
* https://github.com/muaz-khan/Chrome-Extensions/tree/master/desktopCapture-p2p
## License
[Chrome-Extensions](https://github.com/muaz-khan/Chrome-Extensions) are released under [MIT licence](https://www.webrtc-experiment.com/licence/) . Copyright (c) [Muaz Khan](https://plus.google.com/+MuazKhan).

6557
RTCMultiConnection.js Executable file

File diff suppressed because it is too large Load Diff

1
camera-mic.html Executable file
View File

@ -0,0 +1 @@
<script src="camera-mic.js"></script>

15
camera-mic.js Executable file
View File

@ -0,0 +1,15 @@
document.write('<h1 style="font-family: Courier New; font-size: 30px; color:green;margin-top:200px;">The purpose of this page is to access your camera and microphone.</h1>');
document.write('<h1 style="font-family: Courier New; font-size: 25px; color:red;margin-top:20px;">You can REMOVE i.e. DELETE camera permissions anytime on this page:</h1>');
document.write('<pre style="font-family: Courier New; font-size: 25px; color:blue;margin-top:20px;">chrome://settings/content/camera?search=camera</pr>');
var constraints = {
audio: true,
video: true
};
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
document.write('<h1 style="font-family: Courier New; font-size: 35px; color: green;"></h1><video autoplay controls src="' + URL.createObjectURL(stream) + '"></video>');
document.querySelector('h1').innerHTML = 'Now you can close this page and click extension icon again.'
}).catch(function() {
document.querySelector('h1').innerHTML = 'Unable to capture your camera and microphone.';
});

925
desktop-capturing.js Executable file
View File

@ -0,0 +1,925 @@
// Muaz Khan - https://github.com/muaz-khan
// MIT License - https://www.WebRTC-Experiment.com/licence/
// Source Code - https://github.com/muaz-khan/Chrome-Extensions
// this page is using desktopCapture API to capture and share desktop
// http://developer.chrome.com/extensions/desktopCapture.html
// chrome.browserAction.onClicked.addListener(captureDesktop);
var runtimePort;
var websocket;
chrome.runtime.onConnect.addListener(function(port) {
runtimePort = port;
runtimePort.onMessage.addListener(function(message) {
if (!message || !message.messageFromContentScript1234) {
return;
}
if (message.startSharing || message.stopSharing) {
captureDesktop();
return;
}
});
});
window.addEventListener('offline', function() {
if (!connection || !connection.attachStreams.length) return;
setDefaults();
chrome.runtime.reload();
}, false);
window.addEventListener('online', function() {
if (!connection) return;
setDefaults();
chrome.runtime.reload();
}, false);
function captureDesktop() {
if (connection && connection.attachStreams[0]) {
setDefaults();
connection.attachStreams.forEach(function(stream) {
stream.getTracks().forEach(function(track) {
track.stop();
});
});
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'false',
enableCamera: 'false',
enableScreen: 'false',
isSharingOn: 'false',
enableSpeakers: 'false'
});
return;
}
chrome.browserAction.setTitle({
title: 'Capturing Desktop'
});
chrome.storage.sync.get(null, function(items) {
var resolutions = {};
if (items['room_password']) {
room_password = items['room_password'];
}
if (items['room_id']) {
room_id = items['room_id'];
}
if (items['codecs']) {
codecs = items['codecs'];
}
if (items['bandwidth']) {
bandwidth = items['bandwidth'];
}
if (items['enableTabCaptureAPI'] == 'true') {
enableTabCaptureAPI = items['enableTabCaptureAPI'];
}
if (items['enableMicrophone'] == 'true') {
enableMicrophone = items['enableMicrophone'];
}
if (items['enableSpeakers'] == 'true') {
enableSpeakers = items['enableSpeakers'];
}
if (items['enableCamera'] == 'true') {
enableCamera = items['enableCamera'];
}
if (items['enableScreen'] == 'true') {
enableScreen = items['enableScreen'];
}
if (items['enableTabCaptureAPI'] == 'true') {
enableTabCaptureAPI = items['enableTabCaptureAPI'];
}
if (items['isSharingOn'] == 'true') {
isSharingOn = items['isSharingOn'];
}
var _resolutions = items['resolutions'];
if (!_resolutions) {
_resolutions = 'fit-screen';
chrome.storage.sync.set({
resolutions: 'fit-screen'
}, function() {});
}
if (_resolutions === 'fit-screen') {
// resolutions.maxWidth = screen.availWidth;
// resolutions.maxHeight = screen.availHeight;
resolutions.maxWidth = screen.width;
resolutions.maxHeight = screen.height;
}
if (_resolutions === '4K') {
resolutions.maxWidth = 3840;
resolutions.maxHeight = 2160;
}
if (_resolutions === '1080p') {
resolutions.maxWidth = 1920;
resolutions.maxHeight = 1080;
}
if (_resolutions === '720p') {
resolutions.maxWidth = 1280;
resolutions.maxHeight = 720;
}
if (_resolutions === '360p') {
resolutions.maxWidth = 640;
resolutions.maxHeight = 360;
}
if (_resolutions === '4K') {
alert('"4K" resolutions is not stable in Chrome. Please try "fit-screen" instead.');
}
var sources = ['screen', 'window', 'tab'];
if (enableSpeakers) {
sources.push('audio');
}
if (enableTabCaptureAPI) {
captureTabUsingTabCapture(resolutions);
return;
}
if (enableCamera || enableMicrophone) {
captureCamera(function(stream) {
if (!enableScreen) {
gotCustomStream(stream);
//CHANGES
//gotStream(stream);
return;
}
desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) {
opts = opts || {};
opts.resolutions = resolutions;
opts.stream = stream;
onAccessApproved(chromeMediaSourceId, opts);
});
});
return;
}
desktop_id = chrome.desktopCapture.chooseDesktopMedia(sources, function(chromeMediaSourceId, opts) {
opts = opts || {};
opts.resolutions = resolutions;
onAccessApproved(chromeMediaSourceId, opts);
});
});
}
function captureTabUsingTabCapture(resolutions) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function(arrayOfTabs) {
var activeTab = arrayOfTabs[0];
var activeTabId = activeTab.id; // or do whatever you need
var constraints = {
video: true,
videoConstraints: {
mandatory: {
chromeMediaSource: 'tab',
maxWidth: resolutions.maxWidth,
maxHeight: resolutions.maxHeight,
minWidth: resolutions.minWidth,
minHeight: resolutions.minHeight,
minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight),
maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight),
minFrameRate: 64,
maxFrameRate: 128
}
}
};
if (!!enableSpeakers) {
constraints.audio = true;
constraints.audioConstraints = {
mandatory: {
echoCancellation: true
}
};
}
// chrome.tabCapture.onStatusChanged.addListener(function(event) { /* event.status */ });
chrome.tabCapture.capture(constraints, function(stream) {
gotTabCaptureStream(stream, constraints);
});
});
}
function gotTabCaptureStream(stream, constraints) {
if (!stream) {
if (constraints.audio === true) {
enableSpeakers = false;
captureTabUsingTabCapture(resolutions);
return;
}
return alert('still no tabCapture stream');
chrome.runtime.reload();
return;
}
var newStream = new MediaStream();
stream.getTracks().forEach(function(track) {
newStream.addTrack(track);
});
initVideoPlayer(newStream);
gotCustomStream(newStream);
// CHANGES
//gotStream(newStream);
}
var desktop_id;
var constraints;
var room_password = '';
var room_id = '';
var codecs = 'default';
var bandwidth;
var enableTabCaptureAPI;
var enableMicrophone;
var enableSpeakers;
var enableCamera;
var enableScreen;
var isSharingOn;
// Array of blobs
var recordedBlobs;
// Socket for sending data
var socket;
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);
}
function onAccessApproved(chromeMediaSourceId, opts) {
if (!chromeMediaSourceId) {
setDefaults();
return;
}
var resolutions = opts.resolutions;
// CHANGES
chrome.storage.sync.get(null, function(items) {
constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: chromeMediaSourceId,
maxWidth: 1280, //resolutions.maxWidth,
maxHeight: 720,//resolutions.maxHeight,
minWidth: resolutions.minWidth,
minHeight: resolutions.minHeight,
minAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight),
maxAspectRatio: getAspectRatio(resolutions.maxWidth, resolutions.maxHeight),
minFrameRate: 25, //64,
maxFrameRate: 60 //128
},
optional: []
}
};
if (opts.canRequestAudioTrack === true) {
constraints.audio = {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: chromeMediaSourceId,
echoCancellation: true
},
optional: []
};
}
navigator.webkitGetUserMedia(constraints, function(screenStream) {
var win;
addStreamStopListener(screenStream, function() {
if (win && !win.closed) {
win.close();
} else {
captureDesktop();
}
});
if (opts.stream) {
if (enableCamera && opts.stream.getVideoTracks().length) {
var cameraStream = opts.stream;
screenStream.fullcanvas = true;
screenStream.width = screen.width; // or 3840
screenStream.height = screen.height; // or 2160
cameraStream.width = parseInt((15 / 100) * screenStream.width);
cameraStream.height = parseInt((15 / 100) * screenStream.height);
cameraStream.top = screenStream.height - cameraStream.height - 20;
cameraStream.left = screenStream.width - cameraStream.width - 20;
var mixer = new MultiStreamsMixer([screenStream, cameraStream]);
mixer.frameInterval = 1;
mixer.startDrawingFrames();
screenStream = mixer.getMixedStream();
// win = openVideoPreview(screenStream);
} else if (enableMicrophone && opts.stream.getAudioTracks().length) {
var speakers = new MediaStream();
screenStream.getAudioTracks().forEach(function(track) {
speakers.addTrack(track);
screenStream.removeTrack(track);
});
var mixer = new MultiStreamsMixer([speakers, opts.stream]);
mixer.getMixedStream().getAudioTracks().forEach(function(track) {
screenStream.addTrack(track);
});
screenStream.getVideoTracks().forEach(function(track) {
track.onended = function() {
if (win && !win.closed) {
win.close();
} else {
captureDesktop();
}
};
})
}
}
gotCustomStream(screenStream);
// CHANGES
//gotStream(screenStream);
}, getUserMediaError);
});
}
function openVideoPreview(stream) {
var win = window.open("video.html?src=" + URL.createObjectURL(stream), "_blank", "top=0,left=0");
var timer = setInterval(function() {
if (win.closed) {
clearInterval(timer);
captureDesktop();
}
}, 1000);
return win;
}
function addStreamStopListener(stream, callback) {
var streamEndedEvent = 'ended';
if ('oninactive' in stream) {
streamEndedEvent = 'inactive';
}
stream.addEventListener(streamEndedEvent, function() {
callback();
callback = function() {};
}, false);
stream.getAudioTracks().forEach(function(track) {
track.addEventListener(streamEndedEvent, function() {
callback();
callback = function() {};
}, false);
});
stream.getVideoTracks().forEach(function(track) {
track.addEventListener(streamEndedEvent, function() {
callback();
callback = function() {};
}, false);
});
}
function gotCustomStream(stream) {
if (!stream) {
setDefaults();
chrome.windows.create({
url: "data:text/html,<h1>Internal error occurred while capturing the screen.</h1>",
type: 'popup',
width: screen.width / 2,
height: 170
});
return;
}
chrome.browserAction.setTitle({
title: 'Connecting to WebSockets server.'
});
chrome.browserAction.disable();
addStreamStopListener(stream, function() {
setDefaults();
chrome.runtime.reload();
});
chrome.windows.create({
url: chrome.extension.getURL('_generated_background_page.html'),
type: 'popup',
focused: false,
width: 1,
height: 1,
top: parseInt(screen.height),
left: parseInt(screen.width)
}, function(win) {
var background_page_id = win.id;
setTimeout(function() {
chrome.windows.remove(background_page_id);
}, 3000);
});
chrome.browserAction.setIcon({
path: 'images/pause22.png'
});
window.stream = stream;
startSharing();
}
function startSharing(){
if(!window.stream){
console.log('windows.steam not found');
return;
}
recordedBlobs = [];
websocket = io('https://kurento.fishrungames.com/');
websocket.on('connect', function(data){
console.log('Connected to socket.')
});
var options = {mimeType: 'video/webm;codecs=vp9'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
console.log(options.mimeType + ' is not Supported');
options = {mimeType: 'video/webm;codecs=vp8'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
console.log(options.mimeType + ' is not Supported');
options = {mimeType: 'video/webm'};
if (!MediaRecorder.isTypeSupported(options.mimeType)) {
console.log(options.mimeType + ' is not Supported');
options = {mimeType: ''};
}
}
}
try {
mediaRecorder = new MediaRecorder(window.stream, options);
} catch (e) {
console.error('Exception while creating MediaRecorder: ' + e);
alert('Exception while creating MediaRecorder: '
+ e + '. mimeType: ' + options.mimeType);
return;
}
//here
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start(60); // collect data
}
function handleDataAvailable(event) {
if (event.data && event.data.size > 0) {
recordedBlobs.push(event.data);
websocket.emit('blob', event.data);
}
}
function gotStream(stream) {
if (!stream) {
setDefaults();
chrome.windows.create({
url: "data:text/html,<h1>Internal error occurred while capturing the screen.</h1>",
type: 'popup',
width: screen.width / 2,
height: 170
});
return;
}
chrome.browserAction.setTitle({
title: 'Connecting to WebSockets server.'
});
chrome.browserAction.disable();
addStreamStopListener(stream, function() {
setDefaults();
chrome.runtime.reload();
});
// as it is reported that if you drag chrome screen's status-bar
// and scroll up/down the screen-viewer page.
// chrome auto-stops the screen without firing any 'onended' event.
// chrome also hides screen status bar.
chrome.windows.create({
url: chrome.extension.getURL('_generated_background_page.html'),
type: 'popup',
focused: false,
width: 1,
height: 1,
top: parseInt(screen.height),
left: parseInt(screen.width)
}, function(win) {
var background_page_id = win.id;
setTimeout(function() {
chrome.windows.remove(background_page_id);
}, 3000);
});
setupRTCMultiConnection(stream);
chrome.browserAction.setIcon({
path: 'images/pause22.png'
});
}
function getUserMediaError(e) {
setDefaults();
chrome.windows.create({
url: "data:text/html,<h1>getUserMediaError: " + JSON.stringify(e, null, '<br>') + "</h1><br>Constraints used:<br><pre>" + JSON.stringify(constraints, null, '<br>') + '</pre>',
type: 'popup',
width: screen.width / 2,
height: 170
});
}
// RTCMultiConnection - www.RTCMultiConnection.org
var connection;
var popup_id;
function setBadgeText(text) {
chrome.browserAction.setBadgeBackgroundColor({
color: [255, 0, 0, 255]
});
chrome.browserAction.setBadgeText({
text: text + ''
});
chrome.browserAction.setTitle({
title: text + ' users are viewing your screen!'
});
}
function setupRTCMultiConnection(stream) {
// www.RTCMultiConnection.org/docs/
connection = new RTCMultiConnection();
connection.optionalArgument = {
optional: [],
mandatory: {}
};
connection.channel = connection.sessionid = connection.userid;
if (room_id && room_id.length) {
connection.channel = connection.sessionid = connection.userid = room_id;
}
connection.autoReDialOnFailure = true;
connection.getExternalIceServers = false;
connection.iceServers = IceServersHandler.getIceServers();
function setBandwidth(sdp, value) {
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:' + value + '\r\n');
return sdp;
}
connection.processSdp = function(sdp) {
if (bandwidth) {
try {
bandwidth = parseInt(bandwidth);
} catch (e) {
bandwidth = null;
}
if (bandwidth && bandwidth != NaN && bandwidth != 'NaN' && typeof bandwidth == 'number') {
sdp = setBandwidth(sdp, bandwidth);
sdp = BandwidthHandler.setVideoBitrates(sdp, {
min: bandwidth,
max: bandwidth
});
}
}
if (!!codecs && codecs !== 'default') {
sdp = CodecsHandler.preferCodec(sdp, codecs);
}
return sdp;
};
// www.RTCMultiConnection.org/docs/session/
connection.session = {
video: true,
oneway: true
};
// www.rtcmulticonnection.org/docs/sdpConstraints/
connection.sdpConstraints.mandatory = {
OfferToReceiveAudio: false,
OfferToReceiveVideo: false
};
connection.onstream = connection.onstreamended = function(event) {
try {
event.mediaElement.pause();
delete event.mediaElement;
} catch (e) {}
};
// www.RTCMultiConnection.org/docs/dontCaptureUserMedia/
connection.dontCaptureUserMedia = true;
// www.RTCMultiConnection.org/docs/attachStreams/
connection.attachStreams.push(stream);
if (room_password && room_password.length) {
connection.onRequest = function(request) {
if (request.extra.password !== room_password) {
connection.reject(request);
chrome.windows.create({
url: "data:text/html,<h1>A user tried to join your room with invalid password. His request is rejected. He tried password: " + request.extra.password + " </h2>",
type: 'popup',
width: screen.width / 2,
height: 170
});
return;
}
connection.accept(request);
};
}
// www.RTCMultiConnection.org/docs/openSignalingChannel/
var onMessageCallbacks = {};
var websocket = io('https://kurento.fishrungames.com/');
var text = '-';
(function looper() {
if (!connection) {
setBadgeText('');
return;
}
if (connection.isInitiator) {
setBadgeText('0');
return;
}
text += ' -';
if (text.length > 6) {
text = '-';
}
setBadgeText(text);
setTimeout(looper, 500);
})();
var connectedUsers = 0;
connection.ondisconnected = function() {
connectedUsers--;
setBadgeText(connectedUsers);
};
websocket.onmessage = function(e) {
data = JSON.parse(e.data);
if (data === 'received-your-screen') {
connectedUsers++;
setBadgeText(connectedUsers);
}
if (data.sender == connection.userid) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
};
websocket.push = websocket.send;
websocket.send = function(data) {
data.sender = connection.userid;
websocket.push(JSON.stringify(data));
};
// overriding "openSignalingChannel" method
connection.openSignalingChannel = function(config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
// directly returning socket object using "return" statement
return {
send: function(message) {
websocket.send({
sender: connection.userid,
channel: channel,
message: message
});
},
channel: channel
};
};
websocket.onerror = function() {
if (!!connection && connection.attachStreams.length) {
chrome.windows.create({
url: "data:text/html,<h1>Failed connecting the WebSockets server. Please click screen icon to try again.</h1>",
type: 'popup',
width: screen.width / 2,
height: 170
});
}
setDefaults();
chrome.runtime.reload();
};
websocket.onopen = function() {
chrome.browserAction.enable();
setBadgeText(0);
console.info('WebSockets connection is opened.');
// www.RTCMultiConnection.org/docs/open/
var sessionDescription = connection.open({
dontTransmit: true
});
var resultingURL = 'https://webrtcweb.com/screen?s=' + connection.sessionid;
// resultingURL = 'http://localhost:9001/?s=' + connection.sessionid;
if (room_password && room_password.length) {
resultingURL += '&p=' + room_password;
}
var popup_width = 600;
var popup_height = 170;
chrome.windows.create({
url: "data:text/html,<title>Unique Room URL</title><h1 style='text-align:center'>Copy following private URL:</h1><input type='text' value='" + resultingURL + "' style='text-align:center;width:100%;font-size:1.2em;'><p style='text-align:center'>You can share this private-session URI with fellows using email or social networks.</p>",
type: 'popup',
width: popup_width,
height: popup_height,
top: parseInt((screen.height / 2) - (popup_height / 2)),
left: parseInt((screen.width / 2) - (popup_width / 2)),
focused: true
}, function(win) {
popup_id = win.id;
});
};
}
function setDefaults() {
if (connection) {
connection.close();
connection.attachStreams = [];
}
chrome.browserAction.setIcon({
path: 'images/desktopCapture22.png'
});
if (popup_id) {
try {
chrome.windows.remove(popup_id);
} catch (e) {}
popup_id = null;
}
chrome.browserAction.setTitle({
title: 'Share Desktop'
});
chrome.browserAction.setBadgeText({
text: ''
});
}
var videoPlayers = [];
function initVideoPlayer(stream) {
var videoPlayer = document.createElement('video');
videoPlayer.muted = !enableTabCaptureAPI;
videoPlayer.volume = !!enableTabCaptureAPI;
videoPlayer.autoplay = true;
videoPlayer.srcObject = stream;
videoPlayers.push(videoPlayer);
}
var microphoneDevice = false;
var cameraDevice = false;
function captureCamera(callback) {
var supported = navigator.mediaDevices.getSupportedConstraints();
var constraints = {};
if (enableCamera) {
constraints.video = {
width: {
min: 640,
ideal: 1920,
max: 1920
},
height: {
min: 400,
ideal: 1080
}
};
if (supported.aspectRatio) {
constraints.video.aspectRatio = 1.777777778;
}
if (supported.frameRate) {
constraints.video.frameRate = {
ideal: 30
};
}
if (cameraDevice && cameraDevice.length) {
constraints.video.deviceId = cameraDevice;
}
}
if (enableMicrophone) {
constraints.audio = {};
if (microphoneDevice && microphoneDevice.length) {
constraints.audio.deviceId = microphoneDevice;
}
if (supported.echoCancellation) {
constraints.audio.echoCancellation = true;
}
}
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
initVideoPlayer(stream);
callback(stream);
if (enableCamera && !enableScreen) {
openVideoPreview(stream);
}
}).catch(function(error) {
setDefaults();
chrome.tabs.create({
url: 'camera-mic.html'
});
});
}

134
dropdown.html Executable file
View File

@ -0,0 +1,134 @@
 <!DOCTYPE html>
<html>
<head>
<style>
* {
-webkit-user-select: none;
-user-select: none;
-webkit-user-drag: none;
-user-drag: none;
}
@font-face {
font-family: 'Custom_Font';
src: local('.SFNSText-Light'),
local('.HelveticaNeueDeskInterface-Light'),
local('.LucidaGrandeUI'),
local('Ubuntu Light'),
local('Segoe UI Light'),
local('Roboto-Light'),
local('DroidSans'),
local('Tahoma');
}
* {
margin: 0;
padding: 0;
}
html, body {
width: 400px;
background-color: #F3F3F3;
font-family: Custom_Font!important;
font-size: 1em;
overflow: hidden;
}
hr {
border: 0;
border-top: solid lightgray 1px;
}
div {
padding: 4px 8px;
}
div.btn {
text-decoration: none;
outline: none;
border: 0;
font-size: inherit;
cursor: pointer;
}
div.btn:hover {
background: rgba(234,234,234,1);
text-decoration: none;
}
a {
outline: none;
text-decoration: none;
color: #0C97BB;
}
div.btn img {
height: 32px;
}
section {
margin-right: 15px;
text-align: right;
width: 24px;
display: inline-block;
vertical-align: middle;
}
</style>
</head>
<body>
<article id="default-section">
<div class="btn" id="full-screen">
<section><img src="images/desktopCapture22.png"></section>
Screen Without Audio
</div>
<hr>
<div class="btn" id="microphone-screen">
<section><img src="images/desktopCapture22.png"></section>
Screen + Microphone
</div>
<hr>
<div class="btn" id="full-screen-audio">
<section><img src="images/desktopCapture22.png"></section>
Screen + Speakers
</div>
<hr>
<div class="btn" id="full-screen-audio-microphone">
<section><img src="images/desktopCapture22.png"></section>
Screen + Microphone + Speakers
</div>
<hr>
<div class="btn" id="full-screen-audio-microphone-camera">
<section><img src="images/desktopCapture22.png"></section>
Screen + Microphone + Speakers + Camera
</div>
<hr>
<div class="btn" id="selected-tab">
<section><img src="images/desktopCapture22.png"></section>
Chrome Tab + Speakers
</div>
<hr>
<div class="btn" id="microphone-screen-camera">
<section><img src="images/desktopCapture22.png"></section>
Screen + Camera
</div>
<hr>
<div class="btn" id="microphone-webcam">
<section><img src="images/desktopCapture22.png"></section>
Camera Only
</div>
<hr>
<div style="text-align: right;">
<a id="btn-options" href="options.html" target="_blank">Options</a>
</div>
</article>
<article id="stop-section">
<div class="btn" id="stop-sharing">
<section><img src="images/desktopCapture22.png"></section>
Stop Sharing
</div>
</article>
<script src="dropdown.js"></script>
</body>
</html>

175
dropdown.js Executable file
View File

@ -0,0 +1,175 @@
var runtimePort = chrome.runtime.connect({
name: location.href.replace(/\/|:|#|\?|\$|\^|%|\.|`|~|!|\+|@|\[|\||]|\|*. /g, '').split('\n').join('').split('\r').join('')
});
runtimePort.onMessage.addListener(function(message) {
if (!message || !message.messageFromContentScript1234) {
return;
}
});
document.getElementById('stop-sharing').onclick = function() {
chrome.storage.sync.set({
isSharingOn: 'false' // FALSE
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
stopSharing: true
});
window.close();
});
};
document.getElementById('full-screen').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'false',
enableCamera: 'false',
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'false' // FALSE
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('full-screen-audio').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'false',
enableCamera: 'false',
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'true' // TRUE
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('full-screen-audio-microphone').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'true', // TRUE
enableCamera: 'false',
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'true' // TRUE
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('full-screen-audio-microphone-camera').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'true', // TRUE
enableCamera: 'true',
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'true' // TRUE
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('selected-tab').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'true', // TRUE
enableMicrophone: 'false',
enableCamera: 'false',
enableScreen: 'false',
isSharingOn: 'true', // TRUE
enableSpeakers: 'true'
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('microphone-screen').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'true', // TRUE
enableCamera: 'false',
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'false'
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('microphone-screen-camera').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'true', // TRUE
enableCamera: 'true', // TRUE
enableScreen: 'true', // TRUE
isSharingOn: 'true', // TRUE
enableSpeakers: 'false'
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('microphone-webcam').onclick = function() {
chrome.storage.sync.set({
enableTabCaptureAPI: 'false',
enableMicrophone: 'true', // TRUE
enableCamera: 'true', // TRUE
enableScreen: 'false', // FALSE
isSharingOn: 'true', // TRUE
enableSpeakers: 'false'
}, function() {
runtimePort.postMessage({
messageFromContentScript1234: true,
startSharing: true
});
window.close();
});
};
document.getElementById('btn-options').onclick = function(e) {
e.preventDefault();
location.href = this.href;
};
var isSharingOn = false;
chrome.storage.sync.get('isSharingOn', function(obj) {
document.getElementById('default-section').style.display = obj.isSharingOn === 'true' ? 'none' : 'block';
document.getElementById('stop-section').style.display = obj.isSharingOn === 'true' ? 'block' : 'none';
isSharingOn = obj.isSharingOn === 'true';
// auto-stop-sharing
if (isSharingOn === true) {
document.getElementById('stop-sharing').click();
}
});

567
getStats.js Executable file
View File

@ -0,0 +1,567 @@
'use strict';
// Last time updated: 2017-11-19 4:49:44 AM UTC
// _______________
// getStats v1.0.6
// Open-Sourced: https://github.com/muaz-khan/getStats
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
window.getStats = function(mediaStreamTrack, callback, interval) {
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (typeof MediaStreamTrack === 'undefined') {
MediaStreamTrack = {}; // todo?
}
var systemNetworkType = ((navigator.connection || {}).type || 'unknown').toString().toLowerCase();
var getStatsResult = {
encryption: 'sha-256',
audio: {
send: {
tracks: [],
codecs: [],
availableBandwidth: 0,
streams: 0
},
recv: {
tracks: [],
codecs: [],
availableBandwidth: 0,
streams: 0
},
bytesSent: 0,
bytesReceived: 0
},
video: {
send: {
tracks: [],
codecs: [],
availableBandwidth: 0,
streams: 0
},
recv: {
tracks: [],
codecs: [],
availableBandwidth: 0,
streams: 0
},
bytesSent: 0,
bytesReceived: 0
},
bandwidth: {
systemBandwidth: 0,
sentPerSecond: 0,
encodedPerSecond: 0,
helper: {
audioBytesSent: 0,
videoBytestSent: 0
},
speed: 0
},
results: {},
connectionType: {
systemNetworkType: systemNetworkType,
systemIpAddress: '192.168.1.2',
local: {
candidateType: [],
transport: [],
ipAddress: [],
networkType: []
},
remote: {
candidateType: [],
transport: [],
ipAddress: [],
networkType: []
}
},
resolutions: {
send: {
width: 0,
height: 0
},
recv: {
width: 0,
height: 0
}
},
internal: {
audio: {
send: {},
recv: {}
},
video: {
send: {},
recv: {}
},
candidates: {}
},
nomore: function() {
nomore = true;
}
};
var getStatsParser = {
checkIfOfferer: function(result) {
if (result.type === 'googLibjingleSession') {
getStatsResult.isOfferer = result.googInitiator;
}
}
};
var peer = this;
if (arguments[0] instanceof RTCPeerConnection) {
peer = arguments[0];
if (!!navigator.mozGetUserMedia) {
mediaStreamTrack = arguments[1];
callback = arguments[2];
interval = arguments[3];
}
if (!(mediaStreamTrack instanceof MediaStreamTrack) && !!navigator.mozGetUserMedia) {
throw '2nd argument is not instance of MediaStreamTrack.';
}
} else if (!(mediaStreamTrack instanceof MediaStreamTrack) && !!navigator.mozGetUserMedia) {
throw '1st argument is not instance of MediaStreamTrack.';
}
var nomore = false;
function getStatsLooper() {
getStatsWrapper(function(results) {
results.forEach(function(result) {
Object.keys(getStatsParser).forEach(function(key) {
if (typeof getStatsParser[key] === 'function') {
getStatsParser[key](result);
}
});
if (result.type !== 'local-candidate' && result.type !== 'remote-candidate' && result.type !== 'candidate-pair') {
// console.error('result', result);
}
});
try {
// failed|closed
if (peer.iceConnectionState.search(/failed/gi) !== -1) {
nomore = true;
}
} catch (e) {
nomore = true;
}
if (nomore === true) {
if (getStatsResult.datachannel) {
getStatsResult.datachannel.state = 'close';
}
getStatsResult.ended = true;
}
// allow users to access native results
getStatsResult.results = results;
if (getStatsResult.audio && getStatsResult.video) {
getStatsResult.bandwidth.speed = (getStatsResult.audio.bytesSent - getStatsResult.bandwidth.helper.audioBytesSent) + (getStatsResult.video.bytesSent - getStatsResult.bandwidth.helper.videoBytesSent);
getStatsResult.bandwidth.helper.audioBytesSent = getStatsResult.audio.bytesSent;
getStatsResult.bandwidth.helper.videoBytesSent = getStatsResult.video.bytesSent;
}
callback(getStatsResult);
// second argument checks to see, if target-user is still connected.
if (!nomore) {
typeof interval != undefined && interval && setTimeout(getStatsLooper, interval || 1000);
}
});
}
// a wrapper around getStats which hides the differences (where possible)
// following code-snippet is taken from somewhere on the github
function getStatsWrapper(cb) {
// if !peer or peer.signalingState == 'closed' then return;
if (typeof window.InstallTrigger !== 'undefined') {
peer.getStats(
mediaStreamTrack,
function(res) {
var items = [];
res.forEach(function(r) {
items.push(r);
});
cb(items);
},
cb
);
} else {
peer.getStats(function(res) {
var items = [];
res.result().forEach(function(res) {
var item = {};
res.names().forEach(function(name) {
item[name] = res.stat(name);
});
item.id = res.id;
item.type = res.type;
item.timestamp = res.timestamp;
items.push(item);
});
cb(items);
});
}
};
getStatsParser.datachannel = function(result) {
if (result.type !== 'datachannel') return;
getStatsResult.datachannel = {
state: result.state // open or connecting
}
};
getStatsParser.googCertificate = function(result) {
if (result.type == 'googCertificate') {
getStatsResult.encryption = result.googFingerprintAlgorithm;
}
};
var AUDIO_codecs = ['opus', 'isac', 'ilbc'];
getStatsParser.checkAudioTracks = function(result) {
if (!result.googCodecName || result.mediaType !== 'audio') return;
if (AUDIO_codecs.indexOf(result.googCodecName.toLowerCase()) === -1) return;
var sendrecvType = result.id.split('_').pop();
if (getStatsResult.audio[sendrecvType].codecs.indexOf(result.googCodecName) === -1) {
getStatsResult.audio[sendrecvType].codecs.push(result.googCodecName);
}
if (result.bytesSent) {
var kilobytes = 0;
if (!!result.bytesSent) {
if (!getStatsResult.internal.audio[sendrecvType].prevBytesSent) {
getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent;
}
var bytes = result.bytesSent - getStatsResult.internal.audio[sendrecvType].prevBytesSent;
getStatsResult.internal.audio[sendrecvType].prevBytesSent = result.bytesSent;
kilobytes = bytes / 1024;
}
getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
}
if (result.bytesReceived) {
var kilobytes = 0;
if (!!result.bytesReceived) {
if (!getStatsResult.internal.audio[sendrecvType].prevBytesReceived) {
getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived;
}
var bytes = result.bytesReceived - getStatsResult.internal.audio[sendrecvType].prevBytesReceived;
getStatsResult.internal.audio[sendrecvType].prevBytesReceived = result.bytesReceived;
kilobytes = bytes / 1024;
}
getStatsResult.audio[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
}
if (getStatsResult.audio[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
getStatsResult.audio[sendrecvType].tracks.push(result.googTrackId);
}
};
var VIDEO_codecs = ['vp9', 'vp8', 'h264'];
getStatsParser.checkVideoTracks = function(result) {
if (!result.googCodecName || result.mediaType !== 'video') return;
if (VIDEO_codecs.indexOf(result.googCodecName.toLowerCase()) === -1) return;
// googCurrentDelayMs, googRenderDelayMs, googTargetDelayMs
// transportId === 'Channel-audio-1'
var sendrecvType = result.id.split('_').pop();
if (getStatsResult.video[sendrecvType].codecs.indexOf(result.googCodecName) === -1) {
getStatsResult.video[sendrecvType].codecs.push(result.googCodecName);
}
if (!!result.bytesSent) {
var kilobytes = 0;
if (!getStatsResult.internal.video[sendrecvType].prevBytesSent) {
getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;
}
var bytes = result.bytesSent - getStatsResult.internal.video[sendrecvType].prevBytesSent;
getStatsResult.internal.video[sendrecvType].prevBytesSent = result.bytesSent;
kilobytes = bytes / 1024;
}
if (!!result.bytesReceived) {
var kilobytes = 0;
if (!getStatsResult.internal.video[sendrecvType].prevBytesReceived) {
getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;
}
var bytes = result.bytesReceived - getStatsResult.internal.video[sendrecvType].prevBytesReceived;
getStatsResult.internal.video[sendrecvType].prevBytesReceived = result.bytesReceived;
kilobytes = bytes / 1024;
}
getStatsResult.video[sendrecvType].availableBandwidth = kilobytes.toFixed(1);
if (result.googFrameHeightReceived && result.googFrameWidthReceived) {
getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthReceived;
getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightReceived;
}
if (result.googFrameHeightSent && result.googFrameWidthSent) {
getStatsResult.resolutions[sendrecvType].width = result.googFrameWidthSent;
getStatsResult.resolutions[sendrecvType].height = result.googFrameHeightSent;
}
if (getStatsResult.video[sendrecvType].tracks.indexOf(result.googTrackId) === -1) {
getStatsResult.video[sendrecvType].tracks.push(result.googTrackId);
}
};
getStatsParser.bweforvideo = function(result) {
if (result.type !== 'VideoBwe') return;
getStatsResult.bandwidth.availableSendBandwidth = result.googAvailableSendBandwidth;
getStatsResult.bandwidth.googActualEncBitrate = result.googActualEncBitrate;
getStatsResult.bandwidth.googAvailableSendBandwidth = result.googAvailableSendBandwidth;
getStatsResult.bandwidth.googAvailableReceiveBandwidth = result.googAvailableReceiveBandwidth;
getStatsResult.bandwidth.googRetransmitBitrate = result.googRetransmitBitrate;
getStatsResult.bandwidth.googTargetEncBitrate = result.googTargetEncBitrate;
getStatsResult.bandwidth.googBucketDelay = result.googBucketDelay;
getStatsResult.bandwidth.googTransmitBitrate = result.googTransmitBitrate;
};
getStatsParser.candidatePair = function(result) {
if (result.type !== 'googCandidatePair' && result.type !== 'candidate-pair') return;
// result.googActiveConnection means either STUN or TURN is used.
if (result.googActiveConnection == 'true') {
// id === 'Conn-audio-1-0'
// localCandidateId, remoteCandidateId
// bytesSent, bytesReceived
Object.keys(getStatsResult.internal.candidates).forEach(function(cid) {
var candidate = getStatsResult.internal.candidates[cid];
if (candidate.ipAddress.indexOf(result.googLocalAddress) !== -1) {
getStatsResult.connectionType.local.candidateType = candidate.candidateType;
getStatsResult.connectionType.local.ipAddress = candidate.ipAddress;
getStatsResult.connectionType.local.networkType = candidate.networkType;
getStatsResult.connectionType.local.transport = candidate.transport;
}
if (candidate.ipAddress.indexOf(result.googRemoteAddress) !== -1) {
getStatsResult.connectionType.remote.candidateType = candidate.candidateType;
getStatsResult.connectionType.remote.ipAddress = candidate.ipAddress;
getStatsResult.connectionType.remote.networkType = candidate.networkType;
getStatsResult.connectionType.remote.transport = candidate.transport;
}
});
getStatsResult.connectionType.transport = result.googTransportType;
var localCandidate = getStatsResult.internal.candidates[result.localCandidateId];
if (localCandidate) {
if (localCandidate.ipAddress) {
getStatsResult.connectionType.systemIpAddress = localCandidate.ipAddress;
}
}
var remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];
if (remoteCandidate) {
if (remoteCandidate.ipAddress) {
getStatsResult.connectionType.systemIpAddress = remoteCandidate.ipAddress;
}
}
}
if (result.type === 'candidate-pair') {
if (result.selected === true && result.nominated === true && result.state === 'succeeded') {
// remoteCandidateId, localCandidateId, componentId
var localCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];
var remoteCandidate = getStatsResult.internal.candidates[result.remoteCandidateId];
// Firefox used above two pairs for connection
}
}
};
var LOCAL_candidateType = {};
var LOCAL_transport = {};
var LOCAL_ipAddress = {};
var LOCAL_networkType = {};
getStatsParser.localcandidate = function(result) {
if (result.type !== 'localcandidate' && result.type !== 'local-candidate') return;
if (!result.id) return;
if (!LOCAL_candidateType[result.id]) {
LOCAL_candidateType[result.id] = [];
}
if (!LOCAL_transport[result.id]) {
LOCAL_transport[result.id] = [];
}
if (!LOCAL_ipAddress[result.id]) {
LOCAL_ipAddress[result.id] = [];
}
if (!LOCAL_networkType[result.id]) {
LOCAL_networkType[result.id] = [];
}
if (result.candidateType && LOCAL_candidateType[result.id].indexOf(result.candidateType) === -1) {
LOCAL_candidateType[result.id].push(result.candidateType);
}
if (result.transport && LOCAL_transport[result.id].indexOf(result.transport) === -1) {
LOCAL_transport[result.id].push(result.transport);
}
if (result.ipAddress && LOCAL_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
LOCAL_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber);
}
if (result.networkType && LOCAL_networkType[result.id].indexOf(result.networkType) === -1) {
LOCAL_networkType[result.id].push(result.networkType);
}
getStatsResult.internal.candidates[result.id] = {
candidateType: LOCAL_candidateType[result.id],
ipAddress: LOCAL_ipAddress[result.id],
portNumber: result.portNumber,
networkType: LOCAL_networkType[result.id],
priority: result.priority,
transport: LOCAL_transport[result.id],
timestamp: result.timestamp,
id: result.id,
type: result.type
};
getStatsResult.connectionType.local.candidateType = LOCAL_candidateType[result.id];
getStatsResult.connectionType.local.ipAddress = LOCAL_ipAddress[result.id];
getStatsResult.connectionType.local.networkType = LOCAL_networkType[result.id];
getStatsResult.connectionType.local.transport = LOCAL_transport[result.id];
};
var REMOTE_candidateType = {};
var REMOTE_transport = {};
var REMOTE_ipAddress = {};
var REMOTE_networkType = {};
getStatsParser.remotecandidate = function(result) {
if (result.type !== 'remotecandidate' && result.type !== 'remote-candidate') return;
if (!result.id) return;
if (!REMOTE_candidateType[result.id]) {
REMOTE_candidateType[result.id] = [];
}
if (!REMOTE_transport[result.id]) {
REMOTE_transport[result.id] = [];
}
if (!REMOTE_ipAddress[result.id]) {
REMOTE_ipAddress[result.id] = [];
}
if (!REMOTE_networkType[result.id]) {
REMOTE_networkType[result.id] = [];
}
if (result.candidateType && REMOTE_candidateType[result.id].indexOf(result.candidateType) === -1) {
REMOTE_candidateType[result.id].push(result.candidateType);
}
if (result.transport && REMOTE_transport[result.id].indexOf(result.transport) === -1) {
REMOTE_transport[result.id].push(result.transport);
}
if (result.ipAddress && REMOTE_ipAddress[result.id].indexOf(result.ipAddress + ':' + result.portNumber) === -1) {
REMOTE_ipAddress[result.id].push(result.ipAddress + ':' + result.portNumber);
}
if (result.networkType && REMOTE_networkType[result.id].indexOf(result.networkType) === -1) {
REMOTE_networkType[result.id].push(result.networkType);
}
getStatsResult.internal.candidates[result.id] = {
candidateType: REMOTE_candidateType[result.id],
ipAddress: REMOTE_ipAddress[result.id],
portNumber: result.portNumber,
networkType: REMOTE_networkType[result.id],
priority: result.priority,
transport: REMOTE_transport[result.id],
timestamp: result.timestamp,
id: result.id,
type: result.type
};
getStatsResult.connectionType.remote.candidateType = REMOTE_candidateType[result.id];
getStatsResult.connectionType.remote.ipAddress = REMOTE_ipAddress[result.id];
getStatsResult.connectionType.remote.networkType = REMOTE_networkType[result.id];
getStatsResult.connectionType.remote.transport = REMOTE_transport[result.id];
};
getStatsParser.dataSentReceived = function(result) {
if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return;
if (!!result.bytesSent) {
getStatsResult[result.mediaType].bytesSent = parseInt(result.bytesSent);
}
if (!!result.bytesReceived) {
getStatsResult[result.mediaType].bytesReceived = parseInt(result.bytesReceived);
}
};
var SSRC = {
audio: {
send: [],
recv: []
},
video: {
send: [],
recv: []
}
};
getStatsParser.ssrc = function(result) {
if (!result.googCodecName || (result.mediaType !== 'video' && result.mediaType !== 'audio')) return;
if (result.type !== 'ssrc') return;
var sendrecvType = result.id.split('_').pop();
if (SSRC[result.mediaType][sendrecvType].indexOf(result.ssrc) === -1) {
SSRC[result.mediaType][sendrecvType].push(result.ssrc)
}
getStatsResult[result.mediaType][sendrecvType].streams = SSRC[result.mediaType][sendrecvType].length;
};
getStatsLooper();
};

BIN
images/desktopCapture128.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
images/desktopCapture16.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
images/desktopCapture22.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

BIN
images/desktopCapture32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

BIN
images/desktopCapture48.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

BIN
images/pause22.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

511
index.html Executable file
View File

@ -0,0 +1,511 @@

<title>WebRTC Desktop Viewer</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="description" content="This WebRTC Experiment page shows privately shared screens, desktops, and parts of the screens." />
<meta name="keywords" content="WebRTC,Desktop-Sharing,Screen-Sharing,RTCWeb,WebRTC-Experiment,WebRTC-Demo" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="author" type="text/html" href="https://plus.google.com/+MuazKhan">
<meta name="author" content="Muaz Khan">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script src="RTCMultiConnection.js"> </script>
<script src="CodecsHandler.js"></script>
<script src="IceServersHandler.js"></script>
<script src="getStats.js"></script>
<script src="websocket.js"> </script>
<style>
body,
html {
background: black;
text-align: center;
color: white;
overflow: hidden;
}
.local-media,
.remote-media {
max-width: 100%;
max-height: 70%;
}
.local-media-small,
.remote-media-small {
width: 20%;
position: fixed;
bottom: 0;
left: 0;
}
button {
display: inline-block;
outline: 0;
color: white;
background: #4472b9;
white-space: nowrap;
border: 5px solid #4472b9 !important;
font-family: 'Gotham Rounded A', 'Gotham Rounded B', sans-serif;
font-weight: 500;
font-style: normal;
padding: 9px 16px !important;
line-height: 1.4;
position: relative;
border-radius: 10px;
-webkit-box-shadow: 5px 5px 0 0 rgba(0, 0, 0, 0.15);
box-shadow: 5px 5px 0 0 rgba(0, 0, 0, 0.15);
-webkit-transition: 0.1s;
transition: 0.1s;
}
button:hover,
button:active,
button:focus {
background: #04C;
}
button[disabled] {
background: transparent;
border-color: rgb(83, 81, 81);
color: rgb(139, 133, 133);
}
#container {
-webkit-perspective: 1000;
background-color: #000000;
height: 100%;
margin: 0px auto;
position: absolute;
width: 100%;
}
#card {
-webkit-transform-style: preserve-3d;
-webkit-transition-duration: 2s;
-webkit-transition-property: rotation;
}
#local {
-webkit-backface-visibility: hidden;
-webkit-transform: scale(-1, 1);
position: absolute;
width: 100%;
}
#remote {
-webkit-backface-visibility: hidden;
-webkit-transform: rotateY(180deg);
position: absolute;
width: 100%;
}
#mini {
/* -webkit-transform: scale(-1, 1); */
bottom: 0;
height: 30%;
opacity: 1.0;
position: absolute;
right: 4px;
width: 30%;
}
#remoteVideo {
-webkit-transition-duration: 2s;
-webkit-transition-property: opacity;
height: 100%;
opacity: 0;
width: 100%;
}
#info-bar {
background-color: #15DBFF;
bottom: 55%;
color: rgb(255, 255, 255);
font-size: 25px;
font-weight: bold;
height: 38px;
line-height: 38px;
position: absolute;
text-align: center;
width: 100%;
text-shadow: 1px 1px rgb(14, 105, 137);
border: 2px solid rgb(47, 102, 118);
box-shadow: 0 0 6px white;
}
#stats-bar {
background-color: rgba(255, 255, 255, 0.92);
top: 20px;
left: 20px;
color: black;
font-size: 17px;
line-height: 1.5em;
position: absolute;
border: 2px solid rgba(0, 0, 0, 0.82);
border-radius: 7px;
font-family: Arial;
text-align: left;
display: none;
}
#stats-bar-html {
padding: 5px 10px;
}
#hide-stats-bar {
float: right;
cursor: pointer;
color: red;
font-size: 20px;
font-weight: bold;
margin-right: 8px;
}
#hide-stats-bar:hover, #hide-stats-bar:active {
color: #6c1414;
}
</style>
<div id="container" ondblclick="enterFullScreen()">
<div id="card">
<div id="remote">
<video id="remoteVideo" autoplay playsinline></video>
</div>
</div>
<div id="info-bar"></div>
<div id="stats-bar">
<div id="hide-stats-bar">x</div>
<div id="stats-bar-html"></div>
</div>
</div>
<script>
(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;
})();
// http://www.rtcmulticonnection.org/docs/constructor/
var connection = new RTCMultiConnection(params.s);
// www.rtcmulticonnection.org/docs/sdpConstraints/
connection.sdpConstraints.mandatory = {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
};
connection.getExternalIceServers = false;
connection.iceServers = IceServersHandler.getIceServers();
function setBandwidth(sdp) {
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:10000\r\n');
return sdp;
}
connection.processSdp = function(sdp) {
return sdp;
sdp = setBandwidth(sdp);
sdp = BandwidthHandler.setVideoBitrates(sdp, {
min: 300,
max: 10000
});
// sdp = CodecsHandler.preferVP9(sdp);
return sdp;
};
connection.optionalArgument = {
optional: [],
mandatory: {}
};
</script>
<script>
// DOM objects
var remoteVideo = document.getElementById('remoteVideo');
var card = document.getElementById('card');
var containerDiv;
if (navigator.mozGetUserMedia) {
attachMediaStream = function(element, stream) {
console.log("Attaching media stream");
element.mozSrcObject = stream;
element.play();
};
reattachMediaStream = function(to, from) {
console.log("Reattaching media stream");
to.mozSrcObject = from.mozSrcObject;
to.play();
};
} else if (navigator.webkitGetUserMedia) {
attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream);
} else {
console.log('Error attaching stream to element.');
}
};
reattachMediaStream = function(to, from) {
to.src = from.src;
};
} else {
console.log("Browser does not appear to be WebRTC-capable");
}
// onstream event; fired both for local and remote videos
var infoBar = document.getElementById('info-bar');
connection.onstatechange = function(state) {
infoBar.innerHTML = state.name + ': ' + state.reason;
if(state.name == 'request-rejected' && params.p) {
infoBar.innerHTML = 'Password (' + params.p + ') did not match with broadcaster, that is why your participation request has been rejected.<br>Please contact him and ask for valid password.';
}
if(state.name === 'room-not-available') {
infoBar.innerHTML = 'Screen share session is closed or paused. You will join automatically when share session is resumed.';
}
};
connection.onstreamid = function(event) {
infoBar.innerHTML = 'Remote peer is about to send his screen.';
};
connection.onstream = function(e) {
if (e.type == 'remote') {
connection.remoteStream = e.stream;
infoBar.style.display = 'none';
remoteStream = e.stream;
attachMediaStream(remoteVideo, e.stream);
waitForRemoteVideo();
remoteVideo.setAttribute('data-id', e.userid);
websocket.send('received-your-screen');
}
};
// if user left
connection.onleave = function(e) {
transitionToWaiting();
connection.onSessionClosed();
};
connection.onSessionClosed = function() {
infoBar.innerHTML = 'Screen sharing has been closed.';
infoBar.style.display = 'block';
statsBar.style.display = 'none';
connection.close();
websocket.onopen();
remoteVideo.pause();
remoteVideo.src = 'https://cdn.webrtc-experiment.com/images/muted.png';
};
connection.ondisconnected = connection.onSessionClosed;
connection.onstreamended = connection.onSessionClosed;
function waitForRemoteVideo() {
// Call the getVideoTracks method via adapter.js.
var videoTracks = remoteStream.getVideoTracks();
if (videoTracks.length === 0 || remoteVideo.currentTime > 0) {
transitionToActive();
} else {
setTimeout(waitForRemoteVideo, 100);
}
}
function transitionToActive() {
remoteVideo.style.opacity = 1;
card.style.webkitTransform = 'rotateY(180deg)';
window.onresize();
}
function transitionToWaiting() {
card.style.webkitTransform = 'rotateY(0deg)';
remoteVideo.style.opacity = 0;
}
// Set the video displaying in the center of window.
window.onresize = function() {
var aspectRatio;
if (remoteVideo.style.opacity === '1') {
aspectRatio = remoteVideo.videoWidth / remoteVideo.videoHeight;
} else {
return;
}
var innerHeight = this.innerHeight;
var innerWidth = this.innerWidth;
var videoWidth = innerWidth < aspectRatio * window.innerHeight ?
innerWidth : aspectRatio * window.innerHeight;
var videoHeight = innerHeight < window.innerWidth / aspectRatio ?
innerHeight : window.innerWidth / aspectRatio;
containerDiv = document.getElementById('container');
containerDiv.style.width = videoWidth + 'px';
containerDiv.style.height = videoHeight + 'px';
containerDiv.style.left = (innerWidth - videoWidth) / 2 + 'px';
containerDiv.style.top = (innerHeight - videoHeight) / 2 + 'px';
};
function enterFullScreen() {
container.webkitRequestFullScreen();
}
</script>
<script>
// using websockets as signaling medium
// http://www.rtcmulticonnection.org/docs/openSignalingChannel/
// using websockets for signaling
// www.RTCMultiConnection.org/docs/openSignalingChannel/
var onMessageCallbacks = {};
var pub = 'pub-c-3c0fc243-9892-4858-aa38-1445e58b4ecb';
var sub = 'sub-c-d0c386c6-7263-11e2-8b02-12313f022c90';
WebSocket = PUBNUB.ws;
var websocket = new WebSocket('wss://pubsub.pubnub.com/' + pub + '/' + sub + '/' + connection.channel);
websocket.onmessage = function(e) {
data = JSON.parse(e.data);
if (data.sender == connection.userid) return;
if (onMessageCallbacks[data.channel]) {
onMessageCallbacks[data.channel](data.message);
};
};
websocket.push = websocket.send;
websocket.send = function(data) {
data.sender = connection.userid;
websocket.push(JSON.stringify(data));
};
// overriding "openSignalingChannel" method
connection.openSignalingChannel = function(config) {
var channel = config.channel || this.channel;
onMessageCallbacks[channel] = config.onmessage;
if (config.onopen) setTimeout(config.onopen, 1000);
// directly returning socket object using "return" statement
return {
send: function(message) {
websocket.send({
sender: connection.userid,
channel: channel,
message: message
});
},
channel: channel
};
};
websocket.onerror = function() {
if(connection.numberOfConnectedUsers <= 0) {
location.reload();
}
};
websocket.onclose = function() {
if(connection.numberOfConnectedUsers <= 0) {
location.reload();
}
};
infoBar.innerHTML = 'Connecting WebSockets server.';
websocket.onopen = function() {
infoBar.innerHTML = 'WebSockets connection is opened.';
var sessionDescription = {
userid: params.s,
extra: {},
session: {
video: true,
oneway: true
},
sessionid: params.s
};
if (params.s) {
infoBar.innerHTML = 'Joining session: ' + params.s;
if(params.p) {
// it seems a password protected room.
connection.extra.password = params.p;
}
// http://www.rtcmulticonnection.org/docs/join/
connection.join(sessionDescription);
}
};
var dontDuplicate = {};
connection.onconnected = function(event) {
if(dontDuplicate[event.userid]) return;
dontDuplicate[event.userid] = true;
var peer = connection.peers[event.userid].peer.connection;
if(DetectRTC.browser.name === 'Firefox') {
getStats(peer, (connection.remoteStream || peer.getRemoteStreams()[0]).getTracks()[0], function(stats) {
onGettingWebRCStats(stats, event.userid);
}, 1000);
return;
}
getStats(peer, function(stats) {
onGettingWebRCStats(stats, event.userid);
}, 1000);
statsBar.style.display = 'block';
};
var statsBar = document.getElementById('stats-bar');
var statsBarHTML = document.getElementById('stats-bar-html');
var NO_MORE = false;
document.getElementById('hide-stats-bar').onclick = function() {
statsBar.style.display = 'none';
NO_MORE = true;
};
function onGettingWebRCStats(stats, userid) {
if(!connection.peers[userid] || NO_MORE) {
stats.nomore();
return;
}
var html = 'Codecs: ' + stats.audio.recv.codecs.concat(stats.video.recv.codecs).join(', ');
html += '<br>';
html += 'Resolutions: ' + stats.resolutions.recv.width + 'x' + stats.resolutions.recv.height;
html += '<br>';
html += 'Data: ' + bytesToSize(stats.audio.bytesReceived + stats.video.bytesReceived);
// html += '<br>';
// html += 'Speed: ' + bytesToSize(stats.bandwidth.speed || 0);
statsBarHTML.innerHTML = html;
}
function bytesToSize(bytes) {
var k = 1000;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) {
return '0 Bytes';
}
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10);
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
}
window.addEventListener('offline', function() {
infoBar.innerHTML = 'You seems offLine.';
}, false);
window.addEventListener('online', function() {
infoBar.innerHTML = 'You seems onLine. Reloading the page..';
location.reload();
}, false);
</script>

47
manifest.json Executable file
View File

@ -0,0 +1,47 @@
{
"name":"WebRTC Desktop Sharing",
"author":"Muaz Khan",
"version":"3.9",
"manifest_version":2,
"minimum_chrome_version":"34",
"description":"WebRTC based P2P HQ/HD screen sharing. Share audio+tab or any application's screen. Even share full/entire screen.",
"homepage_url":"https://github.com/muaz-khan/Chrome-Extensions/tree/master/desktopCapture-p2p",
"background":{
"scripts":[
"socket.io.js",
"RTCMultiConnection.js",
"CodecsHandler.js",
"IceServersHandler.js",
"MultiStreamsMixer.js",
"desktop-capturing.js"
],
"persistent":false
},
"browser_action":{
"default_icon":"images/desktopCapture22.png",
"default_title":"Share Your Screen",
"default_popup": "dropdown.html"
},
"icons":{
"16":"images/desktopCapture16.png",
"22":"images/desktopCapture22.png",
"32":"images/desktopCapture32.png",
"48":"images/desktopCapture48.png",
"128":"images/desktopCapture128.png"
},
"permissions":[
"desktopCapture",
"storage",
"tabs",
"<all_urls>",
"activeTab",
"tabCapture"
],
"web_accessible_resources":[
"images/desktopCapture48.png"
],
"options_ui":{
"page":"options.html",
"chrome_style":true
}
}

73
options.html Executable file
View File

@ -0,0 +1,73 @@
<style>
body {
min-width: 450px;
}
h2 {
font-size: 1.5em;
font-weight: bold;
}
select {
font-size: 1.2em;
border: 1px solid;
padding: 0;
outline: none!important;
border: 1px solid black !important;
}
select option {
padding: 2px 5px;
border-bottom: 1px solid black;
}
select option:last-child {
border-bottom: 0;
}
label {
font-size: 1.2em;
font-weight: bold;
}
label, input {
vertical-align: middle;
}
select[disabled] {
background-color: rgba(232, 229, 229, 0.17);
color: rgb(84, 82, 82);
}
</style>
<h2>Select Screen Resolutions:</h2>
<select id="resolutions" size="5">
<option value="fit-screen" selected>Fit Screen</option>
<option value="4K">4K (2160p)</option>
<option value="1080p">Full-HD (1080p)</option>
<option value="720p">HD (720p)</option>
<option value="360p">SD (360p)</option>
</select>
<hr>
<h2>Select Codecs:</h2>
<select id="codecs" size="4">
<option value="default" selected>Default</option>
<option value="vp8">VP8</option>
<option value="vp9">VP9</option>
<option value="h264">H264</option>
</select>
<hr>
<label for="bandwidth">Set Bandwidth (e.g. 8192)</label>
<input type="text" id="bandwidth" value="">
<hr>
<label for="room_password">Set Room Password</label>
<input type="password" id="room_password" value="">
<br>
<small>Keep empty for No password.</small>
<hr>
<label for="room_id">Set ReUsable Room ID</label>
<input type="text" id="room_id" value="">
<br>
<small>Set Your Own Room-ID. Keep empty for random room-id.</small>
<script src="options.js"></script>

83
options.js Executable file
View File

@ -0,0 +1,83 @@
chrome.storage.sync.get(null, function(items) {
if (items['resolutions']) {
document.getElementById('resolutions').value = items['resolutions'];
} else {
chrome.storage.sync.set({
resolutions: 'fit-screen'
}, function() {
document.getElementById('resolutions').value = 'fit-screen'
});
}
if (items['codecs']) {
document.getElementById('codecs').value = items['codecs'];
} else {
chrome.storage.sync.set({
codecs: 'default'
}, function() {
document.getElementById('codecs').value = 'default'
});
}
if (items['room_password']) {
document.getElementById('room_password').value = items['room_password'];
}
if (items['bandwidth']) {
document.getElementById('bandwidth').value = items['bandwidth'];
}
if (items['room_id']) {
document.getElementById('room_id').value = items['room_id'];
}
});
document.getElementById('resolutions').onchange = function() {
this.disabled = true;
chrome.storage.sync.set({
resolutions: this.value
}, function() {
document.getElementById('resolutions').disabled = false;
});
};
document.getElementById('codecs').onchange = function() {
this.disabled = true;
chrome.storage.sync.set({
codecs: this.value
}, function() {
document.getElementById('codecs').disabled = false;
});
};
document.getElementById('bandwidth').onblur = function() {
this.disabled = true;
chrome.storage.sync.set({
bandwidth: this.value
}, function() {
document.getElementById('bandwidth').disabled = false;
});
};
document.getElementById('room_password').onblur = function() {
this.disabled = true;
chrome.storage.sync.set({
room_password: this.value
}, function() {
document.getElementById('room_password').disabled = false;
});
};
document.getElementById('room_id').onblur = function() {
this.disabled = true;
chrome.storage.sync.set({
room_id: this.value
}, function() {
document.getElementById('room_id').disabled = false;
});
};

74
server.js Executable file
View File

@ -0,0 +1,74 @@
// http://127.0.0.1:9001
// http://localhost:9001
var server = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs');
function serverHandler(request, response) {
var uri = url.parse(request.url).pathname,
filename = path.join(process.cwd(), uri);
fs.exists(filename, function(exists) {
if (!exists) {
response.writeHead(404, {
'Content-Type': 'text/plain'
});
response.write('404 Not Found: ' + filename + '\n');
response.end();
return;
}
if (filename.indexOf('favicon.ico') !== -1) {
return;
}
var isWin = !!process.platform.match(/^win/);
if (fs.statSync(filename).isDirectory() && !isWin) {
filename += '/index.html';
} else if (fs.statSync(filename).isDirectory() && !!isWin) {
filename += '\\index.html';
}
fs.readFile(filename, 'binary', function(err, file) {
if (err) {
response.writeHead(500, {
'Content-Type': 'text/plain'
});
response.write(err + '\n');
response.end();
return;
}
var contentType;
if (filename.indexOf('.html') !== -1) {
contentType = 'text/html';
}
if (filename.indexOf('.js') !== -1) {
contentType = 'application/javascript';
}
if (contentType) {
response.writeHead(200, {
'Content-Type': contentType
});
} else response.writeHead(200);
response.write(file, 'binary');
response.end();
});
});
}
var app;
app = server.createServer(serverHandler);
app = app.listen(process.env.PORT || 9001, process.env.IP || "0.0.0.0", function() {
var addr = app.address();
console.log("Server listening at", addr.address + ":" + addr.port);
});

8
socket.io.js Executable file

File diff suppressed because one or more lines are too long

24
video.html Executable file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html dir="ltr" lang="en" style="overflow: hidden;padding: 0;margin: 0;">
<head>
<title>RecordRTC</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
h1 {
margin: 10px 0;
text-align: center;
}
video {
width: 100%;
}
</style>
</head>
<body style="padding: 0;margin: 0;overflow: hidden;">
<h1>Close this window to stop the broadcast!</h1>
<video autoplay playsinline muted></video>
<script src="video.js"></script>
</body>
</html>

2
video.js Executable file
View File

@ -0,0 +1,2 @@
var src = location.href.split('?src=')[1];
document.querySelector('video').src = src;

96
websocket.js Executable file
View File

@ -0,0 +1,96 @@
// Version: 3.6.7
(function(){
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new r.init(c,a)}}),l=f.enc={},k=l.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,
2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}},
u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);c.sigBytes-=b}return new r.init(g,b)},clone:function(){var a=m.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});g.Hasher=u.extend({cfg:m.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){u.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new t.HMAC.init(a,
d)).finalize(c)}}});var t=f.algo={};return f}(Math);
(function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]=
c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;
d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math);
(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j<h;j++)k[j]^=1549556828,n[j]^=909522486;r.sigBytes=l.sigBytes=m;this.reset()},reset:function(){var f=this._hasher;f.reset();f.update(this._iKey)},update:function(f){this._hasher.update(f);return this},finalize:function(f){var g=
this._hasher;f=g.finalize(f);g.reset();return g.finalize(this._oKey.clone().concat(f))}})})();
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
(function(){var h=CryptoJS,j=h.lib.WordArray;h.enc.Base64={stringify:function(b){var e=b.words,f=b.sigBytes,c=this._map;b.clamp();b=[];for(var a=0;a<f;a+=3)for(var d=(e[a>>>2]>>>24-8*(a%4)&255)<<16|(e[a+1>>>2]>>>24-8*((a+1)%4)&255)<<8|e[a+2>>>2]>>>24-8*((a+2)%4)&255,g=0;4>g&&a+0.75*g<f;g++)b.push(c.charAt(d>>>6*(3-g)&63));if(e=c.charAt(64))for(;b.length%4;)b.push(e);return b.join("")},parse:function(b){var e=b.length,f=this._map,c=f.charAt(64);c&&(c=b.indexOf(c),-1!=c&&(e=c));for(var c=[],a=0,d=0;d<
e;d++)if(d%4){var g=f.indexOf(b.charAt(d-1))<<2*(d%4),h=f.indexOf(b.charAt(d))>>>6-2*(d%4);c[a>>>2]|=(g|h)<<24-8*(a%4);a++}return j.create(c,a)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
var v=void 0,y=!0,z=null,A=!1;function C(){return function(){}}
window.JSON&&window.JSON.stringify||function(){function a(){try{return this.valueOf()}catch(a){return z}}function d(a){c.lastIndex=0;return c.test(a)?'"'+a.replace(c,function(a){var b=q[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function b(c,q){var t,r,g,j,h,l=f,e=q[c];e&&"object"===typeof e&&(e=a.call(e));"function"===typeof m&&(e=m.call(q,c,e));switch(typeof e){case "string":return d(e);case "number":return isFinite(e)?String(e):"null";case "boolean":case "null":return String(e);
case "object":if(!e)return"null";f+=p;h=[];if("[object Array]"===Object.prototype.toString.apply(e)){j=e.length;for(t=0;t<j;t+=1)h[t]=b(t,e)||"null";g=0===h.length?"[]":f?"[\n"+f+h.join(",\n"+f)+"\n"+l+"]":"["+h.join(",")+"]";f=l;return g}if(m&&"object"===typeof m){j=m.length;for(t=0;t<j;t+=1)r=m[t],"string"===typeof r&&(g=b(r,e))&&h.push(d(r)+(f?": ":":")+g)}else for(r in e)Object.hasOwnProperty.call(e,r)&&(g=b(r,e))&&h.push(d(r)+(f?": ":":")+g);g=0===h.length?"{}":f?"{\n"+f+h.join(",\n"+f)+"\n"+
l+"}":"{"+h.join(",")+"}";f=l;return g}}window.JSON||(window.JSON={});var c=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,f,p,q={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},m;"function"!==typeof JSON.stringify&&(JSON.stringify=function(a,c,d){var q;p=f="";if("number"===typeof d)for(q=0;q<d;q+=1)p+=" ";else"string"===typeof d&&(p=d);if((m=c)&&"function"!==typeof c&&("object"!==typeof c||"number"!==
typeof c.length))throw Error("JSON.stringify");return b("",{"":a})});"function"!==typeof JSON.parse&&(JSON.parse=function(a){return eval("("+a+")")})}();var ca=1,fa=A,ga=[],ha="-pnpres",D=1E3,ia="/",ja="&",ka=/{([\w\-]+)}/g;function la(){return"x"+ ++ca+""+ +new Date}function G(){return+new Date}var ma,na=Math.floor(20*Math.random());ma=function(a,d){return 0<a.indexOf("pubsub.")&&a.replace("pubsub","ps"+(d?pa().split("-")[0]:20>++na?na:na=1))||a};
function qa(a,d){var b=a.join(ia),c=[];if(!d)return b;N(d,function(a,b){var d="object"==typeof b?JSON.stringify(b):b;"undefined"!=typeof b&&(b!=z&&0<encodeURIComponent(d).length)&&c.push(a+"="+encodeURIComponent(d))});return b+="?"+c.join(ja)}function ra(a,d){function b(){f+d>G()?(clearTimeout(c),c=setTimeout(b,d)):(f=G(),a())}var c,f=0;return b}function sa(a,d){var b=[];N(a||[],function(a){d(a)&&b.push(a)});return b}function ta(a,d){return a.replace(ka,function(a,c){return d[c]||a})}
function pa(a){var d="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=16*Math.random()|0;return("x"==a?c:c&3|8).toString(16)});a&&a(d);return d}function N(a,d){if(a&&d)if(a&&(Array.isArray&&Array.isArray(a)||"number"===typeof a.length))for(var b=0,c=a.length;b<c;)d.call(a[b],a[b],b++);else for(b in a)a.hasOwnProperty&&a.hasOwnProperty(b)&&d.call(a[b],b,a[b])}function ua(a,d){var b=[];N(a||[],function(a,f){b.push(d(a,f))});return b}
function va(a,d){var b=[];N(a,function(a,f){d?0>a.search("-pnpres")&&f.f&&b.push(a):f.f&&b.push(a)});return b.sort()}function wa(){setTimeout(function(){fa||(fa=1,N(ga,function(a){a()}))},D)}var O,T=14,U=8,xa=A;function ya(a,d){var b="",c,f;if(d){c=a[15];if(16<c)throw"Decryption error: Maybe bad key";if(16==c)return"";for(f=0;f<16-c;f++)b+=String.fromCharCode(a[f])}else for(f=0;16>f;f++)b+=String.fromCharCode(a[f]);return b}
function za(a,d){var b=[],c;if(!d)try{a=unescape(encodeURIComponent(a))}catch(f){throw"Error on UTF-8 encode";}for(c=0;c<a.length;c++)b[c]=a.charCodeAt(c);return b}function Aa(a,d){var b=12<=T?3:2,c=[],f=[],c=[],f=[],p=a.concat(d),q;c[0]=GibberishAES.l.m(p);f=c[0];for(q=1;q<b;q++)c[q]=GibberishAES.l.m(c[q-1].concat(p)),f=f.concat(c[q]);c=f.slice(0,4*U);f=f.slice(4*U,4*U+16);return{key:c,i:f}}
function Ba(a,d,b){var d=Ca(d),c=Math.ceil(a.length/16),f=[],p,q=[];for(p=0;p<c;p++){var m=f,u=p,x=a.slice(16*p,16*p+16),t=[],r=v,r=v;16>x.length&&(r=16-x.length,t=[r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r]);for(r=0;r<x.length;r++)t[r]=x[r];m[u]=t}0===a.length%16&&f.push([16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16]);for(p=0;p<f.length;p++)f[p]=0===p?Da(f[p],b):Da(f[p],q[p-1]),q[p]=Ea(f[p],d);return q}
function Fa(a,d,b,c){var d=Ca(d),f=a.length/16,p=[],q,m=[],u="";for(q=0;q<f;q++)p.push(a.slice(16*q,16*(q+1)));for(q=p.length-1;0<=q;q--)m[q]=Ga(p[q],d),m[q]=0===q?Da(m[q],b):Da(m[q],p[q-1]);for(q=0;q<f-1;q++)u+=ya(m[q]);var u=u+ya(m[q],y),x;if(c)x=u;else try{x=decodeURIComponent(escape(u))}catch(t){throw"Bad Key";}return x}function Ea(a,d){xa=A;var b=La(a,d,0),c;for(c=1;c<T+1;c++)b=Ma(b),b=Na(b),c<T&&(b=Oa(b)),b=La(b,d,c);return b}
function Ga(a,d){xa=y;var b=La(a,d,T),c;for(c=T-1;-1<c;c--)b=Na(b),b=Ma(b),b=La(b,d,c),0<c&&(b=Oa(b));return b}function Ma(a){var d=xa?Pa:Qa,b=[],c;for(c=0;16>c;c++)b[c]=d[a[c]];return b}function Na(a){var d=[],b=xa?[0,13,10,7,4,1,14,11,8,5,2,15,12,9,6,3]:[0,5,10,15,4,9,14,3,8,13,2,7,12,1,6,11],c;for(c=0;16>c;c++)d[c]=a[b[c]];return d}
function Oa(a){var d=[],b;if(xa)for(b=0;4>b;b++)d[4*b]=Ta[a[4*b]]^Ua[a[1+4*b]]^Za[a[2+4*b]]^$a[a[3+4*b]],d[1+4*b]=$a[a[4*b]]^Ta[a[1+4*b]]^Ua[a[2+4*b]]^Za[a[3+4*b]],d[2+4*b]=Za[a[4*b]]^$a[a[1+4*b]]^Ta[a[2+4*b]]^Ua[a[3+4*b]],d[3+4*b]=Ua[a[4*b]]^Za[a[1+4*b]]^$a[a[2+4*b]]^Ta[a[3+4*b]];else for(b=0;4>b;b++)d[4*b]=ab[a[4*b]]^bb[a[1+4*b]]^a[2+4*b]^a[3+4*b],d[1+4*b]=a[4*b]^ab[a[1+4*b]]^bb[a[2+4*b]]^a[3+4*b],d[2+4*b]=a[4*b]^a[1+4*b]^ab[a[2+4*b]]^bb[a[3+4*b]],d[3+4*b]=bb[a[4*b]]^a[1+4*b]^a[2+4*b]^ab[a[3+4*
b]];return d}function La(a,d,b){var c=[],f;for(f=0;16>f;f++)c[f]=a[f]^d[b][f];return c}function Da(a,d){var b=[],c;for(c=0;16>c;c++)b[c]=a[c]^d[c];return b}
function Ca(a){var d=[],b=[],c,f,p=[];for(c=0;c<U;c++)f=[a[4*c],a[4*c+1],a[4*c+2],a[4*c+3]],d[c]=f;for(c=U;c<4*(T+1);c++){d[c]=[];for(a=0;4>a;a++)b[a]=d[c-1][a];if(0===c%U){a=b[0];f=v;for(f=0;4>f;f++)b[f]=b[f+1];b[3]=a;b=cb(b);b[0]^=db[c/U-1]}else 6<U&&4==c%U&&(b=cb(b));for(a=0;4>a;a++)d[c][a]=d[c-U][a]^b[a]}for(c=0;c<T+1;c++){p[c]=[];for(b=0;4>b;b++)p[c].push(d[4*c+b][0],d[4*c+b][1],d[4*c+b][2],d[4*c+b][3])}return p}function cb(a){for(var d=0;4>d;d++)a[d]=Qa[a[d]];return a}
function eb(a,d){var b=[];for(i=0;i<a.length;i+=d)b[i/d]=parseInt(a.substr(i,d),16);return b}function fb(a){for(var d=[],b=0;256>b;b++){for(var c=a,f=b,p=v,q=v,p=q=0;8>p;p++)q=1==(f&1)?q^c:q,c=127<c?283^c<<1:c<<1,f>>>=1;d[b]=q}return d}
var Qa=eb("637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb16",2),
Pa,gb=Qa,hb=[];for(i=0;i<gb.length;i++)hb[gb[i]]=i;Pa=hb;var db=eb("01020408102040801b366cd8ab4d9a2f5ebc63c697356ad4b37dfaefc591",2),ab=fb(2),bb=fb(3),$a=fb(9),Ua=fb(11),Za=fb(13),Ta=fb(14),ib,jb="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",kb=jb.split("");"function"===typeof Array.indexOf&&(jb=kb);
ib={encode:function(a){var d=[],b="",c;for(c=0;c<16*a.length;c++)d.push(a[Math.floor(c/16)][c%16]);for(c=0;c<d.length;c+=3)b+=kb[d[c]>>2],b+=kb[(d[c]&3)<<4|d[c+1]>>4],b=d[c+1]!==v?b+kb[(d[c+1]&15)<<2|d[c+2]>>6]:b+"=",b=d[c+2]!==v?b+kb[d[c+2]&63]:b+"=";a=b.slice(0,64);for(c=1;c<Math.ceil(b.length/64);c++)a+=b.slice(64*c,64*c+64)+(Math.ceil(b.length/64)==c+1?"":"\n");return a},decode:function(a){var a=a.replace(/\n/g,""),d=[],b=[],c=[],f;for(f=0;f<a.length;f+=4)b[0]=jb.indexOf(a.charAt(f)),b[1]=jb.indexOf(a.charAt(f+
1)),b[2]=jb.indexOf(a.charAt(f+2)),b[3]=jb.indexOf(a.charAt(f+3)),c[0]=b[0]<<2|b[1]>>4,c[1]=(b[1]&15)<<4|b[2]>>2,c[2]=(b[2]&3)<<6|b[3],d.push(c[0],c[1],c[2]);return d=d.slice(0,d.length-d.length%16)}};
O={size:function(a){switch(a){case 128:T=10;U=4;break;case 192:T=12;U=6;break;case 256:T=14;U=8;break;default:throw"Invalid Key Size Specified:"+a;}},h2a:function(a){var d=[];a.replace(/(..)/g,function(a){d.push(parseInt(a,16))});return d},expandKey:Ca,encryptBlock:Ea,decryptBlock:Ga,Decrypt:xa,s2a:za,rawEncrypt:Ba,rawDecrypt:Fa,dec:function(a,d,b){var a=ib.q(a),c=a.slice(8,16),c=Aa(za(d,b),c),d=c.key,c=c.i,a=a.slice(16,a.length);return a=Fa(a,d,c,b)},openSSLKey:Aa,a2h:function(a){var d="",b;for(b=
0;b<a.length;b++)d+=(16>a[b]?"0":"")+a[b].toString(16);return d},enc:function(a,d,b){var c;c=[];var f;for(f=0;8>f;f++)c=c.concat(Math.floor(256*Math.random()));f=Aa(za(d,b),c);d=f.key;f=f.i;c=[[83,97,108,116,101,100,95,95].concat(c)];a=za(a,b);a=Ba(a,d,f);a=c.concat(a);return ib.s(a)},Hash:{MD5:function(a){function d(a,b){var c,d,f,e,g;f=a&2147483648;e=b&2147483648;c=a&1073741824;d=b&1073741824;g=(a&1073741823)+(b&1073741823);return c&d?g^2147483648^f^e:c|d?g&1073741824?g^3221225472^f^e:g^1073741824^
f^e:g^f^e}function b(a,b,c,f,e,g,l){a=d(a,d(d(b&c|~b&f,e),l));return d(a<<g|a>>>32-g,b)}function c(a,b,c,f,e,g,l){a=d(a,d(d(b&f|c&~f,e),l));return d(a<<g|a>>>32-g,b)}function f(a,b,c,f,e,g,l){a=d(a,d(d(b^c^f,e),l));return d(a<<g|a>>>32-g,b)}function p(a,b,c,f,g,e,l){a=d(a,d(d(c^(b|~f),g),l));return d(a<<e|a>>>32-e,b)}function q(a){var b,c,d=[];for(c=0;3>=c;c++)b=a>>>8*c&255,d=d.concat(b);return d}var m=[],u,x,t,r,g,j,h,l,e=eb("67452301efcdab8998badcfe10325476d76aa478e8c7b756242070dbc1bdceeef57c0faf4787c62aa8304613fd469501698098d88b44f7afffff5bb1895cd7be6b901122fd987193a679438e49b40821f61e2562c040b340265e5a51e9b6c7aad62f105d02441453d8a1e681e7d3fbc821e1cde6c33707d6f4d50d87455a14eda9e3e905fcefa3f8676f02d98d2a4c8afffa39428771f6816d9d6122fde5380ca4beea444bdecfa9f6bb4b60bebfbc70289b7ec6eaa127fad4ef308504881d05d9d4d039e6db99e51fa27cf8c4ac5665f4292244432aff97ab9423a7fc93a039655b59c38f0ccc92ffeff47d85845dd16fa87e4ffe2ce6e0a30143144e0811a1f7537e82bd3af2352ad7d2bbeb86d391",
8),m=a.length;u=m+8;x=16*((u-u%64)/64+1);t=[];for(g=r=0;g<m;)u=(g-g%4)/4,r=8*(g%4),t[u]|=a[g]<<r,g++;u=(g-g%4)/4;t[u]|=128<<8*(g%4);t[x-2]=m<<3;t[x-1]=m>>>29;m=t;g=e[0];j=e[1];h=e[2];l=e[3];for(a=0;a<m.length;a+=16)u=g,x=j,t=h,r=l,g=b(g,j,h,l,m[a+0],7,e[4]),l=b(l,g,j,h,m[a+1],12,e[5]),h=b(h,l,g,j,m[a+2],17,e[6]),j=b(j,h,l,g,m[a+3],22,e[7]),g=b(g,j,h,l,m[a+4],7,e[8]),l=b(l,g,j,h,m[a+5],12,e[9]),h=b(h,l,g,j,m[a+6],17,e[10]),j=b(j,h,l,g,m[a+7],22,e[11]),g=b(g,j,h,l,m[a+8],7,e[12]),l=b(l,g,j,h,m[a+9],
12,e[13]),h=b(h,l,g,j,m[a+10],17,e[14]),j=b(j,h,l,g,m[a+11],22,e[15]),g=b(g,j,h,l,m[a+12],7,e[16]),l=b(l,g,j,h,m[a+13],12,e[17]),h=b(h,l,g,j,m[a+14],17,e[18]),j=b(j,h,l,g,m[a+15],22,e[19]),g=c(g,j,h,l,m[a+1],5,e[20]),l=c(l,g,j,h,m[a+6],9,e[21]),h=c(h,l,g,j,m[a+11],14,e[22]),j=c(j,h,l,g,m[a+0],20,e[23]),g=c(g,j,h,l,m[a+5],5,e[24]),l=c(l,g,j,h,m[a+10],9,e[25]),h=c(h,l,g,j,m[a+15],14,e[26]),j=c(j,h,l,g,m[a+4],20,e[27]),g=c(g,j,h,l,m[a+9],5,e[28]),l=c(l,g,j,h,m[a+14],9,e[29]),h=c(h,l,g,j,m[a+3],14,e[30]),
j=c(j,h,l,g,m[a+8],20,e[31]),g=c(g,j,h,l,m[a+13],5,e[32]),l=c(l,g,j,h,m[a+2],9,e[33]),h=c(h,l,g,j,m[a+7],14,e[34]),j=c(j,h,l,g,m[a+12],20,e[35]),g=f(g,j,h,l,m[a+5],4,e[36]),l=f(l,g,j,h,m[a+8],11,e[37]),h=f(h,l,g,j,m[a+11],16,e[38]),j=f(j,h,l,g,m[a+14],23,e[39]),g=f(g,j,h,l,m[a+1],4,e[40]),l=f(l,g,j,h,m[a+4],11,e[41]),h=f(h,l,g,j,m[a+7],16,e[42]),j=f(j,h,l,g,m[a+10],23,e[43]),g=f(g,j,h,l,m[a+13],4,e[44]),l=f(l,g,j,h,m[a+0],11,e[45]),h=f(h,l,g,j,m[a+3],16,e[46]),j=f(j,h,l,g,m[a+6],23,e[47]),g=f(g,j,
h,l,m[a+9],4,e[48]),l=f(l,g,j,h,m[a+12],11,e[49]),h=f(h,l,g,j,m[a+15],16,e[50]),j=f(j,h,l,g,m[a+2],23,e[51]),g=p(g,j,h,l,m[a+0],6,e[52]),l=p(l,g,j,h,m[a+7],10,e[53]),h=p(h,l,g,j,m[a+14],15,e[54]),j=p(j,h,l,g,m[a+5],21,e[55]),g=p(g,j,h,l,m[a+12],6,e[56]),l=p(l,g,j,h,m[a+3],10,e[57]),h=p(h,l,g,j,m[a+10],15,e[58]),j=p(j,h,l,g,m[a+1],21,e[59]),g=p(g,j,h,l,m[a+8],6,e[60]),l=p(l,g,j,h,m[a+15],10,e[61]),h=p(h,l,g,j,m[a+6],15,e[62]),j=p(j,h,l,g,m[a+13],21,e[63]),g=p(g,j,h,l,m[a+4],6,e[64]),l=p(l,g,j,h,m[a+
11],10,e[65]),h=p(h,l,g,j,m[a+2],15,e[66]),j=p(j,h,l,g,m[a+9],21,e[67]),g=d(g,u),j=d(j,x),h=d(h,t),l=d(l,r);return q(g).concat(q(j),q(h),q(l))}},Base64:ib};
if(!window.PUBNUB){var lb=function(a,d){return CryptoJS.HmacSHA256(a,d).toString(CryptoJS.enc.Base64)},mb=function(a){return document.getElementById(a)},nb=function(a){console.error(a)},pb=function(a,d){var b=[];N(a.split(/\s+/),function(a){N((d||document).getElementsByTagName(a),function(a){b.push(a)})});return b},qb=function(a,d,b){N(a.split(","),function(a){function f(a){a||(a=window.event);b(a)||(a.cancelBubble=y,a.preventDefault&&a.preventDefault(),a.stopPropagation&&a.stopPropagation())}d.addEventListener?
d.addEventListener(a,f,A):d.attachEvent?d.attachEvent("on"+a,f):d["on"+a]=f})},rb=function(){return pb("head")[0]},V=function(a,d,b){if(b)a.setAttribute(d,b);else return a&&a.getAttribute&&a.getAttribute(d)},sb=function(a,d){for(var b in d)if(d.hasOwnProperty(b))try{a.style[b]=d[b]+(0<"|width|height|top|left|".indexOf(b)&&"number"==typeof d[b]?"px":"")}catch(c){}},tb=function(a){return document.createElement(a)},zb=function(){return ub||$()?0:la()},Ab=function(a){function d(a,b){H||(H=1,e.onerror=
z,clearTimeout(Ra),a||!b||Sa(b),setTimeout(function(){a&&S();var b=mb(aa),c=b&&b.parentNode;c&&c.removeChild(b)},D))}if(ub||$()){a:{var b,c,f=function(){if(!q){q=1;clearTimeout(u);try{c=JSON.parse(b.responseText)}catch(a){return j(1)}p=1;r(c)}},p=0,q=0,m=a.timeout||1E4,u=setTimeout(function(){j(1,{message:"timeout"})},m),x=a.b||C(),t=a.data||{},r=a.c||C(),g=!a.h,j=function(a,c){p||(p=1,clearTimeout(u),b&&(b.onerror=b.onload=z,b.abort&&b.abort(),b=z),a&&x(c))};try{b=$()||window.XDomainRequest&&new XDomainRequest||
new XMLHttpRequest;b.onerror=b.onabort=function(){j(1,b.responseText||{error:"Network Connection Error"})};b.onload=b.onloadend=f;b.onreadystatechange=function(){if(b&&4==b.readyState)switch(b.status){case 401:case 402:case 403:try{c=JSON.parse(b.responseText),j(1,c)}catch(a){return j(1,b.responseText)}}};var h=qa(a.url,t);b.open("GET",h,g);g&&(b.timeout=m);b.send()}catch(l){j(0);ub=0;a=Ab(a);break a}a=j}return a}var e=tb("script"),f=a.a,aa=la(),H=0,Ra=setTimeout(function(){d(1,{message:"timeout"})},
a.timeout||1E4),S=a.b||C(),m=a.data||{},Sa=a.c||C();window[f]=function(a){d(0,a)};a.h||(e[Bb]=Bb);e.onerror=function(){d(1)};e.src=qa(a.url,m);V(e,"id",aa);rb().appendChild(e);return d},Cb=function(){return!("onLine"in navigator)?1:navigator.onLine},$=function(){if(!Db||!Db.get)return 0;var a={id:$.id++,send:C(),abort:function(){a.id={}},open:function(d,b){$[a.id]=a;Db.get(a.id,b)}};return a},Bb="async",ub=-1==navigator.userAgent.indexOf("MSIE 6");window.console||(window.console=window.console||{});
console.log||(console.log=console.error=(window.opera||{}).postError||C());var Eb,Fb=window.localStorage;Eb={get:function(a){try{return Fb?Fb.getItem(a):-1==document.cookie.indexOf(a)?z:((document.cookie||"").match(RegExp(a+"=([^;]+)"))||[])[1]||z}catch(d){}},set:function(a,d){try{if(Fb)return Fb.setItem(a,d)&&0;document.cookie=a+"="+d+"; expires=Thu, 1 Aug 2030 20:00:00 UTC; path=/"}catch(b){}}};var Gb={list:{},unbind:function(a){Gb.list[a]=[]},bind:function(a,d){(Gb.list[a]=Gb.list[a]||[]).push(d)},
fire:function(a,d){N(Gb.list[a]||[],function(a){a(d)})}},Ib=mb("pubnub")||0,Jb=function(a){function d(){}function b(a,b){function c(b){b&&(Va=G()-(b/1E4+(G()-d)/2),a&&a(Va))}var d=G();b&&c(b)||I.time(c)}function c(a,b){Ha&&Ha(a,b);Ha=z}function f(){I.time(function(a){b(C(),a);a||c(1,{error:"Heartbeat failed to connect to Pubnub Servers.Please check your network settings."});setTimeout(f,ob)})}function p(){Nb()||c(1,{error:"Offline. Please check your network settings. "});setTimeout(p,D)}function q(a,
b){"object"==typeof a&&a.error&&a.message&&a.payload?b({message:a.message,payload:a.payload}):b(a)}function m(a,b,c){if("object"==typeof a){if(a.error&&a.message&&a.payload){c({message:a.message,payload:a.payload});return}if(a.payload){b(a.payload);return}}b(a)}function u(a){var b=0;N(va(B),function(c){if(c=B[c])b++,(a||C())(c)});return b}function x(a){if(Ob){if(!Q.length)return}else{a&&(Q.j=0);if(Q.j||!Q.length)return;Q.j=1}E(Q.shift())}function t(){!Wa&&r()}function r(){clearTimeout(W);!J||500<=
J||1>J||!va(B,y).length?Wa=A:(Wa=y,I.presence_heartbeat({callback:function(){W=setTimeout(r,J*D)},error:function(a){s&&s("Presence Heartbeat unable to reach Pubnub servers."+JSON.stringify(a));W=setTimeout(r,J*D)}}))}function g(a,b){return ba.decrypt(a,b||X)||ba.decrypt(a,X)||a}function j(a,b,c){var d=A;if("number"===typeof a)d=5<a||0==a?A:y;else{if("boolean"===typeof a)return a?30:0;d=y}return d?(c&&c("Presence Heartbeat value invalid. Valid range ( x > 5 or x = 0). Current Value : "+(b||5)),b||
5):a}function h(a){var b="",c=[];N(a,function(a){c.push(a)});var d=c.sort(),f;for(f in d){var e=d[f],b=b+(e+"="+encodeURIComponent(a[e]));f!=d.length-1&&(b+="&")}return b}function l(a){a||(a={});N(Y,function(b,c){b in a||(a[b]=c)});return a}function e(a){return Jb(a)}function aa(a){function b(a,c){var d=(a&65535)+(c&65535);return(a>>16)+(c>>16)+(d>>16)<<16|d&65535}function c(a,b){return a>>>b|a<<32-b}var d;d=a.replace(/\r\n/g,"\n");for(var a="",f=0;f<d.length;f++){var e=d.charCodeAt(f);128>e?a+=String.fromCharCode(e):
(127<e&&2048>e?a+=String.fromCharCode(e>>6|192):(a+=String.fromCharCode(e>>12|224),a+=String.fromCharCode(e>>6&63|128)),a+=String.fromCharCode(e&63|128))}f=a;d=[];for(e=0;e<8*f.length;e+=8)d[e>>5]|=(f.charCodeAt(e/8)&255)<<24-e%32;var g=8*a.length,f=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,
2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],a=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,
1541459225],e=Array(64),l,m,j,h,q,p,r,t,s,w,u;d[g>>5]|=128<<24-g%32;d[(g+64>>9<<4)+15]=g;for(t=0;t<d.length;t+=16){g=a[0];l=a[1];m=a[2];j=a[3];h=a[4];q=a[5];p=a[6];r=a[7];for(s=0;64>s;s++)e[s]=16>s?d[s+t]:b(b(b(c(e[s-2],17)^c(e[s-2],19)^e[s-2]>>>10,e[s-7]),c(e[s-15],7)^c(e[s-15],18)^e[s-15]>>>3),e[s-16]),w=b(b(b(b(r,c(h,6)^c(h,11)^c(h,25)),h&q^~h&p),f[s]),e[s]),u=b(c(g,2)^c(g,13)^c(g,22),g&l^g&m^l&m),r=p,p=q,q=h,h=b(j,w),j=m,m=l,l=g,g=b(w,u);a[0]=b(g,a[0]);a[1]=b(l,a[1]);a[2]=b(m,a[2]);a[3]=b(j,a[3]);
a[4]=b(h,a[4]);a[5]=b(q,a[5]);a[6]=b(p,a[6]);a[7]=b(r,a[7])}d="";for(f=0;f<4*a.length;f++)d+="0123456789abcdef".charAt(a[f>>2]>>8*(3-f%4)+4&15)+"0123456789abcdef".charAt(a[f>>2]>>8*(3-f%4)&15);return d}a.jsonp&&(ub=0);var H=a.subscribe_key||"";a.uuid||Eb.get(H+"uuid");var Ra=a.leave_on_unload||0;a.xdr=Ab;a.db=Eb;a.error=a.error||nb;a._is_online=Cb;a.jsonp_cb=zb;a.hmac_SHA256=lb;O.size(256);var S=O.s2a("0123456789012345");a.crypto_obj={encrypt:function(a,b){if(!b)return a;var c=O.s2a(aa(b).slice(0,
32)),d=O.s2a(JSON.stringify(a)),c=O.rawEncrypt(d,c,S);return O.Base64.encode(c)||a},decrypt:function(a,b){if(!b)return a;var c=O.s2a(aa(b).slice(0,32));try{var d=O.Base64.decode(a),e=O.rawDecrypt(d,c,S,A);return JSON.parse(e)}catch(f){}}};a.params={pnsdk:"PubNub-JS-Web/3.6.7"};var Sa=+a.windowing||10,Hb=(+a.timeout||310)*D,ob=(+a.keepalive||60)*D,Lb=a.noleave||0,P=a.publish_key||"demo",w=a.subscribe_key||"demo",M=a.auth_key||"",Ia=a.secret_key||"",vb=a.hmac_SHA256,Xa=a.ssl?"s":"",oa="http"+Xa+"://"+
(a.origin||"pubsub.pubnub.com"),K=ma(oa),wb=ma(oa),Q=[],Va=0,xb=0,yb=0,Ha=0,Ja=a.restore||0,da=0,Ya=A,B={},Z={},W=z,R=j(a.heartbeat||a.pnexpires||0,a.error),J=a.heartbeat_interval||R-3,Wa=A,Ob=a.no_wait_for_pending,Pb=a["compatible_3.5"]||A,E=a.xdr,Y=a.params||{},s=a.error||C(),Nb=a._is_online||function(){return 1},L=a.jsonp_cb||function(){return 0},ea=a.db||{get:C(),set:C()},X=a.cipher_key,F=a.uuid||ea&&ea.get(w+"uuid")||"",ba=a.crypto_obj||{encrypt:function(a){return a},decrypt:function(a){return a}},
I={LEAVE:function(a,b,c,d){var e={uuid:F,auth:M},f=ma(oa),c=c||C(),g=d||C(),d=L();if(0<a.indexOf(ha))return y;if(Pb&&(!Xa||"0"==d)||Lb)return A;"0"!=d&&(e.callback=d);E({h:b||Xa,timeout:2E3,a:d,data:l(e),c:function(a){m(a,c,g)},b:function(a){q(a,g)},url:[f,"v2","presence","sub_key",w,"channel",encodeURIComponent(a),"leave"]});return y},set_resumed:function(a){Ya=a},get_cipher_key:function(){return X},set_cipher_key:function(a){X=a},raw_encrypt:function(a,b){return ba.encrypt(a,b||X)||a},raw_decrypt:function(a,
b){return g(a,b)},get_heartbeat:function(){return R},set_heartbeat:function(a){R=j(a,J,s);J=1<=R-3?R-3:1;d();r()},get_heartbeat_interval:function(){return J},set_heartbeat_interval:function(a){J=a;r()},get_version:function(){return"3.6.7"},getGcmMessageObject:function(a){return{data:a}},getApnsMessageObject:function(a){var b={aps:{badge:1,alert:""}};for(k in a)k[b]=a[k];return b},newPnMessage:function(){var a={};gcm&&(a.pn_gcm=gcm);apns&&(a.pn_apns=apns);for(k in n)a[k]=n[k];return a},_add_param:function(a,
b){Y[a]=b},history:function(a,b){var b=a.callback||b,c=a.count||a.limit||100,d=a.reverse||"false",e=a.error||C(),f=a.auth_key||M,h=a.cipher_key,m=a.channel,j=a.start,p=a.end,r=a.include_token,t={},u=L();if(!m)return s("Missing Channel");if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");t.stringtoken="true";t.count=c;t.reverse=d;t.auth=f;u&&(t.callback=u);j&&(t.start=j);p&&(t.end=p);r&&(t.include_token="true");E({a:u,data:l(t),c:function(a){if("object"==typeof a&&a.error)e({message:a.message,
payload:a.payload});else{for(var c=a[0],d=[],f=0;f<c.length;f++){var l=g(c[f],h);try{d.push(JSON.parse(l))}catch(m){d.push(l)}}b([d,a[1],a[2]])}},b:function(a){q(a,e)},url:[K,"v2","history","sub-key",w,"channel",encodeURIComponent(m)]})},replay:function(a,b){var b=b||a.callback||C(),c=a.auth_key||M,d=a.source,e=a.destination,f=a.stop,g=a.start,h=a.end,j=a.reverse,q=a.limit,p=L(),r={};if(!d)return s("Missing Source Channel");if(!e)return s("Missing Destination Channel");if(!P)return s("Missing Publish Key");
if(!w)return s("Missing Subscribe Key");"0"!=p&&(r.callback=p);f&&(r.stop="all");j&&(r.reverse="true");g&&(r.start=g);h&&(r.end=h);q&&(r.count=q);r.auth=c;E({a:p,c:function(a){m(a,b,err)},b:function(){b([0,"Disconnected"])},url:[K,"v1","replay",P,w,d,e],data:l(r)})},auth:function(a){M=a;d()},time:function(a){var b=L();E({a:b,data:l({uuid:F,auth:M}),timeout:5*D,url:[K,"time",b],c:function(b){a(b[0])},b:function(){a(0)}})},publish:function(a,b){var c=a.message;if(!c)return s("Missing Message");var b=
b||a.callback||c.callback||C(),d=a.channel||c.channel,e=a.auth_key||M,f=a.cipher_key,g=a.error||c.error||C(),h=a.post||A,j="store_in_history"in a?a.store_in_history:y,p=L(),r="push";a.prepend&&(r="unshift");if(!d)return s("Missing Channel");if(!P)return s("Missing Publish Key");if(!w)return s("Missing Subscribe Key");c.getPubnubMessage&&(c=c.getPubnubMessage());c=JSON.stringify(ba.encrypt(c,f||X)||c);c=[K,"publish",P,w,0,encodeURIComponent(d),p,encodeURIComponent(c)];Y={uuid:F,auth:e};j||(Y.store=
"0");Q[r]({a:p,timeout:5*D,url:c,data:l(Y),b:function(a){q(a,g);x(1)},c:function(a){m(a,b,g);x(1)},mode:h?"POST":"GET"});x()},unsubscribe:function(a,b){var c=a.channel,b=b||a.callback||C(),e=a.error||C();da=0;c=ua((c.join?c.join(","):""+c).split(","),function(a){if(B[a])return a+","+a+ha}).join(",");N(c.split(","),function(a){var c=y;a&&(fa&&(c=I.LEAVE(a,0,b,e)),c||b({action:"leave"}),B[a]=0,a in Z&&delete Z[a])});d()},subscribe:function(a,b){function e(a){a?setTimeout(d,D):(K=ma(oa,1),wb=ma(oa,1),
setTimeout(function(){I.time(e)},D));u(function(b){if(a&&b.d)return b.d=0,b.p(b.name);!a&&!b.d&&(b.d=1,b.o(b.name))})}function f(){var a=L(),b=va(B).join(",");if(b){c();var h=l({uuid:F,auth:m});2<JSON.stringify(Z).length&&(h.state=JSON.stringify(Z));R&&(h.heartbeat=R);t();Ha=E({timeout:aa,a:a,b:function(a){q(a,x);I.time(e)},data:l(h),url:[wb,"subscribe",w,encodeURIComponent(b),a,da],c:function(a){if(!a||"object"==typeof a&&"error"in a&&a.error)return x(a.error),setTimeout(d,D);J(a[1]);da=!da&&Ja&&
ea.get(w)||a[1];u(function(a){a.g||(a.g=1,a.n(a.name))});if(Ya&&!Ja)da=0,Ya=A,ea.set(w,0);else{Q&&(da=1E4,Q=0);ea.set(w,a[1]);var b,c=(2<a.length?a[2]:ua(va(B),function(b){return ua(Array(a[0].length).join(",").split(","),function(){return b})}).join(",")).split(",");b=function(){var a=c.shift()||yb;return[(B[a]||{}).a||xb,a.split(ha)[0]]};var e=G()-Va-+a[1]/1E4;N(a[0],function(c){var d=b(),c=g(c,B[d[1]]?B[d[1]].cipher_key:z);d[0](c,a,d[1],e)})}setTimeout(f,Y)}})}}var h=a.channel,b=(b=b||a.callback)||
a.message,m=a.auth_key||M,j=a.connect||C(),p=a.reconnect||C(),r=a.disconnect||C(),x=a.error||C(),J=a.idle||C(),H=a.presence||0,P=a.noheresync||0,Q=a.backfill||0,X=a.timetoken||0,aa=a.timeout||Hb,Y=a.windowing||Sa,S=a.state,W=a.heartbeat||a.pnexpires,ba=a.restore||Ja;Ja=ba;da=X;if(!h)return s("Missing Channel");if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");(W||0===W)&&I.set_heartbeat(W);N((h.join?h.join(","):""+h).split(","),function(c){var d=B[c]||{};B[yb=c]={name:c,g:d.g,
d:d.d,f:1,a:xb=b,cipher_key:a.cipher_key,n:j,o:r,p:p};S&&(Z[c]=c in S?S[c]:S);H&&(I.subscribe({channel:c+ha,callback:H,restore:ba}),!d.f&&!P&&I.here_now({channel:c,callback:function(a){N("uuids"in a?a.uuids:[],function(b){H({action:"join",uuid:b,timestamp:Math.floor(G()/1E3),occupancy:a.occupancy||1},a,c)})}}))});d=function(){c();setTimeout(f,Y)};if(!fa)return ga.push(d);d()},here_now:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.auth_key||M,e=a.channel,f=L(),g=a.state,d={uuid:F,auth:d};if(!("uuids"in
a?a.uuids:1))d.disable_uuids=1;g&&(d.state=1);if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");g=[K,"v2","presence","sub_key",w];e&&g.push("channel")&&g.push(encodeURIComponent(e));"0"!=f&&(d.callback=f);E({a:f,data:l(d),c:function(a){m(a,b,c)},b:function(a){q(a,c)},url:g})},where_now:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.auth_key||M,e=L(),f=a.uuid||F,d={auth:d};if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");"0"!=e&&(d.callback=
e);E({a:e,data:l(d),c:function(a){m(a,b,c)},b:function(a){q(a,c)},url:[K,"v2","presence","sub_key",w,"uuid",encodeURIComponent(f)]})},state:function(a,b){var b=a.callback||b||C(),c=a.error||C(),d=a.auth_key||M,e=L(),f=a.state,g=a.uuid||F,h=a.channel,d=l({auth:d});if(!w)return s("Missing Subscribe Key");if(!g)return s("Missing UUID");if(!h)return s("Missing Channel");"0"!=e&&(d.callback=e);B[h]&&(B[h].f&&f)&&(Z[h]=f);d.state=JSON.stringify(f);f=f?[K,"v2","presence","sub-key",w,"channel",encodeURIComponent(h),
"uuid",g,"data"]:[K,"v2","presence","sub-key",w,"channel",encodeURIComponent(h),"uuid",encodeURIComponent(g)];E({a:e,data:l(d),c:function(a){m(a,b,c)},b:function(a){q(a,c)},url:f})},grant:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.channel,e=L(),f=a.ttl,g=a.read?"1":"0",j=a.write?"1":"0",p=a.auth_key;if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");if(!P)return s("Missing Publish Key");if(!Ia)return s("Missing Secret Key");var r=w+"\n"+P+"\ngrant\n",g={w:j,r:g,timestamp:Math.floor((new Date).getTime()/
1E3)};"undefined"!=d&&(d!=z&&0<d.length)&&(g.channel=d);"0"!=e&&(g.callback=e);if(f||0===f)g.ttl=f;p&&(g.auth=p);g=l(g);p||delete g.auth;r+=h(g);d=vb(r,Ia);d=d.replace(/\+/g,"-");d=d.replace(/\//g,"_");g.signature=d;E({a:e,data:g,c:function(a){m(a,b,c)},b:function(a){q(a,c)},url:[K,"v1","auth","grant","sub-key",w]})},audit:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.channel,e=a.auth_key,f=L();if(!b)return s("Missing Callback");if(!w)return s("Missing Subscribe Key");if(!P)return s("Missing Publish Key");
if(!Ia)return s("Missing Secret Key");var g=w+"\n"+P+"\naudit\n",j={timestamp:Math.floor((new Date).getTime()/1E3)};"0"!=f&&(j.callback=f);"undefined"!=d&&(d!=z&&0<d.length)&&(j.channel=d);e&&(j.auth=e);j=l(j);e||delete j.auth;g+=h(j);d=vb(g,Ia);d=d.replace(/\+/g,"-");d=d.replace(/\//g,"_");j.signature=d;E({a:f,data:j,c:function(a){m(a,b,c)},b:function(a){q(a,c)},url:[K,"v1","auth","audit","sub-key",w]})},revoke:function(a,b){a.read=A;a.write=A;I.grant(a,b)},set_uuid:function(a){F=a;d()},get_uuid:function(){return F},
presence_heartbeat:function(a){var b=a.callback||C(),c=a.error||C(),a=L(),d={uuid:F,auth:M};2<JSON.stringify(Z).length&&(d.state=JSON.stringify(Z));0<R&&320>R&&(d.heartbeat=R);"0"!=a&&(d.callback=a);var e=E,d=l(d),f=5*D,g=K,h=w,j=va(B,y).join(",");e({a:a,data:d,timeout:f,url:[g,"v2","presence","sub-key",h,"channel",encodeURIComponent(j),"heartbeat"],c:function(a){m(a,b,c)},b:function(a){q(a,c)}})},xdr:E,ready:wa,db:ea,uuid:pa,map:ua,each:N,"each-channel":u,grep:sa,offline:function(){c(1,{message:"Offline. Please check your network settings."})},
supplant:ta,now:G,unique:la,updater:ra};F||(F=I.uuid());ea.set(w+"uuid",F);setTimeout(p,D);setTimeout(f,ob);W=setTimeout(t,(J-3)*D);b();var H=I,Ka;for(Ka in H)H.hasOwnProperty(Ka)&&(e[Ka]=H[Ka]);e.css=sb;e.$=mb;e.create=tb;e.bind=qb;e.head=rb;e.search=pb;e.attr=V;e.events=Gb;e.init=e;e.secure=e;qb("beforeunload",window,function(){if(Ra)e["each-channel"](function(a){e.LEAVE(a.name,0)});return y});if(a.notest)return e;qb("offline",window,e.offline);qb("offline",document,e.offline);return e};Jb.init=
Jb;Jb.secure=Jb;"complete"===document.readyState?setTimeout(wa,0):qb("load",window,function(){setTimeout(wa,0)});var Kb=Ib||{};PUBNUB=Jb({notest:1,publish_key:V(Kb,"pub-key"),subscribe_key:V(Kb,"sub-key"),ssl:!document.location.href.indexOf("https")||"on"==V(Kb,"ssl"),origin:V(Kb,"origin"),uuid:V(Kb,"uuid")});window.jQuery&&(window.jQuery.PUBNUB=Jb);"undefined"!==typeof module&&(module.exports=PUBNUB)&&wa();var Db=mb("pubnubs")||0;if(Ib){sb(Ib,{position:"absolute",top:-D});if("opera"in window||V(Ib,
"flash"))Ib.innerHTML="<object id=pubnubs data=https://pubnub.a.ssl.fastly.net/pubnub.swf><param name=movie value=https://pubnub.a.ssl.fastly.net/pubnub.swf><param name=allowscriptaccess value=always></object>";PUBNUB.rdx=function(a,d){if(!d)return $[a].onerror();$[a].responseText=unescape(d);$[a].onload()};$.id=D}}
var Mb=PUBNUB.ws=function(a,d){if(!(this instanceof Mb))return new Mb(a,d);var b=this,a=b.url=a||"";b.protocol=d||"Sec-WebSocket-Protocol";var c=a.split("/"),c={ssl:"wss:"===c[0],origin:c[2],publish_key:c[3],subscribe_key:c[4],channel:c[5]};b.CONNECTING=0;b.OPEN=1;b.CLOSING=2;b.CLOSED=3;b.CLOSE_NORMAL=1E3;b.CLOSE_GOING_AWAY=1001;b.CLOSE_PROTOCOL_ERROR=1002;b.CLOSE_UNSUPPORTED=1003;b.CLOSE_TOO_LARGE=1004;b.CLOSE_NO_STATUS=1005;b.CLOSE_ABNORMAL=1006;b.onclose=b.onerror=b.onmessage=b.onopen=b.onsend=
C();b.binaryType="";b.extensions="";b.bufferedAmount=0;b.trasnmitting=A;b.buffer=[];b.readyState=b.CONNECTING;if(!a)return b.readyState=b.CLOSED,b.onclose({code:b.CLOSE_ABNORMAL,reason:"Missing URL",wasClean:y}),b;b.e=PUBNUB.init(c);b.e.k=c;b.k=c;b.e.subscribe({restore:A,channel:c.channel,disconnect:b.onerror,reconnect:b.onopen,error:function(){b.onclose({code:b.CLOSE_ABNORMAL,reason:"Missing URL",wasClean:A})},callback:function(a){b.onmessage({data:a})},connect:function(){b.readyState=b.OPEN;b.onopen()}})};
Mb.prototype.send=function(a){var d=this;d.e.publish({channel:d.e.k.channel,message:a,callback:function(a){d.onsend({data:a})}})};
})();