├── README.md ├── css └── style.css ├── index.html ├── js ├── video-effect.js └── webrtc-phone-handler.js └── lib ├── EdgeShader.js ├── pubnub-3.7.13.min.js ├── three.min.js └── webrtc.js /README.md: -------------------------------------------------------------------------------- 1 | # As seen on AgilityFeat's Blog 2 | 3 | ![Jean Lescure giving a thumbs up, with an edge shader effect, through WebRTC call](http://blog.agilityfeat.com/wp-content/uploads/2015/08/WebGL_on_WebRTC.gif) 4 | 5 | [(Click Here to Read Full Post)](http://www.agilityfeat.com/blog/2015/08/did-you-know-using-webgl-shaders-on-webrtc-video) 6 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html{margin:0;padding:0;} 2 | body{margin:0;padding:50px;background:#3780cd;font-family:sans-serif;font-size:18px;color:#fff;font-weight:bold;} 3 | div{text-align:center;padding:10px;} 4 | p{margin:0;padding:0;width:50%;display:inline-block;text-align:left;} 5 | p.right{text-align:right;} 6 | #video-out canvas{border:5px #62bb46 solid;} 7 | button{border:none;padding:10px;border-radius:3px;background:#fff;color:#62bb46;font-size:18px;font-weight:bold;} -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

My Number: 

17 |

Friend's Number: 

