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