18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /js/video-effect.js: -------------------------------------------------------------------------------- 1 | // standard global variables 2 | var container, scene, camera, renderer; 3 | 4 | // custom global variables 5 | var video, videoImage, videoImageContext, videoTexture, movieScreen; 6 | 7 | // FUNCTIONS 8 | function init_video(){ 9 | // SCENE 10 | scene = new THREE.Scene(); 11 | // CAMERA 12 | camera = new THREE.OrthographicCamera( 0, video.videoWidth, 0, video.videoHeight, 0, 10000); 13 | scene.add(camera); 14 | camera.position.set(0,0,-100); 15 | camera.lookAt(scene.position); 16 | // RENDERER 17 | renderer = new THREE.WebGLRenderer( {antialias:true} ); 18 | 19 | renderer.setSize(video.videoWidth, video.videoHeight); 20 | container = document.getElementById('video-out'); 21 | container.appendChild( renderer.domElement ); 22 | 23 | /////////// 24 | // VIDEO // 25 | /////////// 26 | 27 | videoImage = document.createElement( 'canvas' ); 28 | videoImage.width = video.videoWidth; 29 | videoImage.height = video.videoHeight; 30 | 31 | videoImageContext = videoImage.getContext( '2d' ); 32 | // background color if no video present 33 | videoImageContext.fillStyle = '#000000'; 34 | videoImageContext.fillRect( 0, 0, videoImage.width, videoImage.height ); 35 | 36 | videoTexture = new THREE.Texture( videoImage ); 37 | videoTexture.flipY = false; 38 | videoTexture.minFilter = THREE.LinearFilter; 39 | videoTexture.magFilter = THREE.LinearFilter; 40 | 41 | var movieMaterial = new THREE.ShaderMaterial(THREE.EdgeShader); 42 | movieMaterial.uniforms.tDiffuse.value = videoTexture; 43 | 44 | // the geometry on which the movie will be displayed; 45 | // movie image will be scaled to fit these dimensions. 46 | var movieGeometry = new THREE.PlaneBufferGeometry( videoImage.width, videoImage.height, 1, 1 ); 47 | movieScreen = new THREE.Mesh( movieGeometry, movieMaterial ); 48 | movieScreen.position.x = -video.videoWidth/2; 49 | movieScreen.position.y = video.videoHeight/2; 50 | 51 | scene.add(movieScreen); 52 | 53 | animate(); 54 | } 55 | 56 | function animate() 57 | { 58 | requestAnimationFrame( animate ); 59 | render(); 60 | } 61 | 62 | function render() 63 | { 64 | if ( video.readyState === video.HAVE_ENOUGH_DATA ) 65 | { 66 | videoImageContext.drawImage( video, 0, 0 ); 67 | if ( videoTexture ) 68 | videoTexture.needsUpdate = true; 69 | } 70 | 71 | renderer.render( scene, camera ); 72 | } -------------------------------------------------------------------------------- /js/webrtc-phone-handler.js: -------------------------------------------------------------------------------- 1 | var my_num; 2 | var friend_num; 3 | var phone; 4 | var session; 5 | function start_phone(){ 6 | my_num = document.getElementById('my_number').value; 7 | friend_num = document.getElementById('friend_number').value; 8 | // ~Warning~ You must get your own API Keys for non-demo purposes. 9 | // ~Warning~ Get your PubNub API Keys: http://www.pubnub.com/get-started/ 10 | // The phone *number* can by any string value 11 | phone = PHONE({ 12 | number : my_num, 13 | publish_key : 'pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 14 | subscribe_key : 'sub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 15 | ssl : true 16 | }); 17 | 18 | // As soon as the phone is ready we can make calls 19 | phone.ready(function(){ 20 | PUBNUB.$('buttons').innerHTML = ''; 21 | var call_button = document.createElement('button'); 22 | call_button.innerHTML = 'Call'; 23 | call_button.onclick = start_call; 24 | PUBNUB.$('buttons').appendChild(call_button); 25 | }); 26 | 27 | // When Call Comes In or is to be Connected 28 | phone.receive(function(session){ 29 | // Display Your Friend's Live Video 30 | session.connected(function(session){ 31 | video = session.video; 32 | video.addEventListener('playing', init_video, false); 33 | }); 34 | }); 35 | }; 36 | function start_call(){ 37 | // Dial a Number and get the Call Session 38 | // For simplicity the phone number is the same for both caller/receiver. 39 | // you should use different phone numbers for each user. 40 | session = phone.dial(friend_num); 41 | } -------------------------------------------------------------------------------- /lib/EdgeShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author zz85 / https://github.com/zz85 | https://www.lab4games.net/zz85/blog 3 | * 4 | * Edge Detection Shader using Frei-Chen filter 5 | * Based on http://rastergrid.com/blog/2011/01/frei-chen-edge-detector 6 | * 7 | * aspect: vec2 of (1/width, 1/height) 8 | */ 9 | 10 | THREE.EdgeShader = { 11 | 12 | uniforms: { 13 | 14 | "tDiffuse": { type: "t", value: null }, 15 | "aspect": { type: "v2", value: new THREE.Vector2( 512, 512 ) }, 16 | }, 17 | 18 | vertexShader: [ 19 | 20 | "varying vec2 vUv;", 21 | 22 | "void main() {", 23 | 24 | "vUv = uv;", 25 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 26 | 27 | "}" 28 | 29 | ].join("\n"), 30 | 31 | fragmentShader: [ 32 | 33 | "uniform sampler2D tDiffuse;", 34 | "varying vec2 vUv;", 35 | 36 | "uniform vec2 aspect;", 37 | 38 | "vec2 texel = vec2(1.0 / aspect.x, 1.0 / aspect.y);", 39 | 40 | 41 | "mat3 G[9];", 42 | 43 | // hard coded matrix values!!!! as suggested in https://github.com/neilmendoza/ofxPostProcessing/blob/master/src/EdgePass.cpp#L45 44 | 45 | "const mat3 g0 = mat3( 0.3535533845424652, 0, -0.3535533845424652, 0.5, 0, -0.5, 0.3535533845424652, 0, -0.3535533845424652 );", 46 | "const mat3 g1 = mat3( 0.3535533845424652, 0.5, 0.3535533845424652, 0, 0, 0, -0.3535533845424652, -0.5, -0.3535533845424652 );", 47 | "const mat3 g2 = mat3( 0, 0.3535533845424652, -0.5, -0.3535533845424652, 0, 0.3535533845424652, 0.5, -0.3535533845424652, 0 );", 48 | "const mat3 g3 = mat3( 0.5, -0.3535533845424652, 0, -0.3535533845424652, 0, 0.3535533845424652, 0, 0.3535533845424652, -0.5 );", 49 | "const mat3 g4 = mat3( 0, -0.5, 0, 0.5, 0, 0.5, 0, -0.5, 0 );", 50 | "const mat3 g5 = mat3( -0.5, 0, 0.5, 0, 0, 0, 0.5, 0, -0.5 );", 51 | "const mat3 g6 = mat3( 0.1666666716337204, -0.3333333432674408, 0.1666666716337204, -0.3333333432674408, 0.6666666865348816, -0.3333333432674408, 0.1666666716337204, -0.3333333432674408, 0.1666666716337204 );", 52 | "const mat3 g7 = mat3( -0.3333333432674408, 0.1666666716337204, -0.3333333432674408, 0.1666666716337204, 0.6666666865348816, 0.1666666716337204, -0.3333333432674408, 0.1666666716337204, -0.3333333432674408 );", 53 | "const mat3 g8 = mat3( 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408, 0.3333333432674408 );", 54 | 55 | "void main(void)", 56 | "{", 57 | 58 | "G[0] = g0,", 59 | "G[1] = g1,", 60 | "G[2] = g2,", 61 | "G[3] = g3,", 62 | "G[4] = g4,", 63 | "G[5] = g5,", 64 | "G[6] = g6,", 65 | "G[7] = g7,", 66 | "G[8] = g8;", 67 | 68 | "mat3 I;", 69 | "float cnv[9];", 70 | "vec3 sample;", 71 | 72 | /* fetch the 3x3 neighbourhood and use the RGB vector's length as intensity value */ 73 | "for (float i=0.0; i<3.0; i++) {", 74 | "for (float j=0.0; j<3.0; j++) {", 75 | "sample = texture2D(tDiffuse, vUv + texel * vec2(i-1.0,j-1.0) ).rgb;", 76 | "I[int(i)][int(j)] = length(sample);", 77 | "}", 78 | "}", 79 | 80 | /* calculate the convolution values for all the masks */ 81 | "for (int i=0; i<9; i++) {", 82 | "float dp3 = dot(G[i][0], I[0]) + dot(G[i][1], I[1]) + dot(G[i][2], I[2]);", 83 | "cnv[i] = dp3 * dp3;", 84 | "}", 85 | 86 | "float M = (cnv[0] + cnv[1]) + (cnv[2] + cnv[3]);", 87 | "float S = (cnv[4] + cnv[5]) + (cnv[6] + cnv[7]) + (cnv[8] + M);", 88 | 89 | "gl_FragColor = vec4(vec3(sqrt(M/S)), 1.0);", 90 | "}", 91 | 92 | ].join("\n") 93 | }; 94 | -------------------------------------------------------------------------------- /lib/pubnub-3.7.13.min.js: -------------------------------------------------------------------------------- 1 | // Version: 3.7.13 2 | (function(){ 3 | var r=!0,y=null,A=!1;function C(){return function(){}} 4 | window.JSON&&window.JSON.stringify||function(){function a(){try{return this.valueOf()}catch(a){return y}}function c(a){e.lastIndex=0;return e.test(a)?'"'+a.replace(e,function(a){var b=E[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function b(e,ba){var J,g,N,u,G,E=f,s=ba[e];s&&"object"===typeof s&&(s=a.call(s));"function"===typeof q&&(s=q.call(ba,e,s));switch(typeof s){case "string":return c(s);case "number":return isFinite(s)?String(s):"null"; 5 | case "boolean":case "null":return String(s);case "object":if(!s)return"null";f+=w;G=[];if("[object Array]"===Object.prototype.toString.apply(s)){u=s.length;for(J=0;J++oa?oa:oa=1))||a}; 8 | function qa(a,c){var b=a.join(fa),e=[];if(!c)return b;$(c,function(a,b){var c="object"==typeof b?JSON.stringify(b):b;"undefined"!=typeof b&&(b!=y&&0K()?(clearTimeout(e),e=setTimeout(b,c)):(f=K(),a())}var e,f=0;return b}function ta(a,c){var b=[];$(a||[],function(a){c(a)&&b.push(a)});return b}function ua(a,c){return a.replace(la,function(a,e){return c[e]||a})} 9 | function pa(a){var c="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(c);return c}function va(a){return!!a&&"string"!==typeof a&&(Array.isArray&&Array.isArray(a)||"number"===typeof a.length)}function $(a,c){if(a&&c)if(va(a))for(var b=0,e=a.length;ba.search("-pnpres")&&f.e&&b.push(a):f.e&&b.push(a)});return b.sort()}function za(a,c){var b=[];$(a,function(a,f){c?0>a.search("-pnpres")&&f.e&&b.push(a):f.e&&b.push(a)});return b.sort()} 11 | function Ha(){setTimeout(function(){ca||(ca=1,$(da,function(a){a()}))},F)} 12 | function Ia(){function a(a){a=a||{};a.hasOwnProperty("encryptKey")||(a.encryptKey=p.encryptKey);a.hasOwnProperty("keyEncoding")||(a.keyEncoding=p.keyEncoding);a.hasOwnProperty("keyLength")||(a.keyLength=p.keyLength);a.hasOwnProperty("mode")||(a.mode=p.mode);-1==w.indexOf(a.keyEncoding.toLowerCase())&&(a.keyEncoding=p.keyEncoding);-1==E.indexOf(parseInt(a.keyLength,10))&&(a.keyLength=p.keyLength);-1==q.indexOf(a.mode.toLowerCase())&&(a.mode=p.mode);return a}function c(a,b){a="base64"==b.keyEncoding? 13 | CryptoJS.enc.Base64.parse(a):"hex"==b.keyEncoding?CryptoJS.enc.Hex.parse(a):a;return b.encryptKey?CryptoJS.enc.Utf8.parse(CryptoJS.SHA256(a).toString(CryptoJS.enc.Hex).slice(0,32)):a}function b(a){return"ecb"==a.mode?CryptoJS.mode.ECB:CryptoJS.mode.CBC}function e(a){return"cbc"==a.mode?CryptoJS.enc.Utf8.parse(f):y}var f="0123456789012345",w=["hex","utf8","base64","binary"],E=[128,256],q=["ecb","cbc"],p={encryptKey:r,keyEncoding:"utf8",keyLength:256,mode:"cbc"};return{encrypt:function(f,q,g){if(!q)return f; 14 | var g=a(g),p=e(g),w=b(g),q=c(q,g),g=JSON.stringify(f);return CryptoJS.AES.encrypt(g,q,{iv:p,mode:w}).ciphertext.toString(CryptoJS.enc.Base64)||f},decrypt:function(f,q,g){if(!q)return f;var g=a(g),p=e(g),w=b(g),q=c(q,g);try{var G=CryptoJS.enc.Base64.parse(f),E=CryptoJS.AES.decrypt({ciphertext:G},q,{iv:p,mode:w}).toString(CryptoJS.enc.Utf8);return JSON.parse(E)}catch(s){}}}} 15 | if(!window.PUBNUB){var Ja=function(a,c){return CryptoJS.HmacSHA256(a,c).toString(CryptoJS.enc.Base64)},Ma=function(a){return document.getElementById(a)},Na=function(a){console.error(a)},Ta=function(a,c){var b=[];$(a.split(/\s+/),function(a){$((c||document).getElementsByTagName(a),function(a){b.push(a)})});return b},Ua=function(a,c,b){$(a.split(","),function(a){function f(a){a||(a=window.event);b(a)||(a.cancelBubble=r,a.preventDefault&&a.preventDefault(),a.stopPropagation&&a.stopPropagation())}c.addEventListener? 16 | c.addEventListener(a,f,A):c.attachEvent?c.attachEvent("on"+a,f):c["on"+a]=f})},Va=function(){return Ta("head")[0]},Wa=function(a,c,b){if(b)a.setAttribute(c,b);else return a&&a.getAttribute&&a.getAttribute(c)},Xa=function(a,c){for(var b in c)if(c.hasOwnProperty(b))try{a.style[b]=c[b]+(0<"|width|height|top|left|".indexOf(b)&&"number"==typeof c[b]?"px":"")}catch(e){}},Ya=function(a){return document.createElement(a)},cb=function(){return Za||$a()?0:ma()},db=function(a){function c(a,b){l||(l=1,s.onerror= 17 | y,clearTimeout(R),a||!b||Ka(b),setTimeout(function(){a&&La();var b=Ma(B),c=b&&b.parentNode;c&&c.removeChild(b)},F))}if(Za||$a()){a:{var b,e,f=function(){if(!E){E=1;clearTimeout(p);try{e=JSON.parse(b.responseText)}catch(a){return u(1)}w=1;g(e)}},w=0,E=0,q=a.timeout||1E4,p=setTimeout(function(){u(1,{message:"timeout"})},q),ba=a.b||C(),J=a.data||{},g=a.c||C(),N=!a.h,u=function(a,c){w||(w=1,clearTimeout(p),b&&(b.onerror=b.onload=y,b.abort&&b.abort(),b=y),a&&ba(c))};try{b=$a()||window.XDomainRequest&& 18 | new XDomainRequest||new XMLHttpRequest;b.onerror=b.onabort=function(a){u(1,a||b&&b.responseText||{error:"Network Connection Error"})};b.onload=b.onloadend=f;b.onreadystatechange=function(){if(b&&4==b.readyState)switch(b.status){case 200:break;default:try{e=JSON.parse(b.responseText),u(1,e)}catch(a){return u(1,{status:b.status,o:y,message:b.responseText})}}};var G=qa(a.url,J);b.open("GET",G,N);N&&(b.timeout=q);b.send()}catch(Ga){u(0);Za=0;a=db(a);break a}a=u}return a}var s=Ya("script"),f=a.a,B=ma(), 19 | l=0,R=setTimeout(function(){c(1,{message:"timeout"})},a.timeout||1E4),La=a.b||C(),q=a.data||{},Ka=a.c||C();window[f]=function(a){c(0,a)};a.h||(s[eb]=eb);s.onerror=function(){c(1)};s.src=qa(a.url,q);Wa(s,"id",B);Va().appendChild(s);return c},fb=function(){if(!("onLine"in navigator))return 1;try{return navigator.onLine}catch(a){return r}},$a=function(){if(!gb||!gb.get)return 0;var a={id:$a.id++,send:C(),abort:function(){a.id={}},open:function(c,b){$a[a.id]=a;gb.get(a.id,b)}};return a},eb="async",mb= 20 | navigator.userAgent,Za=-1==mb.indexOf("MSIE 6");window.console||(window.console=window.console||{});console.log||(console.log=console.error=(window.opera||{}).postError||C());var nb,ob={},pb=A;try{pb=window.localStorage}catch(qb){}var rb=function(a){return-1==document.cookie.indexOf(a)?y:((document.cookie||"").match(RegExp(a+"=([^;]+)"))||[])[1]||y},sb=function(a,c){document.cookie=a+"="+c+"; expires=Thu, 1 Aug 2030 20:00:00 UTC; path=/"},tb;try{sb("pnctest","1"),tb="1"===rb("pnctest")}catch(ub){tb= 21 | A}nb={get:function(a){try{return pb?pb.getItem(a):tb?rb(a):ob[a]}catch(c){return ob[a]}},set:function(a,c){try{if(pb)return pb.setItem(a,c)&&0;tb&&sb(a,c);ob[a]=c}catch(b){ob[a]=c}}};var vb={list:{},unbind:function(a){vb.list[a]=[]},bind:function(a,c){(vb.list[a]=vb.list[a]||[]).push(c)},fire:function(a,c){$(vb.list[a]||[],function(a){a(c)})}},wb=Ma("pubnub")||0,zb=function(a){function c(){}function b(j,a){function b(a){a&&(Oa=K()-(a/1E4+(K()-d)/2),j&&j(Oa))}var d=K();a&&b(a)||x.time(b)}function e(j, 22 | a){Aa&&Aa(j,a);Aa=y;clearTimeout(X);clearTimeout(Y)}function f(){yb&&x.time(function(j){b(C(),j);j||e(1,{error:"Heartbeat failed to connect to Pubnub Servers.Please check your network settings."});Y&&clearTimeout(Y);Y=setTimeout(f,ab)})}function w(){Ab()||e(1,{error:"Offline. Please check your network settings. "});X&&clearTimeout(X);X=setTimeout(w,F)}function E(j,a,b,d){var a=j.callback||a,c=j.error||m,v=M(),d=d||{};d.auth||(d.auth=j.auth_key||D);j=[O,"v1","channel-registration","sub-key",t];j.push.apply(j, 23 | b);v&&(d.callback=v);L({a:v,data:B(d),c:function(j){p(j,a,c)},b:function(j){q(j,c)},url:j})}function q(j,a){if("object"==typeof j&&j.error){var b={};j.message&&(b.message=j.message);j.payload&&(b.payload=j.payload);a&&a(b)}else a&&a(j)}function p(j,a,b){if("object"==typeof j){if(j.error){a={};j.message&&(a.message=j.message);j.payload&&(a.payload=j.payload);b&&b(a);return}if(j.payload){j.next_page?a&&a(j.payload,j.next_page):a&&a(j.payload);return}}a&&a(j)}function ba(j){var a=0;$(ya(H),function(b){if(b= 24 | H[b])a++,(j||C())(b)});return a}function J(a){var b=0;$(za(S),function(c){if(c=S[c])b++,(a||C())(c)})}function g(a){if(Bb){if(!V.length)return}else{a&&(V.l=0);if(V.l||!V.length)return;V.l=1}L(V.shift())}function N(){!Pa&&u()}function u(){clearTimeout(Ba);!P||500<=P||1>P||!ya(H,r).length&&!za(S,r).length?Pa=A:(Pa=r,x.presence_heartbeat({callback:function(){Ba=setTimeout(u,P*F)},error:function(a){m&&m("Presence Heartbeat unable to reach Pubnub servers."+JSON.stringify(a));Ba=setTimeout(u,P*F)}}))}function G(a, 25 | b){return Ca.decrypt(a,b||ja)||Ca.decrypt(a,ja)||a}function Ga(a,b,c){var d=A;if("undefined"===typeof a)return b;if("number"===typeof a)d=5 5 or x = 0). Current Value : "+(b||5)),b||5):a}function s(a){var b="",c=[];$(a,function(a){c.push(a)});var d=c.sort(),i;for(i in d){var v=d[i],b=b+(v+"="+xa(a[v]));i!=d.length-1&&(b+="&")}return b}function B(a){a||(a={});$(hb,function(b, 26 | c){b in a||(a[b]=c)});return a}function l(a){return zb(a)}Za=a.jsonp?0:-1==mb.indexOf("MSIE 6");var R=a.subscribe_key||"";a.uuid||nb.get(R+"uuid");var La=a.leave_on_unload||0;a.xdr=db;a.db=nb;a.error=a.error||Na;a._is_online=fb;a.jsonp_cb=cb;a.hmac_SHA256=Ja;a.crypto_obj=Ia();a.params={pnsdk:"PubNub-JS-Web/3.7.13"};var Ka=+a.windowing||10,xb=(+a.timeout||310)*F,ab=(+a.keepalive||60)*F,yb=a.timecheck||0,bb=a.noleave||0,Q=a.publish_key||"demo",t=a.subscribe_key||"demo",D=a.auth_key||"",Da=a.secret_key|| 27 | "",ib=a.hmac_SHA256,sa=a.ssl?"s":"",ka="http"+sa+"://"+(a.origin||"pubsub.pubnub.com"),O=na(ka),jb=na(ka),V=[],Qa=r,Oa=0,Ra=0,kb=0,Aa=0,Ea=a.restore||0,ga=0,Sa=A,H={},S={},W={},Ba=y,T=Ga(a.heartbeat||a.pnexpires||0,a.error),P=a.heartbeat_interval||T/2-1,Pa=A,Bb=a.no_wait_for_pending,lb=a["compatible_3.5"]||A,L=a.xdr,hb=a.params||{},m=a.error||C(),Ab=a._is_online||function(){return 1},M=a.jsonp_cb||function(){return 0},ha=a.db||{get:C(),set:C()},ja=a.cipher_key,I=a.uuid||!a.unique_uuid&&ha&&ha.get(t+ 28 | "uuid")||"",Z=a.instance_id||A,U="",X,Y;2===T&&(P=1);var Ca=a.crypto_obj||{encrypt:function(a){return a},decrypt:function(a){return a}},x={LEAVE:function(a,b,c,d,i){var c={uuid:I,auth:c||D},v=na(ka),d=d||C(),z=i||C(),i=M();if(0b.indexOf("-pnpres"))&&(b+="-pnpres"),c=S[b]||H[b]||{callback:C()}):c=H[a];a=[c.a||Ra,a.split(ea)[0]];b&&a.push(b.split(ea)[0]);return a};var h=K()-Oa-+a[1]/1E4;$(a[0],function(c){var j=b(),c=G(c,H[j[1]]?H[j[1]].cipher_key:y);j[0]&&j[0](c,a,j[2]||j[1],h,j[1])})}setTimeout(d,Q)}})}}var i=a.channel,v=a.channel_group,b=(b=b||a.callback)||a.message,z=a.connect||C(),h=a.reconnect||C(),g=a.disconnect|| 43 | C(),p=a.error||p||C(),s=a.idle||C(),l=a.presence||0,u=a.noheresync||0,w=a.backfill||0,E=a.timetoken||0,P=a.timeout||xb,Q=a.windowing||Ka,R=a.state,V=a.heartbeat||a.pnexpires,X=a.heartbeat_interval,Y=a.restore||Ea;D=a.auth_key||D;Ea=Y;ga=E;if(!i&&!v)return m("Missing Channel");if(!b)return m("Missing Callback");if(!t)return m("Missing Subscribe Key");(V||0===V||X||0===X)&&x.set_heartbeat(V,X);i&&$((i.join?i.join(","):""+i).split(","),function(c){var d=H[c]||{};H[kb=c]={name:c,f:d.f,d:d.d,e:1,a:Ra= 44 | b,cipher_key:a.cipher_key,i:z,j:g,k:h};R&&(W[c]=c in R?R[c]:R);l&&(x.subscribe({channel:c+ea,callback:l,restore:Y}),!d.e&&!u&&x.here_now({channel:c,data:B({uuid:I,auth:D}),callback:function(a){$("uuids"in a?a.uuids:[],function(b){l({action:"join",uuid:b,timestamp:Math.floor(K()/1E3),occupancy:a.occupancy||1},a,c)})}}))});v&&$((v.join?v.join(","):""+v).split(","),function(c){var d=S[c]||{};S[c]={name:c,f:d.f,d:d.d,e:1,a:Ra=b,cipher_key:a.cipher_key,i:z,j:g,k:h};l&&(x.subscribe({channel_group:c+ea, 45 | callback:l,restore:Y,auth_key:D}),!d.e&&!u&&x.here_now({channel_group:c,data:B({uuid:I,auth:D}),callback:function(a){$("uuids"in a?a.uuids:[],function(b){l({action:"join",uuid:b,timestamp:Math.floor(K()/1E3),occupancy:a.occupancy||1},a,c)})}}))});c=function(){e();setTimeout(d,Q)};if(!ca)return da.push(c);c()},here_now:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.auth_key||D,i=a.channel,e=a.channel_group,f=M(),h=a.state,d={uuid:I,auth:d};if(!("uuids"in a?a.uuids:1))d.disable_uuids=1;h&&(d.state= 46 | 1);if(!b)return m("Missing Callback");if(!t)return m("Missing Subscribe Key");h=[O,"v2","presence","sub_key",t];i&&h.push("channel")&&h.push(encodeURIComponent(i));"0"!=f&&(d.callback=f);e&&(d["channel-group"]=e,!i&&h.push("channel")&&h.push(","));Z&&(d.instanceid=U);L({a:f,data:B(d),c:function(a){p(a,b,c)},b:function(a){q(a,c)},url:h})},where_now:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.auth_key||D,i=M(),e=a.uuid||I,d={auth:d};if(!b)return m("Missing Callback");if(!t)return m("Missing Subscribe Key"); 47 | "0"!=i&&(d.callback=i);Z&&(d.instanceid=U);L({a:i,data:B(d),c:function(a){p(a,b,c)},b:function(a){q(a,c)},url:[O,"v2","presence","sub_key",t,"uuid",encodeURIComponent(e)]})},state:function(a,b){var b=a.callback||b||C(),c=a.error||C(),d=a.auth_key||D,i=M(),e=a.state,f=a.uuid||I,h=a.channel,g=a.channel_group,d=B({auth:d});if(!t)return m("Missing Subscribe Key");if(!f)return m("Missing UUID");if(!h&&!g)return m("Missing Channel");"0"!=i&&(d.callback=i);"undefined"!=typeof h&&H[h]&&H[h].e&&e&&(W[h]=e); 48 | "undefined"!=typeof g&&(S[g]&&S[g].e)&&(e&&(W[g]=e),d["channel-group"]=g,h||(h=","));d.state=JSON.stringify(e);Z&&(d.instanceid=U);e=e?[O,"v2","presence","sub-key",t,"channel",h,"uuid",f,"data"]:[O,"v2","presence","sub-key",t,"channel",h,"uuid",encodeURIComponent(f)];L({a:i,data:B(d),c:function(a){p(a,b,c)},b:function(a){q(a,c)},url:e})},grant:function(a,b){var b=a.callback||b,c=a.error||C(),d=a.channel||a.channels,e=a.channel_group,f=M(),g=a.ttl,h=a.read?"1":"0",l=a.write?"1":"0",x=a.manage?"1": 49 | "0",u=a.auth_key||a.auth_keys;if(!b)return m("Missing Callback");if(!t)return m("Missing Subscribe Key");if(!Q)return m("Missing Publish Key");if(!Da)return m("Missing Secret Key");var w=t+"\n"+Q+"\ngrant\n",h={w:l,r:h,timestamp:Math.floor((new Date).getTime()/1E3)};a.manage&&(h.m=x);va(d)&&(d=d.join(","));va(u)&&(u=u.join(","));"undefined"!=typeof d&&(d!=y&&0T&&(d.heartbeat=T);"0"!=a&&(d.callback=a);var e;e=ya(H,r).join(",");e=encodeURIComponent(e);var f=za(S,r).join(",");e||(e=",");f&& 54 | (d["channel-group"]=f);Z&&(d.instanceid=U);L({a:a,data:B(d),timeout:5*F,url:[O,"v2","presence","sub-key",t,"channel",e,"heartbeat"],c:function(a){p(a,b,c)},b:function(a){q(a,c)}})},stop_timers:function(){clearTimeout(X);clearTimeout(Y)},xdr:L,ready:Ha,db:ha,uuid:pa,map:wa,each:$,"each-channel":ba,grep:ta,offline:function(){e(1,{message:"Offline. Please check your network settings."})},supplant:ua,now:K,unique:ma,updater:ra};I||(I=x.uuid());U||(U=x.uuid());ha.set(t+"uuid",I);X=setTimeout(w,F);Y=setTimeout(f, 55 | ab);Ba=setTimeout(N,(P-3)*F);b();var R=x,Fa;for(Fa in R)R.hasOwnProperty(Fa)&&(l[Fa]=R[Fa]);l.css=Xa;l.$=Ma;l.create=Ya;l.bind=Ua;l.head=Va;l.search=Ta;l.attr=Wa;l.events=vb;l.init=l;l.secure=l;l.crypto_obj=Ia();Ua("beforeunload",window,function(){if(La)l["each-channel"](function(a){l.LEAVE(a.name,0)});return r});if(a.notest)return l;Ua("offline",window,l.offline);Ua("offline",document,l.offline);return l};zb.init=zb;zb.secure=zb;zb.crypto_obj=Ia();"complete"===document.readyState?setTimeout(Ha,0): 56 | Ua("load",window,function(){setTimeout(Ha,0)});var Cb=wb||{};PUBNUB=zb({notest:1,publish_key:Wa(Cb,"pub-key"),subscribe_key:Wa(Cb,"sub-key"),ssl:!document.location.href.indexOf("https")||"on"==Wa(Cb,"ssl"),origin:Wa(Cb,"origin"),uuid:Wa(Cb,"uuid")});window.jQuery&&(window.jQuery.PUBNUB=zb);"undefined"!==typeof module&&(module.exports=PUBNUB)&&Ha();var gb=Ma("pubnubs")||0;if(wb){Xa(wb,{position:"absolute",top:-F});if("opera"in window||Wa(wb,"flash"))wb.innerHTML=""; 57 | PUBNUB.rdx=function(a,c){if(!c)return $a[a].onerror();$a[a].responseText=unescape(c);$a[a].onload()};$a.id=F}} 58 | var Db=PUBNUB.ws=function(a,c){if(!(this instanceof Db))return new Db(a,c);var b=this,a=b.url=a||"";b.protocol=c||"Sec-WebSocket-Protocol";var e=a.split("/"),e={ssl:"wss:"===e[0],origin:e[2],publish_key:e[3],subscribe_key:e[4],channel:e[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= 59 | 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:r}),b;b.g=PUBNUB.init(e);b.g.n=e;b.n=e;b.g.subscribe({restore:A,channel:e.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()}})}; 60 | Db.prototype.send=function(a){var c=this;c.g.publish({channel:c.g.n.channel,message:a,callback:function(a){c.onsend({data:a})}})}; 61 | })(); 62 | /* 63 | CryptoJS v3.1.2 64 | code.google.com/p/crypto-js 65 | (c) 2009-2013 by Jeff Mott. All rights reserved. 66 | code.google.com/p/crypto-js/wiki/License 67 | */ 68 | 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)}}, 69 | 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>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>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<< 70 | 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>>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>>3]|=parseInt(a.substr(b, 71 | 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>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;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)))}}, 72 | 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;gn;){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]= 78 | 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; 79 | 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); 80 | 81 | // HMAC SHA256 82 | (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>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w< 87 | l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})(); 88 | 89 | // BlockCipher 90 | (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])}, 91 | _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]), 92 | f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f, 93 | m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m, 94 | E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/ 95 | 4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math); 96 | (function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a, 104 | this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684, 105 | 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})}, 106 | decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d, 107 | b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}(); 108 | 109 | // AES 110 | (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8, 111 | 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;dd||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>> 112 | 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t= 113 | d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})(); 114 | 115 | // Mode ECB 116 | CryptoJS.mode.ECB = (function () { 117 | var ECB = CryptoJS.lib.BlockCipherMode.extend(); 118 | 119 | ECB.Encryptor = ECB.extend({ 120 | processBlock: function (words, offset) { 121 | this._cipher.encryptBlock(words, offset); 122 | } 123 | }); 124 | 125 | ECB.Decryptor = ECB.extend({ 126 | processBlock: function (words, offset) { 127 | this._cipher.decryptBlock(words, offset); 128 | } 129 | }); 130 | 131 | return ECB; 132 | }());// Moved to hmac-sha-256.js -------------------------------------------------------------------------------- /lib/webrtc.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 3 | 4 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 5 | // WebRTC Simple Calling API + Mobile 6 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 7 | var PHONE = window.PHONE = function(config) { 8 | var PHONE = function(){}; 9 | var pubnub = PUBNUB(config); 10 | var pubkey = config.publish_key || 'demo'; 11 | var snapper = function(){ return ' ' } 12 | var subkey = config.subscribe_key || 'demo'; 13 | var sessionid = PUBNUB.uuid(); 14 | var mystream = null; 15 | var myvideo = document.createElement('video'); 16 | var myconnection = false; 17 | var mediaconf = config.media || { audio : true, video : true }; 18 | var conversations = {}; 19 | 20 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 21 | // RTC Peer Connection Session (one per call) 22 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 23 | var PeerConnection = 24 | window.RTCPeerConnection || 25 | window.mozRTCPeerConnection || 26 | window.webkitRTCPeerConnection; 27 | 28 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 29 | // ICE (many route options per call) 30 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 31 | var IceCandidate = 32 | window.mozRTCIceCandidate || 33 | window.RTCIceCandidate; 34 | 35 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 36 | // Media Session Description (offer and answer per call) 37 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 38 | var SessionDescription = 39 | window.RTCSessionDescription || 40 | window.mozRTCSessionDescription || 41 | window.webkitRTCSessionDescription; 42 | 43 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 44 | // Local Microphone and Camera Media (one per device) 45 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 46 | navigator.getUserMedia = 47 | navigator.getUserMedia || 48 | navigator.webkitGetUserMedia || 49 | navigator.mozGetUserMedia || 50 | navigator.msGetUserMedia; 51 | 52 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 53 | // STUN Server List Configuration (public STUN list) 54 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 55 | var rtcconfig = { iceServers : [{ "url" : 56 | navigator.mozGetUserMedia ? "stun:stun.services.mozilla.com" : 57 | navigator.webkitGetUserMedia ? "stun:stun.l.google.com:19302" : 58 | "stun:23.21.150.121" 59 | }, 60 | {url: "stun:stun.l.google.com:19302"}, 61 | {url: "stun:stun1.l.google.com:19302"}, 62 | {url: "stun:stun2.l.google.com:19302"}, 63 | {url: "stun:stun3.l.google.com:19302"}, 64 | {url: "stun:stun4.l.google.com:19302"}, 65 | {url: "stun:23.21.150.121"}, 66 | {url: "stun:stun01.sipphone.com"}, 67 | {url: "stun:stun.ekiga.net"}, 68 | {url: "stun:stun.fwdnet.net"}, 69 | {url: "stun:stun.ideasip.com"}, 70 | {url: "stun:stun.iptel.org"}, 71 | {url: "stun:stun.rixtelecom.se"}, 72 | {url: "stun:stun.schlund.de"}, 73 | {url: "stun:stunserver.org"}, 74 | {url: "stun:stun.softjoys.com"}, 75 | {url: "stun:stun.voiparound.com"}, 76 | {url: "stun:stun.voipbuster.com"}, 77 | {url: "stun:stun.voipstunt.com"}, 78 | {url: "stun:stun.voxgratia.org"}, 79 | {url: "stun:stun.xten.com"} 80 | ] }; 81 | 82 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 83 | // Custom STUN Options 84 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 85 | function add_servers(servers) { 86 | if (servers.constructor === Array) 87 | [].unshift.apply(rtcconfig.iceServers, servers); 88 | else rtcconfig.iceServers.unshift(servers); 89 | } 90 | 91 | if ('servers' in config) add_servers(config.servers); 92 | 93 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 94 | // PHONE Events 95 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 96 | var messagecb = function(){}; 97 | var readycb = function(){}; 98 | var unablecb = function(){}; 99 | var debugcb = function(){}; 100 | var connectcb = function(){}; 101 | var disconnectcb = function(){}; 102 | var reconnectcb = function(){}; 103 | var callstatuscb = function(){}; 104 | var receivercb = function(){}; 105 | 106 | PHONE.message = function(cb) { messagecb = cb }; 107 | PHONE.ready = function(cb) { readycb = cb }; 108 | PHONE.unable = function(cb) { unablecb = cb }; 109 | PHONE.callstatus = function(cb) { callstatuscb = cb }; 110 | PHONE.debug = function(cb) { debugcb = cb }; 111 | PHONE.connect = function(cb) { connectcb = cb }; 112 | PHONE.disconnect = function(cb) { disconnectcb = cb }; 113 | PHONE.reconnect = function(cb) { reconnectcb = cb }; 114 | PHONE.receive = function(cb) { receivercb = cb }; 115 | 116 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 117 | // Add/Get Conversation - Creates a new PC or Returns Existing PC 118 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 119 | function get_conversation(number) { 120 | var talk = conversations[number] || (function(number){ 121 | var talk = { 122 | number : number, 123 | status : '', 124 | image : document.createElement('img'), 125 | started : +new Date, 126 | imgset : false, 127 | imgsent : 0, 128 | pc : new PeerConnection(rtcconfig), 129 | closed : false, 130 | usermsg : function(){}, 131 | thumb : null, 132 | connect : function(){}, 133 | end : function(){} 134 | }; 135 | 136 | // Setup Event Methods 137 | talk.pc.onaddstream = config.onaddstream || onaddstream; 138 | talk.pc.onicecandidate = onicecandidate; 139 | talk.pc.number = number; 140 | 141 | // Disconnect and Hangup 142 | talk.hangup = function(signal) { 143 | if (talk.closed) return; 144 | 145 | talk.closed = true; 146 | talk.imgset = false; 147 | clearInterval(talk.snapi); 148 | 149 | if (signal !== false) transmit( number, { hangup : true } ); 150 | 151 | talk.end(talk); 152 | talk.pc.close(); 153 | close_conversation(number); 154 | }; 155 | 156 | // Sending Messages 157 | talk.send = function(message) { 158 | transmit( number, { usermsg : message } ); 159 | }; 160 | 161 | // Sending Stanpshots 162 | talk.snap = function() { 163 | var pic = snapper(); 164 | if (talk.closed) clearInterval(talk.snapi); 165 | transmit( number, { thumbnail : pic } ); 166 | var img = document.createElement('img'); 167 | img.src = pic; 168 | return { data : pic, image : img }; 169 | }; 170 | talk.snapi = setInterval( function() { 171 | if (talk.imgsent++ > 5) return clearInterval(talk.snapi); 172 | talk.snap(); 173 | }, 1500 ); 174 | talk.snap(); 175 | 176 | // Nice Accessor to Update Disconnect & Establis CBs 177 | talk.thumbnail = function(cb) {talk.thumb = cb; return talk}; 178 | talk.ended = function(cb) {talk.end = cb; return talk}; 179 | talk.connected = function(cb) {talk.connect = cb; return talk}; 180 | talk.message = function(cb) {talk.usermsg = cb; return talk}; 181 | 182 | // Add Local Media Streams Audio Video Mic Camera 183 | talk.pc.addStream(mystream); 184 | 185 | // Notify of Call Status 186 | update_conversation( talk, 'connecting' ); 187 | 188 | // Return Brand New Talk Reference 189 | conversations[number] = talk; 190 | return talk; 191 | })(number); 192 | 193 | // Return Existing or New Reference to Caller 194 | return talk; 195 | } 196 | 197 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 198 | // Remove Conversation 199 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 200 | function close_conversation(number) { 201 | conversations[number] = null; 202 | delete conversations[number]; 203 | } 204 | 205 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 206 | // Notify of Call Status Events 207 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 208 | function update_conversation( talk, status ) { 209 | talk.status = status; 210 | callstatuscb(talk); 211 | return talk; 212 | } 213 | 214 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 215 | // Get Number 216 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 217 | PHONE.number = function() { 218 | return config.number; 219 | }; 220 | 221 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 222 | // Get Call History 223 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 224 | PHONE.history = function(settings) { 225 | pubnub.history({ 226 | channel : settings[number], 227 | callback : function(call_history) { 228 | settings['history'](call_history[0]); 229 | } 230 | }) 231 | }; 232 | 233 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 234 | // Make Call - Create new PeerConnection 235 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 236 | PHONE.dial = function(number, servers) { 237 | if (!!servers) add_servers(servers); 238 | var talk = get_conversation(number); 239 | var pc = talk.pc; 240 | 241 | // Prevent Repeat Calls 242 | if (talk.dialed) return false; 243 | talk.dialed = true; 244 | 245 | // Send SDP Offer (Call) 246 | pc.createOffer( function(offer) { 247 | transmit( number, { hangup : true } ); 248 | transmit( number, offer, 2 ); 249 | pc.setLocalDescription( offer, debugcb, debugcb ); 250 | }, debugcb ); 251 | 252 | // Return Session Reference 253 | return talk; 254 | }; 255 | 256 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 257 | // Send Image Snap - Send Image Snap to All Calls or a Specific Call 258 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 259 | PHONE.snap = function( message, number ) { 260 | if (number) return get_conversation(number).snap(message); 261 | var pic = {}; 262 | PUBNUB.each( conversations, function( number, talk ) { 263 | pic = talk.snap(); 264 | } ); 265 | return pic; 266 | }; 267 | 268 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 269 | // Send Message - Send Message to All Calls or a Specific Call 270 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 271 | PHONE.send = function( message, number ) { 272 | if (number) return get_conversation(number).send(message); 273 | PUBNUB.each( conversations, function( number, talk ) { 274 | talk.send(message); 275 | } ); 276 | }; 277 | 278 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 279 | // End Call - Close All Calls or a Specific Call 280 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 281 | PHONE.hangup = function(number) { 282 | if (number) return get_conversation(number).hangup(); 283 | PUBNUB.each( conversations, function( number, talk ) { 284 | talk.hangup(); 285 | } ); 286 | }; 287 | 288 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 289 | // Auto-hangup on Leave 290 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 291 | PUBNUB.bind( 'unload,beforeunload', window, function() { 292 | if (PHONE.goodbye) return true; 293 | PHONE.goodbye = true; 294 | 295 | PUBNUB.each( conversations, function( number, talk ) { 296 | var mynumber = config.number; 297 | var packet = { hangup:true }; 298 | var message = { packet:packet, id:sessionid, number:mynumber }; 299 | var client = new XMLHttpRequest(); 300 | var url = 'http://pubsub.pubnub.com/publish/' 301 | + pubkey + '/' 302 | + subkey + '/0/' 303 | + number + '/0/' 304 | + JSON.stringify(message); 305 | 306 | client.open( 'GET', url, false ); 307 | client.send(); 308 | talk.hangup(); 309 | } ); 310 | 311 | return true; 312 | } ); 313 | 314 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 315 | // Grab Local Video Snapshot 316 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 317 | function snapshots_setup(stream) { 318 | var video = myvideo; 319 | var canvas = document.createElement('canvas'); 320 | var context = canvas.getContext("2d"); 321 | var snap = { width: 240, height: 180 }; 322 | 323 | // Video Settings 324 | video.width = snap.width; 325 | video.height = snap.height; 326 | video.src = URL.createObjectURL(stream); 327 | video.volume = 0.0; 328 | video.play(); 329 | 330 | // Canvas Settings 331 | canvas.width = snap.width; 332 | canvas.height = snap.height; 333 | 334 | // Capture Local Pic 335 | snapper = function() { 336 | try { 337 | context.drawImage( video, 0, 0, snap.width, snap.height ); 338 | } catch(e) {} 339 | return canvas.toDataURL( 'image/jpeg', 0.30 ); 340 | }; 341 | 342 | PHONE.video = video; 343 | } 344 | 345 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 346 | // Visually Display New Stream 347 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 348 | function onaddstream(obj) { 349 | var vid = document.createElement('video'); 350 | var stream = obj.stream; 351 | var number = (obj.srcElement || obj.target).number; 352 | var talk = get_conversation(number); 353 | 354 | vid.setAttribute( 'autoplay', 'autoplay' ); 355 | vid.src = URL.createObjectURL(stream); 356 | 357 | talk.video = vid; 358 | talk.connect(talk); 359 | } 360 | 361 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 362 | // On ICE Route Candidate Discovery 363 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 364 | function onicecandidate(event) { 365 | if (!event.candidate) return; 366 | transmit( this.number, event.candidate ); 367 | }; 368 | 369 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 370 | // Listen For New Incoming Calls 371 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 372 | function subscribe() { 373 | pubnub.subscribe({ 374 | restore : true, 375 | channel : config.number, 376 | message : receive, 377 | disconnect : disconnectcb, 378 | reconnect : reconnectcb, 379 | connect : function() { onready(true) } 380 | }); 381 | } 382 | 383 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 384 | // When Ready to Receive Calls 385 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 386 | function onready(subscribed) { 387 | if (subscribed) myconnection = true; 388 | if (!(mystream && myconnection)) return; 389 | 390 | connectcb(); 391 | readycb(); 392 | } 393 | 394 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 395 | // Prepare Local Media Camera and Mic 396 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 397 | function getusermedia() { 398 | navigator.getUserMedia( mediaconf, function(stream) { 399 | if (!stream) return unablecb(stream); 400 | mystream = stream; 401 | snapshots_setup(stream); 402 | onready(); 403 | subscribe(); 404 | }, function(info) { 405 | debugcb(info); 406 | return unablecb(info); 407 | } ); 408 | } 409 | 410 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 411 | // Send SDP Call Offers/Answers and ICE Candidates to Peer 412 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 413 | function transmit( phone, packet, times, time ) { 414 | if (!packet) return; 415 | var number = config.number; 416 | var message = { packet : packet, id : sessionid, number : number }; 417 | debugcb(message); 418 | pubnub.publish({ channel : phone, message : message }); 419 | 420 | // Recurse if Requested for 421 | if (!times) return; 422 | time = time || 1; 423 | if (time++ >= times) return; 424 | setTimeout( function(){ 425 | transmit( phone, packet, times, time ); 426 | }, 150 ); 427 | } 428 | 429 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 430 | // SDP Offers & ICE Candidates Receivable Processing 431 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 432 | function receive(message) { 433 | // Debug Callback of Data to Watch 434 | debugcb(message); 435 | 436 | // Get Call Reference 437 | var talk = get_conversation(message.number); 438 | 439 | // Ignore if Closed 440 | if (talk.closed) return; 441 | 442 | // User Message 443 | if (message.packet.usermsg) { 444 | messagecb( talk, message.packet.usermsg ); 445 | return talk.usermsg( talk, message.packet.usermsg ); 446 | } 447 | 448 | // Thumbnail Preview Image 449 | if (message.packet.thumbnail) return create_thumbnail(message); 450 | 451 | // If Hangup Request 452 | if (message.packet.hangup) return talk.hangup(false); 453 | 454 | // If Peer Calling Inbound (Incoming) 455 | if ( message.packet.sdp && !talk.received ) { 456 | talk.received = true; 457 | receivercb(talk); 458 | } 459 | 460 | // Update Peer Connection with SDP Offer or ICE Routes 461 | if (message.packet.sdp) add_sdp_offer(message); 462 | else add_ice_route(message); 463 | } 464 | 465 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 466 | // Create Remote Friend Thumbnail 467 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 468 | function create_thumbnail(message) { 469 | var talk = get_conversation(message.number); 470 | talk.image.src = message.packet.thumbnail; 471 | 472 | // Call only once 473 | if (!talk.thumb) return; 474 | if (!talk.imgset) talk.thumb(talk); 475 | talk.imgset = true; 476 | } 477 | 478 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 479 | // Add SDP Offer/Answers 480 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 481 | function add_sdp_offer(message) { 482 | // Get Call Reference 483 | var talk = get_conversation(message.number); 484 | var pc = talk.pc; 485 | var type = message.packet.type == 'offer' ? 'offer' : 'answer'; 486 | 487 | // Deduplicate SDP Offerings/Answers 488 | if (type in talk) return; 489 | talk[type] = true; 490 | talk.dialed = true; 491 | 492 | // Notify of Call Status 493 | update_conversation( talk, 'routing' ); 494 | 495 | // Add SDP Offer/Answer 496 | pc.setRemoteDescription( 497 | new SessionDescription(message.packet), function() { 498 | // Set Connected Status 499 | update_conversation( talk, 'connected' ); 500 | 501 | // Call Online and Ready 502 | if (pc.remoteDescription.type != 'offer') return; 503 | 504 | // Create Answer to Call 505 | pc.createAnswer( function(answer) { 506 | pc.setLocalDescription( answer, debugcb, debugcb ); 507 | transmit( message.number, answer, 2 ); 508 | }, debugcb ); 509 | }, debugcb 510 | ); 511 | } 512 | 513 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 514 | // Add ICE Candidate Routes 515 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 516 | function add_ice_route(message) { 517 | // Leave if Non-good ICE Packet 518 | if (!message.packet) return; 519 | if (!message.packet.candidate) return; 520 | 521 | // Get Call Reference 522 | var talk = get_conversation(message.number); 523 | var pc = talk.pc; 524 | 525 | // Add ICE Candidate Routes 526 | pc.addIceCandidate( 527 | new IceCandidate(message.packet), 528 | debugcb, 529 | debugcb 530 | ); 531 | } 532 | 533 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 534 | // Main - Request Camera and Mic 535 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 536 | getusermedia() 537 | 538 | return PHONE; 539 | }; 540 | 541 | 542 | })(); 543 | --------------------------------------------------------------------------------