check in raw verto js files and directory structure for development

This commit is contained in:
Anthony Minessale 2014-06-21 05:26:06 +05:00
parent 011e069652
commit f95acb5a53
40 changed files with 16605 additions and 0 deletions

3
html5/verto/README Normal file
View File

@ -0,0 +1,3 @@
This needs to be fleshed out more.
It would be nice to develop a web app here that implements a web phone.
Do not base it on the demo because it was just tossed together.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,64 @@
.nthChildTest > div:nth-child(odd) {
display: none;
}
.jsDataTable > thead > tr > th {
border-width:4px;
padding: 2px;
font-size:10pt;
text-align: left;
}
.jsDataTable > thead > tr > th.notSortable {
padding: 5px;
}
.jsDataTable > tbody > tr > td {
border-bottom: 1px solid #ccc;
padding: 2px;
vertical-align: middle;
}
.jsDataTable {
font-family: verdana;
font-size:10pt;
}
.jsDataTable > tbody > tr:nth-child(odd),
.jsDataTable > tbody > tr.odd {
background-color: #ffffee;
}
.jsDataTable > tbody > tr:nth-child(even),
.jsDataTable > tbody > tr.even {
background-color: #ffffff;
}
.jsDataTable > thead th.sortAsc,
.jsDataTable > thead th.sortDesc {
color:ffffff;
background-color: #7777ff;
background-position: right center;
background-repeat: no-repeat;
}
.jsDataTable > thead th.sortAsc {
background-image: url(/images/table/asc.png);
}
.jsDataTable > thead th.sortDesc {
background-image: url(/images/table/desc.png);
}
.jsDataTable.clickable > tbody > tr,
.clickable > .jsDataTable > tbody > tr {
cursor: pointer;
}
.jsDataTable.clickable > tbody > tr.nonDataRow,
.clickable > .jsDataTable > tbody > tr.nonDataRow {
cursor: auto;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,5 @@
<script>
document.location = "https://" + window.location.hostname + "/verto/verto.html";
</script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,90 @@
/*!
* jQuery Cookie Plugin v1.3.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function ($, document, undefined) {
var pluses = /\+/g;
function raw(s) {
return s;
}
function decoded(s) {
return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
}
function unRfc2068(value) {
if (value.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape
value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
return value;
}
function fromJSON(value) {
return config.json ? JSON.parse(value) : value;
}
var config = $.cookie = function (key, value, options) {
// write
if (value !== undefined) {
options = $.extend({}, config.defaults, options);
if (value === null) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = config.json ? JSON.stringify(value) : String(value);
return (document.cookie = [
encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// read
var decode = config.raw ? raw : decoded;
var cookies = document.cookie.split('; ');
var result = key ? null : {};
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = decode(parts.join('='));
if (key && key === name) {
result = fromJSON(cookie);
break;
}
if (!key) {
result[name] = fromJSON(cookie);
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== null) {
$.cookie(key, null, options);
return true;
}
return false;
};
})(jQuery, document);

12099
html5/verto/demo/js/jquery.dataTables.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
/*! jQuery JSON plugin 2.4.0 | code.google.com/p/jquery-json */
(function($){'use strict';var escape=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},hasOwn=Object.prototype.hasOwnProperty;$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';}
var pairs,k,name,val,type=$.type(o);if(type==='undefined'){return undefined;}
if(type==='number'||type==='boolean'){return String(o);}
if(type==='string'){return $.quoteString(o);}
if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());}
if(type==='date'){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;}
if(day<10){day='0'+day;}
if(hours<10){hours='0'+hours;}
if(minutes<10){minutes='0'+minutes;}
if(seconds<10){seconds='0'+seconds;}
if(milli<100){milli='0'+milli;}
if(milli<10){milli='0'+milli;}
return'"'+year+'-'+month+'-'+day+'T'+
hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
pairs=[];if($.isArray(o)){for(k=0;k<o.length;k++){pairs.push($.toJSON(o[k])||'null');}
return'['+pairs.join(',')+']';}
if(typeof o==='object'){for(k in o){if(hasOwn.call(o,k)){type=typeof k;if(type==='number'){name='"'+k+'"';}else if(type==='string'){name=$.quoteString(k);}else{continue;}
type=typeof o[k];if(type!=='function'&&type!=='undefined'){val=$.toJSON(o[k]);pairs.push(name+':'+val);}}}
return'{'+pairs.join(',')+'}';}};$.evalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){return eval('('+str+')');};$.secureEvalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){var filtered=str.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered)){return eval('('+str+')');}
throw new SyntaxError('Error parsing JSON, source is not valid.');};$.quoteString=function(str){if(str.match(escape)){return'"'+str.replace(escape,function(a){var c=meta[a];if(typeof c==='string'){return c;}
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
return'"'+str+'"';};}(jQuery));

File diff suppressed because one or more lines are too long

196
html5/verto/demo/js/verto-min.js vendored Normal file
View File

@ -0,0 +1,196 @@
(function($){function findLine(sdpLines,prefix,substr){return findLineInRange(sdpLines,0,-1,prefix,substr);}
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;}
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 setDefaultCodec(mLine,payload){var elements=mLine.split(' ');var newLine=[];var index=0;for(var i=0;i<elements.length;i++){if(index===3)
newLine[index++]=payload;if(elements[i]!==payload)newLine[index++]=elements[i];}
return newLine.join(' ');}
$.FSRTC=function(options){this.options=$.extend({useVideo:null,useStereo:false,userData:null,videoParams:{},callbacks:{onICEComplete:function(){},onICE:function(){},onOfferSDP:function(){}}},options);this.mediaData={SDP:null,profile:{},candidateList:[]};this.constraints={optional:[{'DtlsSrtpKeyAgreement':'true'}],mandatory:{OfferToReceiveAudio:true,OfferToReceiveVideo:this.options.useVideo?true:false,}};if(self.options.useVideo){self.options.useVideo.style.display='none';}
setCompat();checkCompat();};$.FSRTC.prototype.useVideo=function(obj){var self=this;if(obj){self.options.useVideo=obj;self.constraints.mandatory.OfferToReceiveVideo=true;}else{self.options.useVideo=null;self.constraints.mandatory.OfferToReceiveVideo=false;}
if(self.options.useVideo){self.options.useVideo.style.display='none';}};$.FSRTC.prototype.useStereo=function(on){var self=this;self.options.useStereo=on;};$.FSRTC.prototype.stereoHack=function(sdp){var self=this;if(!self.options.useStereo){return sdp;}
var sdpLines=sdp.split('\r\n');var opusIndex=findLine(sdpLines,'a=rtpmap','opus/48000'),opusPayload;if(opusIndex){opusPayload=getCodecPayloadType(sdpLines[opusIndex]);}
var fmtpLineIndex=findLine(sdpLines,'a=fmtp:'+opusPayload.toString());if(fmtpLineIndex===null)return sdp;sdpLines[fmtpLineIndex]=sdpLines[fmtpLineIndex].concat('; stereo=1');sdp=sdpLines.join('\r\n');return sdp;};function setCompat(){$.FSRTC.moz=!!navigator.mozGetUserMedia;if(!navigator.getUserMedia){navigator.getUserMedia=navigator.mozGetUserMedia||navigator.webkitGetUserMedia||navigator.msGetUserMedia;}}
function checkCompat(){if(!navigator.getUserMedia){alert('This application cannot function in this browser.');return false;}
return true;}
function onStreamError(self){console.log('There has been a problem retrieving the streams - did you allow access?');}
function onStreamSuccess(self){console.log("Stream Success");}
function onICE(self,candidate){self.mediaData.candidate=candidate;self.mediaData.candidateList.push(self.mediaData.candidate);doCallback(self,"onICE");}
function doCallback(self,func,arg){if(func in self.options.callbacks){self.options.callbacks[func](self,arg);}}
function onICEComplete(self,candidate){console.log("ICE Complete");doCallback(self,"onICEComplete");}
function onChannelError(self,e){console.error("Channel Error",e);doCallback(self,"onError",e);}
function onICESDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("ICE SDP");doCallback(self,"onICESDP");}
function onAnswerSDP(self,sdp){self.answer.SDP=self.stereoHack(sdp.sdp);console.log("ICE ANSWER SDP");doCallback(self,"onAnswerSDP",self.answer.SDP);}
function onMessage(self,msg){console.log("Message");doCallback(self,"onICESDP",msg);}
function onRemoteStream(self,stream){if(self.options.useVideo){self.options.useVideo.style.display='block';}
var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);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.error('Error attaching stream to element.');}
self.options.useAudio.play();self.remoteStream=stream;}
function onOfferSDP(self,sdp){self.mediaData.SDP=self.stereoHack(sdp.sdp);console.log("Offer SDP");doCallback(self,"onOfferSDP");}
$.FSRTC.prototype.answer=function(sdp,onSuccess,onError){this.peer.addAnswerSDP({type:"answer",sdp:sdp},onSuccess,onError);};$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';}
if(self.localStream){self.localStream.stop();self.localStream=null;}
if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.createAnswer=function(sdp){var self=this;self.type="answer";self.remoteSDP=sdp;console.debug("inbound sdp: ",sdp);function onSuccess(stream){self.localStream=stream;self.peer=RTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:function(stream){return onRemoteStream(self,stream);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints,offerSDP:{type:"offer",sdp:self.remoteSDP}});onStreamSuccess(self);}
function onError(){onStreamError(self);}
getUserMedia({constraints:{audio:true,video:this.options.useVideo?{mandatory:this.options.videoParams,optional:[]}:null},video:this.options.useVideo?true:false,onsuccess:onSuccess,onerror:onError});};$.FSRTC.prototype.call=function(profile){checkCompat();var self=this;self.type="offer";function onSuccess(stream){self.localStream=stream;self.peer=RTCPeerConnection({type:self.type,attachStream:self.localStream,onICE:function(candidate){return onICE(self,candidate);},onICEComplete:function(){return onICEComplete(self);},onRemoteStream:function(stream){return onRemoteStream(self,stream);},onOfferSDP:function(sdp){return onOfferSDP(self,sdp);},onICESDP:function(sdp){return onICESDP(self,sdp);},onChannelError:function(e){return onChannelError(self,e);},constraints:self.constraints});onStreamSuccess(self);}
function onError(){onStreamError(self);}
getUserMedia({constraints:{audio:true,video:this.options.useVideo?{mandatory:this.options.videoParams,optional:[]}:null},video:this.options.useVideo?true:false,onsuccess:onSuccess,onerror:onError});};window.moz=!!navigator.mozGetUserMedia;function RTCPeerConnection(options){var w=window,PeerConnection=w.mozRTCPeerConnection||w.webkitRTCPeerConnection,SessionDescription=w.mozRTCSessionDescription||w.RTCSessionDescription,IceCandidate=w.mozRTCIceCandidate||w.RTCIceCandidate;var STUN={url:!moz?'stun:stun.l.google.com:19302':'stun:23.21.150.121'};var TURN={url:'turn:homeo@turn.bistri.com:80',credential:'homeo'};var iceServers={iceServers:options.iceServers||[STUN]};if(!moz&&!options.iceServers){if(parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2])>=28)TURN={url:'turn:turn.bistri.com:80',credential:'homeo',username:'homeo'};iceServers.iceServers=[STUN];}
var optional={optional:[]};if(!moz){optional.optional=[{DtlsSrtpKeyAgreement:true},{RtpDataChannels:options.onChannelMessage?true:false}];}
var peer=new PeerConnection(iceServers,optional);openOffererChannel();var x=0;peer.onicecandidate=function(event){console.log("WTF ICE",event);if(event.candidate){options.onICE(event.candidate);}else{if(options.onICEComplete){options.onICEComplete();}
if(options.type=="offer"){if(!moz&&!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}else{if(!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}}};if(options.attachStream)peer.addStream(options.attachStream);if(options.attachStreams&&options.attachStream.length){var streams=options.attachStreams;for(var i=0;i<streams.length;i++){peer.addStream(streams[i]);}}
peer.onaddstream=function(event){var remoteMediaStream=event.stream;remoteMediaStream.onended=function(){if(options.onRemoteStreamEnded)options.onRemoteStreamEnded(remoteMediaStream);};if(options.onRemoteStream)options.onRemoteStream(remoteMediaStream);};var constraints=options.constraints||{optional:[],mandatory:{OfferToReceiveAudio:true,OfferToReceiveVideo:true}};function createOffer(){if(!options.onOfferSDP)return;peer.createOffer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);options.onOfferSDP(sessionDescription);if(moz&&options.onICESDP){options.onICESDP(sessionDescription);}},onSdpError,constraints);}
function createAnswer(){if(options.type!="answer")return;peer.setRemoteDescription(new SessionDescription(options.offerSDP),onSdpSuccess,onSdpError);peer.createAnswer(function(sessionDescription){sessionDescription.sdp=serializeSdp(sessionDescription.sdp);peer.setLocalDescription(sessionDescription);if(options.onAnswerSDP){options.onAnswerSDP(sessionDescription);}},onSdpError,constraints);}
if((options.onChannelMessage&&!moz)||!options.onChannelMessage){createOffer();createAnswer();}
function setBandwidth(sdp){sdp=sdp.replace(/b=AS([^\r\n]+\r\n)/g,'');sdp=sdp.replace(/a=mid:data\r\n/g,'a=mid:data\r\nb=AS:1638400\r\n');return sdp;}
function getInteropSDP(sdp){var chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),extractedChars='';function getChars(){extractedChars+=chars[parseInt(Math.random()*40)]||'';if(extractedChars.length<40)getChars();return extractedChars;}
if(options.onAnswerSDP)sdp=sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g,'');var inline=getChars()+'\r\n'+(extractedChars='');sdp=sdp.indexOf('a=crypto')==-1?sdp.replace(/c=IN/g,'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:'+inline+'c=IN'):sdp;return sdp;}
function serializeSdp(sdp){return sdp;}
var channel;function openOffererChannel(){if(!options.onChannelMessage||(moz&&!options.onOfferSDP))return;_openOffererChannel();if(!moz)return;navigator.mozGetUserMedia({audio:true,fake:true},function(stream){peer.addStream(stream);createOffer();},useless);}
function _openOffererChannel(){channel=peer.createDataChannel(options.channel||'RTCDataChannel',moz?{}:{reliable:false});if(moz)channel.binaryType='blob';setChannelEvents();}
function setChannelEvents(){channel.onmessage=function(event){if(options.onChannelMessage)options.onChannelMessage(event);};channel.onopen=function(){if(options.onChannelOpened)options.onChannelOpened(channel);};channel.onclose=function(event){if(options.onChannelClosed)options.onChannelClosed(event);console.warn('WebRTC DataChannel closed',event);};channel.onerror=function(event){if(options.onChannelError)options.onChannelError(event);console.error('WebRTC DataChannel error',event);};}
if(options.onAnswerSDP&&moz&&options.onChannelMessage)openAnswererChannel();function openAnswererChannel(){peer.ondatachannel=function(event){channel=event.channel;channel.binaryType='blob';setChannelEvents();};if(!moz)return;navigator.mozGetUserMedia({audio:true,fake:true},function(stream){peer.addStream(stream);createAnswer();},useless);}
function useless(){log('Error in fake:true');}
function onSdpSuccess(){}
function onSdpError(e){if(options.onChannelError){options.onChannelError(e);}
console.error('sdp error:',e);}
return{addAnswerSDP:function(sdp,cbSuccess,cbError){peer.setRemoteDescription(new SessionDescription(sdp),cbSuccess?cbSuccess:onSdpSuccess,cbError?cbError:onSdpError);},addICE:function(candidate){peer.addIceCandidate(new IceCandidate({sdpMLineIndex:candidate.sdpMLineIndex,candidate:candidate.candidate}));},peer:peer,channel:channel,sendData:function(message){if(channel){channel.send(message);}},stop:function(){peer.close();if(options.attachStream){options.attachStream.stop();}}};}
var video_constraints={mandatory:{},optional:[]};function getUserMedia(options){var n=navigator,media;n.getMedia=n.webkitGetUserMedia||n.mozGetUserMedia;n.getMedia(options.constraints||{audio:true,video:video_constraints},streaming,options.onerror||function(e){console.error(e);});function streaming(stream){var video=options.video;if(video){video[moz?'mozSrcObject':'src']=moz?stream:window.webkitURL.createObjectURL(stream);}
if(options.onsuccess){options.onsuccess(stream);}
media=stream;}
return media;}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
if(this.options.sessid){params.sessid=this.options.sessid;}
var request={jsonrpc:'2.0',method:method,params:params,id:this._current_id++};if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request,success_cb,error_cb);return;}
if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.call used with no websocket and no http endpoint.";}
$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false,success:function(data){if('error'in data)error_cb(data.error,this);success_cb(data.result,this);},error:function(jqXHR,textStatus,errorThrown){try{var response=$.parseJSON(jqXHR.responseText);if('console'in window)console.log(response);error_cb(response.error,this);}
catch(err){error_cb({error:jqXHR.responseText},this);}}});};$.JsonRpcClient.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
var request={jsonrpc:'2.0',method:method,params:params};var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this._wsCall(socket,request);return;}
if(this.options.ajaxUrl===null){throw"$.JsonRpcClient.notify used with no websocket and no http endpoint.";}
$.ajax({type:'POST',url:this.options.ajaxUrl,data:$.toJSON(request),dataType:'json',cache:false});};$.JsonRpcClient.prototype.batch=function(callback,all_done_cb,error_cb){var batch=new $.JsonRpcClient._batchObject(this,all_done_cb,error_cb);callback(batch);batch._execute();};$.JsonRpcClient.prototype.socketReady=function(){if(this._ws_socket===null||this._ws_socket.readyState>1){return false;}
return true;}
$.JsonRpcClient.prototype.closeSocket=function(){if(self.socketReady()){this._ws_socket.onclose=function(w){console.log("Closing Socket")}
this._ws_socket.close();}}
$.JsonRpcClient.prototype.loginData=function(params){self.options.login=params.login;self.options.passwd=params.passwd;}
$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(!self.socketReady()){self.authing=false;this._ws_socket=new WebSocket(this.options.socketUrl);if(this._ws_socket){this._ws_socket.onmessage=onmessage_cb;this._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=2;}
self.ws_cnt=0;if(self.options.onWSClose){self.options.onWSClose(self);}
console.error("Websocket Lost sleep: "+self.ws_sleep+"sec");setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep*1000);if(++self.ws_cnt>=150){self.ws_sleep=30;}}
this._ws_socket.onopen=function(){this.ws_sleep=2;this.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);}
var req;while(req=$.JsonRpcClient.q.pop()){self._ws_socket.send(req);}}}}
return this._ws_socket?true:false;}
$.JsonRpcClient.prototype._getSocket=function(onmessage_cb){if(this.options.socketUrl===null||!("WebSocket"in window))return null;this.connectSocket(onmessage_cb);return this._ws_socket;};$.JsonRpcClient.q=[];$.JsonRpcClient.prototype._wsCall=function(socket,request,success_cb,error_cb){var request_json=$.toJSON(request);if(socket.readyState<1){self=this;$.JsonRpcClient.q.push(request_json);}
else{socket.send(request_json);}
if('id'in request&&typeof success_cb!=='undefined'){this._ws_callbacks[request.id]={request:request_json,request_obj:request,success_cb:success_cb,error_cb:error_cb};}};$.JsonRpcClient.prototype._wsOnMessage=function(event){var response;try{response=$.parseJSON(event.data);if(typeof response==='object'&&'jsonrpc'in response&&response.jsonrpc==='2.0'){if('result'in response&&this._ws_callbacks[response.id]){var success_cb=this._ws_callbacks[response.id].success_cb;delete this._ws_callbacks[response.id];success_cb(response.result,this);return;}
else if('error'in response&&this._ws_callbacks[response.id]){var error_cb=this._ws_callbacks[response.id].error_cb;var orig_req=this._ws_callbacks[response.id].request;if(!self.authing&&response.error.code==-32000&&self.options.login&&self.options.passwd){self.authing=true;this.call("login",{login:self.options.login,passwd:self.options.passwd},this._ws_callbacks[response.id].request_obj.method=="login"?function(e){self.authing=false;console.log("logged in");delete self._ws_callbacks[response.id];if(self.options.onWSLogin){self.options.onWSLogin(true,self);}}:function(e){self.authing=false;console.log("logged in, resending request id: "+response.id);var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send(orig_req);}
if(self.options.onWSLogin){self.options.onWSLogin(true,self);}},function(e){console.log("error logging in, request id:",response.id);delete self._ws_callbacks[response.id];error_cb(response.error,this);if(self.options.onWSLogin){self.options.onWSLogin(false,self);}});return;}
delete this._ws_callbacks[response.id];error_cb(response.error,this);return;}}}
catch(err){console.log("ERROR: "+err);return;}
if(typeof this.options.onmessage==='function'){event.eventData=response;if(!event.eventData){event.eventData={};}
var reply=this.options.onmessage(event);if(reply&&typeof reply==="object"&&event.eventData.id){var msg={jsonrpc:"2.0",id:event.eventData.id,result:reply};var socket=self.options.getSocket(self.wsOnMessage);if(socket!==null){socket.send($.toJSON(msg));}}}};$.JsonRpcClient._batchObject=function(jsonrpcclient,all_done_cb,error_cb){this._requests=[];this.jsonrpcclient=jsonrpcclient;this.all_done_cb=all_done_cb;this.error_cb=typeof error_cb==='function'?error_cb:function(){};};$.JsonRpcClient._batchObject.prototype.call=function(method,params,success_cb,error_cb){if(!params){params={};}
if(this.options.sessid){params.sessid=this.options.sessid;}
if(!success_cb){success_cb=function(e){console.log("Success: ",e);};}
if(!error_cb){error_cb=function(e){console.log("Error: ",e);};}
this._requests.push({request:{jsonrpc:'2.0',method:method,params:params,id:this.jsonrpcclient._current_id++},success_cb:success_cb,error_cb:error_cb});};$.JsonRpcClient._batchObject.prototype.notify=function(method,params){if(this.options.sessid){params.sessid=this.options.sessid;}
this._requests.push({request:{jsonrpc:'2.0',method:method,params:params}});};$.JsonRpcClient._batchObject.prototype._execute=function(){var self=this;if(this._requests.length===0)return;var batch_request=[];var handlers={};var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(var i=0;i<this._requests.length;i++){var call=this._requests[i];var success_cb=('success_cb'in call)?call.success_cb:undefined;var error_cb=('error_cb'in call)?call.error_cb:undefined;self.jsonrpcclient._wsCall(socket,call.request,success_cb,error_cb);}
if(typeof all_done_cb==='function')all_done_cb(result);return;}
for(var i=0;i<this._requests.length;i++){var call=this._requests[i];batch_request.push(call.request);if('id'in call.request){handlers[call.request.id]={success_cb:call.success_cb,error_cb:call.error_cb};}}
var success_cb=function(data){self._batchCb(data,handlers,self.all_done_cb);};if(self.jsonrpcclient.options.ajaxUrl===null){throw"$.JsonRpcClient.batch used with no websocket and no http endpoint.";}
$.ajax({url:self.jsonrpcclient.options.ajaxUrl,data:$.toJSON(batch_request),dataType:'json',cache:false,type:'POST',error:function(jqXHR,textStatus,errorThrown){self.error_cb(jqXHR,textStatus,errorThrown);},success:success_cb});};$.JsonRpcClient._batchObject.prototype._batchCb=function(result,handlers,all_done_cb){for(var i=0;i<result.length;i++){var response=result[i];if('error'in response){if(response.id===null||!(response.id in handlers)){if('console'in window)console.log(response);}
else handlers[response.id].error_cb(response.error,this);}
else{if(!(response.id in handlers)&&'console'in window)console.log(response);else handlers[response.id].success_cb(response.result,this);}}
if(typeof all_done_cb==='function')all_done_cb(result);};})(jQuery);(function($){var generateGUID=(typeof(window.crypto)!=='undefined'&&typeof(window.crypto.getRandomValues)!=='undefined')?function(){var buf=new Uint16Array(8);window.crypto.getRandomValues(buf);var S4=function(num){var ret=num.toString(16);while(ret.length<4){ret="0"+ret;}
return ret;};return(S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));}:function(){return'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,function(c){var r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);return v.toString(16);});};$.verto=function(options,callbacks){var verto=this;$.verto.saved.push(verto);verto.options=$.extend({login:null,passwd:null,socketUrl:null,tag:null,videoParams:{},ringSleep:6000},options);verto.sessid=$.cookie('verto_session_uuid')||generateGUID();$.cookie('verto_session_uuid',verto.sessid,{expires:1});verto.dialogs={};verto.callbacks=callbacks||{};verto.eventSUBS={};verto.rpcClient=new $.JsonRpcClient({login:verto.options.login,passwd:verto.options.passwd,socketUrl:verto.options.socketUrl,sessid:verto.sessid,onmessage:function(e){return verto.handleMessage(e.eventData);},onWSConnect:function(o){o.call('login',{});},onWSLogin:function(success){if(verto.callbacks.onWSLogin){verto.callbacks.onWSLogin(verto,success);}},onWSClose:function(success){if(verto.callbacks.onWSClose){verto.callbacks.onWSClose(verto,success);}
verto.purge();}});if(verto.options.ringFile&&verto.options.tag){verto.ringer=$("#"+verto.options.tag);}
verto.rpcClient.call('login',{});};$.verto.prototype.loginData=function(params){verto.options.login=params.login;verto.options.passwd=params.passwd;verto.rpcClient.loginData(params);};$.verto.prototype.logout=function(msg){var verto=this;verto.rpcClient.closeSocket();verto.purge();};$.verto.prototype.login=function(msg){var verto=this;verto.logout();verto.rpcClient.call('login',{});};$.verto.prototype.message=function(msg){var verto=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
if(!msg.body){console.error("Missing Body");err++;}
if(err){return false;}
verto.sendMethod("verto.info",{msg:msg});return true;};$.verto.prototype.processReply=function(method,success,e){var verto=this;var i;console.log("Response: "+method,success,e);switch(method){case"verto.subscribe":for(i in e.unauthorizedChannels){drop_bad(verto,e.unauthorizedChannels[i]);}
for(i in e.subscribedChannels){mark_ready(verto,e.subscribedChannels[i]);}
break;case"verto.unsubscribe":break;}};$.verto.prototype.sendMethod=function(method,params){var verto=this;verto.rpcClient.call(method,params,function(e){verto.processReply(method,true,e);},function(e){verto.processReply(method,false,e);});};function do_sub(verto,channel,obj){}
function drop_bad(verto,channel){console.error("drop unauthorized channel: "+channel);delete verto.eventSUBS[channel];}
function mark_ready(verto,channel){for(var j in verto.eventSUBS[channel]){verto.eventSUBS[channel][j].ready=true;console.log("subscribed to channel: "+channel);if(verto.eventSUBS[channel][j].readyHandler){verto.eventSUBS[channel][j].readyHandler(verto,channel);}}}
var SERNO=1;function do_subscribe(verto,channel,subChannels,sparams){var params=sparams||{};var local=params.local;var obj={eventChannel:channel,userData:params.userData,handler:params.handler,ready:false,readyHandler:params.readyHandler,serno:SERNO++};var isnew=false;if(!verto.eventSUBS[channel]){verto.eventSUBS[channel]=[];subChannels.push(channel);isnew=true;}
verto.eventSUBS[channel].push(obj);if(local){obj.ready=true;obj.local=true;}
if(!isnew&&verto.eventSUBS[channel][0].ready){obj.ready=true;if(obj.readyHandler){obj.readyHandler(verto,channel);}}
return{serno:obj.serno,eventChannel:channel};}
$.verto.prototype.subscribe=function(channel,sparams){var verto=this;var r=[];var subChannels=[];var params=sparams||{};if(typeof(channel)==="string"){r.push(do_subscribe(verto,channel,subChannels,params));}else{for(var i in channel){r.push(do_subscribe(verto,channel,subChannels,params));}}
if(subChannels.length){verto.sendMethod("verto.subscribe",{eventChannel:subChannels.length==1?subChannels[0]:subChannels,subParams:params.subParams});}
return r;};$.verto.prototype.unsubscribe=function(handle){var verto=this;var i;if(!handle){for(i in verto.eventSUBS){if(verto.eventSUBS[i]){verto.unsubscribe(verto.eventSUBS[i]);}}}else{var unsubChannels={};var sendChannels=[];var channel;if(typeof(handle)=="string"){delete verto.eventSUBS[handle];unsubChannels[handle]++;}else{for(i in handle){if(typeof(handle[i])=="string"){channel=handle[i];delete verto.eventSUBS[channel];unsubChannels[channel]++;}else{var repl=[];channel=handle[i].eventChannel;for(var j in verto.eventSUBS[channel]){if(verto.eventSUBS[channel][j].serno==handle[i].serno){}else{repl.push(verto.eventSUBS[channel][j]);}}
verto.eventSUBS[channel]=repl;if(verto.eventSUBS[channel].length===0){delete verto.eventSUBS[channel];unsubChannels[channel]++;}}}}
for(var u in unsubChannels){console.log("Sending Unsubscribe for: ",u);sendChannels.push(u);}
if(sendChannels.length){verto.sendMethod("verto.unsubscribe",{eventChannel:sendChannels.length==1?sendChannels[0]:sendChannels});}}};$.verto.prototype.broadcast=function(channel,params){var verto=this;var msg={eventChannel:channel,data:{}};for(var i in params){msg.data[i]=params[i];}
verto.sendMethod("verto.broadcast",msg);};$.verto.prototype.purge=function(callID){var verto=this;var x=0;var i;for(i in verto.dialogs){if(!x){console.log("purging dialogs");}
x++;verto.dialogs[i].setState($.verto.enum.state.purge);}
for(i in verto.eventSUBS){if(verto.eventSUBS[i]){console.log("purging subscription: "+i);delete verto.eventSUBS[i];}}};$.verto.prototype.hangup=function(callID){var verto=this;if(callID){var dialog=verto.dialogs[callID];if(dialog){dialog.hangup();}}else{for(var i in verto.dialogs){verto.dialogs[i].hangup();}}};$.verto.prototype.newCall=function(args,callbacks){var verto=this;if(!verto.rpcClient.socketReady()){console.error("Not Connected...");return;}
var dialog=new $.verto.dialog($.verto.enum.direction.outbound,this,args);dialog.invite();if(callbacks){dialog.callbacks=callbacks;}
return dialog;};$.verto.prototype.handleMessage=function(data){var verto=this;if(data.params.callID){var dialog=verto.dialogs[data.params.callID];if(dialog){switch(data.method){case'verto.bye':dialog.hangup();break;case'verto.answer':dialog.handleAnswer(data.params.sdp);break;case'verto.media':dialog.handleMedia(data.params.sdp);break;case'verto.display':dialog.handleDisplay(data.params);break;case'verto.info':dialog.handleInfo(data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}else{switch(data.method){case'verto.attach':data.params.attach=true;if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.useVideo=true;}
if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;case'verto.invite':if(data.params.sdp&&data.params.sdp.indexOf("m=video")>0){data.params.wantVideo=true;}
if(data.params.sdp&&data.params.sdp.indexOf("stereo=1")>0){data.params.useStereo=true;}
dialog=new $.verto.dialog($.verto.enum.direction.inbound,verto,data.params);break;default:console.debug("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED");break;}}
return{method:data.method};}else{switch(data.method){case'verto.event':var list=null;var key=null;if(data.params){key=data.params.eventChannel;}
if(key){list=verto.eventSUBS[key];if(!list){list=verto.eventSUBS[key.split(".")[0]];}}
if(!list&&key&&verto.dialogs[key]){verto.dialogs[key].sendMessage($.verto.enum.message.pvtEvent,data.params);}else if(!list){if(!key){key="UNDEFINED";}
console.error("UNSUBBED or invalid EVENT "+key+" IGNORED");}else{for(var i in list){var sub=list[i];if(!sub||!sub.ready){console.error("invalid EVENT for "+key+" IGNORED");}else if(sub.handler){sub.handler(verto,data.params,sub.userData);}else if(verto.callbacks.onEvent){verto.callbacks.onEvent(verto,data.params,sub.userData);}else{console.log("EVENT:",data.params);}}}
break;case"verto.info":if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.info,data.params.msg);}
console.error(data);console.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;default:console.error("INVALID METHOD OR NON-EXISTANT CALL REFERENCE IGNORED",data.method);break;}}};var del_array=function(array,name){var r=[];var len=array.length;for(var i=0;i<len;i++){if(array[i]!=name){r.push(array[i]);}}
return r;};var hashArray=function(){var vha=this;var hash={};var array=[];vha.reorder=function(a){array=a;var h=hash;hash={};var len=array.length;for(var i=0;i<len;i++){var key=array[i];if(h[key]){hash[key]=h[key];delete h[key];}}
h=undefined;};vha.clear=function(){hash=undefined;array=undefined;hash={};array=[];};vha.add=function(name,val,insertAt){var redraw=false;if(!hash[name]){if(insertAt===undefined||insertAt<0||insertAt>=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i<len;i++){if(x++==insertAt){n.push(name);}
n.push(array[i]);}
array=undefined;array=n;n=undefined;redraw=true;}}
hash[name]=val;return redraw;};vha.del=function(name){var r=false;if(hash[name]){array=del_array(array,name);delete hash[name];r=true;}else{console.error("can't del nonexistant key "+name);}
return r;};vha.get=function(name){return hash[name];};vha.order=function(){return array;};vha.hash=function(){return hash;};vha.indexOf=function(name){var len=array.length;for(var i=0;i<len;i++){if(array[i]==name){return i;}}};vha.arrayLen=function(){return array.length;};vha.asArray=function(){var r=[];var len=array.length;for(var i=0;i<len;i++){var key=array[i];r.push(hash[key]);}
return r;};vha.each=function(cb){var len=array.length;for(var i=0;i<len;i++){cb(array[i],hash[array[i]]);}};vha.dump=function(html){var str="";vha.each(function(name,val){str+="name: "+name+" val: "+JSON.stringify(val)+(html?"<br>":"\n");});return str;};};$.verto.liveArray=function(verto,context,name,config){var la=this;var lastSerno=0;var binding=null;var user_obj=config.userObj;var local=false;hashArray.call(la);la._add=la.add;la._del=la.del;la._reorder=la.reorder;la._clear=la.clear;la.context=context;la.name=name;la.user_obj=user_obj;la.verto=verto;la.broadcast=function(channel,obj){verto.broadcast(channel,obj);};la.errs=0;la.clear=function(){la._clear();lastSerno=0;if(la.onChange){la.onChange(la,{action:"clear"});}};la.checkSerno=function(serno){if(serno<0){return true;}
if(lastSerno>0&&serno!=(lastSerno+1)){if(la.onErr){la.onErr(la,{lastSerno:lastSerno,serno:serno});}
la.errs++;console.debug(la.errs);if(la.errs<3){la.bootstrap(la.user_obj);}
return false;}else{lastSerno=serno;return true;}};la.reorder=function(serno,a){if(la.checkSerno(serno)){la._reorder(a);if(la.onChange){la.onChange(la,{serno:serno,action:"reorder"});}}};la.init=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){if(la.onChange){la.onChange(la,{serno:serno,action:"init",index:index,key:key,data:val});}}};la.bootObj=function(serno,val){if(la.checkSerno(serno)){for(var i in val){la._add(val[i][0],val[i][1]);}
if(la.onChange){la.onChange(la,{serno:serno,action:"bootObj",data:val,redraw:true});}}};la.add=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){var redraw=la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"add",index:index,key:key,data:val,redraw:redraw});}}};la.modify=function(serno,val,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){la._add(key,val,index);if(la.onChange){la.onChange(la,{serno:serno,action:"modify",key:key,data:val,index:index});}}};la.del=function(serno,key,index){if(key===null||key===undefined){key=serno;}
if(la.checkSerno(serno)){if(index===null||index<0||index===undefined){index=la.indexOf(key);}
var ok=la._del(key);if(ok&&la.onChange){la.onChange(la,{serno:serno,action:"del",key:key,index:index});}}};var eventHandler=function(v,e,la){var packet=e.data;if(packet.name!=la.name){return;}
console.error("READ:",packet);switch(packet.action){case"init":la.init(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"bootObj":la.bootObj(packet.wireSerno,packet.data);break;case"add":la.add(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);break;case"modify":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.modify(packet.wireSerno,packet.data,packet.hashKey,packet.arrIndex);}
break;case"del":if(!(packet.arrIndex||packet.hashKey)){console.error("Invalid Packet",packet);}else{la.del(packet.wireSerno,packet.hashKey,packet.arrIndex);}
break;case"clear":la.clear();break;case"reorder":la.reorder(packet.wireSerno,packet.order);break;default:if(la.checkSerno(packet.wireSerno)){if(la.onChange){la.onChange(la,{serno:packet.wireSerno,action:packet.action,data:packet.data});}}
break;}};if(la.context){binding=la.verto.subscribe(la.context,{handler:eventHandler,userData:la,subParams:config.subParams});}
la.destroy=function(){la._clear();la.verto.unsubscribe(binding);};la.bootstrap=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"bootstrap",context:self.context,name:self.name,obj:obj}});};la.changepage=function(obj){var self=la;self.clear();self.broadcast(self.context,{liveArray:{command:"changepage",context:la.context,name:la.name,obj:obj}});};la.heartbeat=function(obj){var self=la;var callback=function(){self.heartbeat.call(self,obj);};self.broadcast(self.context,{liveArray:{command:"heartbeat",context:self.context,name:self.name,obj:obj}});self.hb_pid=setTimeout(callback,30000);};la.bootstrap(la.user_obj);};$.verto.liveTable=function(verto,context,name,jq,config){var dt;var la=new $.verto.liveArray(verto,context,name,{subParams:config.subParams});var lt=this;lt.liveArray=la;lt.dataTable=dt;lt.verto=verto;lt.destroy=function(){if(dt){dt.fnDestroy();}
if(la){la.destroy();}
dt=null;la=null;};la.onErr=function(obj,args){console.error("Error: ",obj,args);};la.onChange=function(obj,args){var index=0;var iserr=0;if(!dt){if(!config.aoColumns){if(args.action!="init"){return;}
config.aoColumns=[];for(var i in args.data){config.aoColumns.push({"sTitle":args.data[i]});}}
dt=jq.dataTable(config);}
if(dt&&(args.action=="del"||args.action=="modify")){index=args.index;if(index===undefined&&args.key){index=la.indexOf(args.key);}
if(index===undefined){console.error("INVALID PACKET Missing INDEX\n",args);return;}}
try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;}
dt.fnClearTable();dt.fnAddData(obj.asArray());dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;}
if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(obj.asArray());}else{dt.fnAddData(args.data);}
dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;}
console.debug(args,index);dt.fnUpdate(args.data,index);dt.fnAdjustColumnSizing();break;case"del":dt.fnDeleteRow(index);dt.fnAdjustColumnSizing();break;case"clear":dt.fnClearTable();break;case"reorder":dt.fnClearTable();dt.fnAddData(obj.asArray());break;case"hide":jq.hide();break;case"show":jq.show();break;}}catch(err){console.error("ERROR: "+err);iserr++;}
if(iserr){obj.errs++;if(obj.errs<3){obj.bootstrap(obj.user_obj);}}else{obj.errs=0;}
if(config.onChange){config.onChange(obj,args);}};la.onChange(la,{action:"init"});};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,tag:verto.options.tag},params);dialog.verto=verto;dialog.direction=direction;dialog.lastState=null;dialog.state=dialog.lastState=$.verto.enum.state.new;dialog.callbacks=verto.callbacks;dialog.answered=false;dialog.attach=params.attach||false;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();}
if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}}
dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;if(!dialog.params.remote_caller_id_name){dialog.params.remote_caller_id_name="Nobody";}
if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number="UNKNOWN";}
RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;}
RTCcallbacks.onICESDP=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.SDP);dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onError=function(e){console.error("ERROR:",e);dialog.hangup();};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,useVideo:dialog.videoStream,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:verto.options.videoParams,});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;}
obj.dialogParams[i]=dialog.params[i];}
dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;}
return false;}
$.verto.dialog.prototype.setState=function(state){var dialog=this;if(dialog.state==$.verto.enum.state.ringing){dialog.stopRinging();}
if(dialog.state==state||!checkStateChange(dialog.state,state)){console.error("Dialog "+dialog.callID+": INVALID state change from "+dialog.state.name+" to "+state.name);dialog.hangup();return false;}
console.error("Dialog "+dialog.callID+": state change from "+dialog.state.name+" to "+state.name);dialog.lastState=dialog.state;dialog.state=state;if(dialog.callbacks.onDialogState){dialog.callbacks.onDialogState(this);}
switch(dialog.state){case $.verto.enum.state.purge:dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.hangup:if(dialog.lastState.val>$.verto.enum.state.requesting.val&&dialog.lastState.val<$.verto.enum.state.hangup.val){dialog.sendMethod("verto.bye",{});}
dialog.setState($.verto.enum.state.destroy);break;case $.verto.enum.state.destroy:delete verto.dialogs[dialog.callID];dialog.rtc.stop();break;}
return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;console.log("Response: "+method+" State:"+dialog.state.name,success,e);switch(method){case"verto.answer":case"verto.attach":if(success){dialog.setState($.verto.enum.state.active);}else{dialog.hangup();}
break;case"verto.invite":if(success){dialog.setState($.verto.enum.state.trying);}else{dialog.setState($.verto.enum.state.destroy);}
break;case"verto.bye":dialog.hangup();break;case"verto.modify":if(e.holdState){if(e.holdState=="held"){if(dialog.state!=$.verto.enum.state.held){dialog.setState($.verto.enum.state.held);}}else if(e.holdState=="active"){if(dialog.state!=$.verto.enum.state.active){dialog.setState($.verto.enum.state.active);}}}
if(success){}
break;default:break;}};$.verto.dialog.prototype.hangup=function(sdp){var dialog=this;if(dialog.state.val>$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
dialog.rtc.useVideo(dialog.videoStream);};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){cur_call.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;cur_call.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;cur_call.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;cur_call.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;if(!msg.to){console.error("Missing To");err++;}
if(!msg.body){console.error("Missing Body");err++;}
if(err){return false;}
dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(params){if(params.useVideo){dialog.useVideo(true);}}
dialog.rtc.createAnswer(dialog.params.sdp);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(sdp){var dialog=this;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{dialog.rtc.answer(sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("ANSWER SDP",sdp);}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params.msg);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(sdp){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
dialog.rtc.answer(sdp,function(){dialog.setState($.verto.enum.state.early);},function(e){console.error(e);dialog.hangup();});console.log("EARLY SDP",sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,ringing:1,destroy:1,answering:1},requesting:{trying:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$(window).bind('beforeunload',function(){for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.logout();verto.purge();}}
return $.verto.warnOnUnload;});})(jQuery);

Binary file not shown.

Binary file not shown.

261
html5/verto/demo/verto.html Normal file
View File

@ -0,0 +1,261 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/jquery.mobile-1.3.2.min.css" />
<link rel="stylesheet" type="text/css" href="css/jsontable.css" />
<link rel="shortcut icon" href="favicon.ico" />
<meta charset="utf-8" />
<title>FreeSWITCH Verto Demo</title>
<style type="text/css">
.pageheader {
font-size: 22px;
font-weight: normal;
height: 27px;
}
</style>
</head>
<body>
<div data-role="page" id="page-login" align="center">
<div data-role="header" class="page-header">
FreeSWITCH Verto Demo
</div>
<br>
<a target="_CC2104" href="https://www.cluecon.com"><img border="0" width="300" src="img/cc_banner2014.gif"></a>
<div data-role="fieldcontain">
<label for="name">Login</label>
<input type="text" size="20" id="login"/>
</div>
<div data-role="fieldcontain">
<label for="name">Password</label>
<input type="password" size="20" id="passwd"/>
</div>
<div data-role="fieldcontain">
<label for="name">CID Name</label>
<input type="text" size="30" id="name"/>
</div>
<div data-role="fieldcontain">
<label for="name">CID Number</label>
<input type="text" size="20" id="cid"/>
</div>
<div data-role="fieldcontain">
<label for="name">Hostname</label>
<input type="text" size="20" id="hostName"/>
</div>
<div data-role="fieldcontain">
<label for="name">Websocket URL</label>
<input type="text" size="20" id="wsURL"/>
</div>
<br><br>
<button data-inline="true" id="loginbtn">Log In</button>
</div>
<div data-role="page" id="page-incall" align="center">
<div data-role="header" id="calltitle" class="pageheader">
Verto IN CALL
</div>
<br>
<a target="_CC2104" href="https://www.cluecon.com"><img border="0" width="300" src="img/cc_banner2014.gif"></a>
<br><br>
<div id="conf">
<div style="color:black;font-family: verdana" align="center" id="mcount"></div>
<table cellspacing="0" cellpadding="0" border="0" align="center" id="example" class="jsDataTable">
</table>
</div>
<div id="display" style="font-weight:bold;font-size:18px"></div>
<br>
<div data-role="fieldcontain" id="xferdiv">
<input data-mini="true" type="text" id="xferto"><br>
<button data-inline="true" id="cancelxferbtn">Cancel Transfer</button>
<button data-inline="true" class="startxferbtn">Complete Transfer</button>
</div>
<div id=keypad>
<button class="dtmf" data-inline="true">1</button>
<button class="dtmf" data-inline="true">2</button>
<button class="dtmf" data-inline="true">3</button>
<br>
<button class="dtmf" data-inline="true">4</button>
<button class="dtmf" data-inline="true">5</button>
<button class="dtmf" data-inline="true">6</button>
<br>
<button class="dtmf" data-inline="true">7</button>
<button class="dtmf" data-inline="true">8</button>
<button class="dtmf" data-inline="true">9</button>
<br>
<button class="dtmf" data-inline="true">*</button>
<button class="dtmf" data-inline="true">0</button>
<button class="dtmf" data-inline="true">#</button>
</div>
<br>
<button data-inline="true" id="hold">HOLD</button>
<button data-inline="true" id="hupbtn">End Call</button>
<button data-inline="true" class="startxferbtn">Transfer</button>
<div id="media">
<video width=800 id="webcam" autoplay="autoplay" hidden="true"></video>
</div>
<br><br>
</div>
<div data-role="page" id="page-main" align="center">
<div data-role="header" class="pageheader">
FreeSWITCH Verto Demo
</div>
<br>
<a target="_CC2104" href="https://www.cluecon.com"><img border="0" width="300" src="img/cc_banner2014.gif"></a>
<div id="offline">
<h2>LOGGING IN</h2>
<img src="images/login.gif"></img>
<div id="errordisplay" style="font-weight:bold;font-size:18px;color:#ae0000"></div>
</div>
<div id="online" align="center">
<div id="text"></div>
<div data-role="fieldcontain">
<input type="text" id="ext"/><br>
<div id="dialpad">
<button class="dialbtn" data-inline="true">1</button>
<button class="dialbtn" data-inline="true">2</button>
<button class="dialbtn" data-inline="true">3</button>
<br>
<button class="dialbtn" data-inline="true">4</button>
<button class="dialbtn" data-inline="true">5</button>
<button class="dialbtn" data-inline="true">6</button>
<br>
<button class="dialbtn" data-inline="true">7</button>
<button class="dialbtn" data-inline="true">8</button>
<button class="dialbtn" data-inline="true">9</button>
<br>
<button class="dialbtn" data-inline="true">*</button>
<button class="dialbtn" data-inline="true">0</button>
<button class="dialbtn" data-inline="true">#</button>
</div>
<button data-inline="true" id="clearbtn">Clear</button>
<button data-inline="true" id="callbtn">Call</button>
<br><br>
<button data-inline="true"id="logoutbtn">Log Out</button>
</div>
<br>
<label><input id="use_vid" type="checkbox" value="foo" > Use Video</label>
<label><input id="use_stereo" type="checkbox" value="foo" > Stereo Audio</label>
<br>
<!--
<b>TEXT</b><br>To: <input type="text" size="20" id="textto"/> MSG: <input type="text" size="40" id="textmsg"/>
<button id="vtxtbtn">Send</button>
-->
</div>
<table width=500 style="font-size:11pt;font-face:arial">
<tr><td align=center colspan=2><b>FreeSWITCH VERTO WebRTC Demo Directory<Br><br></td></tr>
<tr><td width=100><b>Dial</b></td><td><b>Desc</b></td></tr>
<tr><td align=left colspan=2><hr></td></tr>
<tr><td>3d1</td><td> 3D sound demo #1</td></tr>
<tr><td>3d2</td><td> 3D sound demo #2</td></tr>
<tr><td>stereo1</td><td> Stereo sound demo #1</td></tr>
<tr><td>stereo2</td><td> Stereo sound demo #2</td></tr>
<tr><td>stereo3</td><td> Stereo sound demo #3</td></tr>
<tr><td>3500</td><td>Local 48k Stereo Conference</td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td>cluecon</td><td> ClueCon Hotline</td></tr>
<tr><td>&lt;number&gt;</td><td>Call a US/Canda Number</td></tr>
<tr><td>vuc</td><td>VoIP Users Conference</td></tr>
<tr><td>888</td><td>FreeSWITCH Community Conference</td></tr>
<tr><td>3300</td><td>Local 48k Conference</td></tr>
<tr><td>5000</td><td>Try the Demo IVR</td></tr>
<tr><td>9664</td><td>Listen to Hold Music</td></tr>
<tr><td>9386</td><td>Funny Prompts</td></tr>
<tr><td>9198</td><td>Tetris (tone generator)</td></tr>
<tr><td colspan=2><Br>
<center>
<br><br><br>
</td></tr>
</td></tr>
</table>
</div><!-- /page -->
<div data-role="page" id="dialog-logout" data-close-btn="none">
<div data-role="header">
<h2>Logged Out</h2>
</div>
<div data-role="content">
<p>You have been logged out or disconnected from the server.</p>
<button onclick="$('#dialog-logout').dialog('close')">OK</button>
</div>
</div>
<div data-role="page" id="dialog-login-error" data-close-btn="none">
<div data-role="header">
<h2>Login Error</h2>
</div>
<div data-role="content">
<p>Error logging in</p>
<button onclick="$('#dialog-login-error').dialog('close')">OK</button>
</div>
</div>
<div data-role="page" id="dialog-hold" data-close-btn="none">
<div data-role="header">
<h2>Call On hold</h2>
</div>
<div data-role="content">
<p>The call is on hold</p>
<button onclick="$('#dialog-hold').dialog('close');cur_call.toggleHold();">Resume Call</button>
</div>
</div>
<div data-role="page" id="dialog-incoming-call" data-close-btn="none">
<div data-role="header">
<h2>Incoming Call</h2>
</div>
<div data-role="content">
<div id="dialog-incoming-call-txt"><p>Incoming Call</p></div>
<button id="ansbtn">Answer</button>
<div id="vansdiv"><button id="vansbtn">Answer Video</button></div>
<button id="declinebtn">Decline</button>
</div>
</div>
<script type="text/javascript" src="js/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="js/jquery.mobile-1.3.2.min.js"></script>
<script type="text/javascript" src="js/jquery.json-2.4.min.js"></script>
<script type="text/javascript" src="js/jquery.cookie.js"></script>
<script type="text/javascript" src="js/jquery.dataTables.js"></script>
<script type="text/javascript" src="js/verto-min.js"></script>
<script type="text/javascript" src="verto.js"></script>
</body>
</html>

434
html5/verto/demo/verto.js Normal file
View File

@ -0,0 +1,434 @@
'use strict';
var cur_call = null;
var la = null;
var $display = $("#display");
var verto;
var ringing = false;
function display(msg) {
$("#calltitle").html(msg);
}
function clearLa() {
if (la) {
la.destroy();
la = null;
}
$("#conf").hide();
}
function goto_dialog(where) {
$.mobile.changePage("#dialog-" + where, {
role: "dialog"
});
}
function goto_page(where) {
$.mobile.changePage("#page-" + where);
}
var first_login = false;
var online_visible = false;
function online(on) {
if (on) {
$("#online").show();
$("#offline").hide();
first_login = true;
} else {
if (first_login && online_visible) {
goto_dialog("logout");
}
$("#online").hide();
$("#offline").show();
}
online_visible = on;
}
function check_vid() {
var use_vid = $("#use_vid").is(':checked');
return use_vid;
}
var callbacks = {
onMessage: function(verto, dialog, msg, data) {
switch (msg) {
case $.verto.enum.message.pvtEvent:
console.error("pvtEvent", data.pvtData.action);
if (data.pvtData) {
switch (data.pvtData.action) {
case "conference-liveArray-part":
clearLa();
break;
case "conference-liveArray-join":
$(".jsDataTable").width(check_vid() ? "650px" : "550px");
la = new $.verto.liveTable(verto, data.pvtData.laChannel, data.pvtData.laName, $('#example'), {
subParams: {
callID: dialog.callID
},
"onChange": function(obj, args) {
//var len = obj.asArray().length;
$("#mcount").text("Conference Members: " + " (" + obj.arrayLen() + " Total)");
},
"aaData": [],
"aoColumns": [{
"sTitle": "ID"
},
{
"sTitle": "Number"
},
{
"sTitle": "Name"
},
{
"sTitle": "Codec"
},
{
"sTitle": "Status",
"sWidth": check_vid() ? "300px" : "150px"
}],
"bAutoWidth": true,
"bDestroy": true,
"bSort": false,
"bInfo": false,
"bFilter": false,
"bLengthChange": false,
"bPaginate": false,
"iDisplayLength": 1000,
"oLanguage": {
"sEmptyTable": "The Conference is Empty....."
}
});
$("#conf").show();
break;
}
}
break;
case $.verto.enum.message.info:
$("#text").html("Message from: <b>" + data.from + "</b>:<br>" + "<pre>" + data.body + "</pre>");
break;
case $.verto.enum.message.display:
var party = dialog.params.remote_caller_id_name + "<" + dialog.params.remote_caller_id_number + ">";
display("Talking to: " + dialog.cidString());
break;
default:
break;
}
},
onDialogState: function(d) {
cur_call = d;
switch (d.state) {
case $.verto.enum.state.ringing:
display("Call From: " + d.cidString());
$("#ansbtn").click(function() {
ringing = false;
cur_call.answer({
useStereo: $("#use_stereo").is(':checked')
});
$('#dialog-incoming-call').dialog('close');
});
$("#declinebtn").click(function() {
ringing = false;
cur_call.hangup();
$('#dialog-incoming-call').dialog('close');
});
ringing = true;
goto_dialog("incoming-call");
$("#dialog-incoming-call-txt").text("Incoming call from: " + d.cidString());
if (d.params.wantVideo) {
$("#vansbtn").click(function() {
ringing = false;
$("#use_vid").prop("checked", true);
cur_call.answer({
useVideo: true,
useStereo: $("#use_stereo").is(':checked')
});
});
// the buttons in this jquery mobile wont hide .. gotta wrap them in a div as a workaround
$("#vansdiv").show();
} else {
$("#vansdiv").hide();
}
break;
case $.verto.enum.state.active:
display("Talking to: " + d.cidString());
goto_page("incall");
break;
case $.verto.enum.state.hangup:
case $.verto.enum.state.destroy:
clearLa();
goto_page("main");
cur_call = null;
break;
case $.verto.enum.state.held:
break;
default:
display("");
break;
}
},
onWSLogin: function(v, success) {
display("");
if (success) {
online(true);
verto.subscribe("presence", {
handler: function(v, e) {
console.error("PRESENCE:", e);
}
});
if (!window.location.hash) {
goto_page("main");
}
} else {
goto_page("login");
goto_dialog("login-error");
}
},
onWSClose: function(v, success) {
if ($('#online').is(':visible')) {
display("");
online(false);
}
var today = new Date();
$("#errordisplay").html("Connection Error.<br>Last Attempt: " + today);
goto_page("main");
},
onEvent: function(v, e) {
console.debug("w00t", e);
},
};
$("#hold").click(function(e) {
cur_call.toggleHold();
goto_dialog("hold");
});
$("#cancelxferbtn").click(function(e) {
$("#xferto").val("");
$("#xferdiv").hide();
});
$(".startxferbtn").click(function(e) {
if ($('#xferdiv').is(':visible')) {
var xfer = $("#xferto").val();
if (xfer) {
cur_call.transfer(xfer);
}
$("#xferto").val("");
$("#xferdiv").hide();
} else {
$("#xferdiv").show();
}
});
$("#clearbtn").click(function(e) {
$("#ext").val("");
});
$(".dialbtn").click(function(e) {
$("#ext").val($("#ext").val() + e.currentTarget.textContent);
});
$(".dtmf").click(function(e) {
if ($('#xferdiv').is(':visible')) {
$("#xferto").val($("#xferto").val() + e.currentTarget.textContent);
} else {
cur_call.dtmf(e.currentTarget.textContent);
}
});
$("#hupbtn").click(function() {
verto.hangup();
cur_call = null;
});
$("#webcam").click(function() {
check_vid();
});
$("#callbtn").click(function() {
$('#ext').trigger('change');
if (cur_call) {
return;
}
cur_call = verto.newCall({
destination_number: $("#ext").val(),
caller_id_name: $("#name").val(),
caller_id_number: $("#cid").val(),
useVideo: check_vid(),
useStereo: $("#use_stereo").is(':checked')
});
});
function pop(id, cname, dft) {
var tmp = $.cookie(cname) || dft;
$.cookie(cname, tmp, {
expires: 365
});
$(id).val(tmp).change(function() {
$.cookie(cname, $(id).val(), {
expires: 365
});
});
}
function init() {
pop("#ext", "verto_demo_ext", "3500");
pop("#name", "verto_demo_name", "FreeSWITCH User");
pop("#cid", "verto_demo_cid", "1008");
pop("#textto", "verto_demo_textto", "1000");
pop("#login", "verto_demo_login", "1008");
pop("#passwd", "verto_demo_passwd", "1234");
pop("#hostName", "verto_demo_hostname", window.location.hostname);
pop("#wsURL", "verto_demo_wsurl", "wss://" + window.location.hostname + ":8082");
var tmp = $.cookie("verto_demo_vid_checked") || "false";
$.cookie("verto_demo_vid_checked", tmp, {
expires: 365
});
$("#use_vid").prop("checked", tmp === "true").change(function(e) {
tmp = $("#use_vid").is(':checked');
$.cookie("verto_demo_vid_checked", tmp ? "true" : "false", {
expires: 365
});
});
tmp = $.cookie("verto_demo_stereo_checked") || "false";
$.cookie("verto_demo_stereo_checked", tmp, {
expires: 365
});
$("#use_stereo").prop("checked", tmp === "true").change(function(e) {
tmp = $("#use_stereo").is(':checked');
$.cookie("verto_demo_stereo_checked", tmp ? "true" : "false", {
expires: 365
});
});
verto = new $.verto({
login: $("#login").val() + "@" + $("#hostName").val(),
passwd: $("#passwd").val(),
socketUrl: $("#wsURL").val(),
tag: "webcam",
ringFile: "sounds/bell_ring2.wav",
videoParams: {
"minWidth": "1280",
"minHeight": "720"
}
},
callbacks);
$("#login").change(function(e) {
$("#cid").val(e.currentTarget.value);
$.cookie("verto_demo_cid", e.currentTarget.value, {
expires: 365
});
});
$("#vtxtbtn").click(function() {
verto.message({
to: $("#textto").val(),
body: $("#textmsg").val()
});
$("#textmsg").val("");
});
$("#logoutbtn").click(function() {
verto.logout();
goto_page("login");
online(false);
});
$("#loginbtn").click(function() {
online(false);
verto.loginData({
login: $("#login").val() + "@" + $("#hostName").val(),
passwd: $("#passwd").val()
});
verto.login();
goto_page("main");
});
$("#xferdiv").hide();
$("#webcam").hide();
online(false);
}
$(document).ready(function() {
init();
$("#page-incall").on("pagebeforechange", function(event) {});
});
$(document).bind("pagebeforechange", function(e, data) {
if (typeof(data.toPage) !== "string") {
return;
}
switch (window.location.hash) {
case "#page-incall":
console.error(e, data);
setTimeout(function() {
if (!cur_call) {
goto_page("main");
}
},
10000);
break;
case "#page-main":
console.error(e, data);
setTimeout(function() {
if (cur_call && !ringing) {
goto_page("incall");
}
},
2000);
break;
case "#page-login":
setTimeout(function() {
if (online_visible) {
goto_page("main");
}
},
1000);
break;
}
});

15
html5/verto/js/Makefile Normal file
View File

@ -0,0 +1,15 @@
JSFILES=src/jquery.FSRTC.js src/jquery.jsonrpcclient.js src/jquery.verto.js
all: jsmin verto-min.js
jsmin: jsmin.c
$(CC) $< -o $@
verto-min.js: jsmin $(JSFILES)
cat $(JSFILES) | ./jsmin > $@
clean:
rm -f verto-min.js jsmin *~
install-demo: all
cp verto-min.js ../demo/js

10
html5/verto/js/README Normal file
View File

@ -0,0 +1,10 @@
This file needs to say more.
Documentation for the api needs to be developed with jsdoc-toolkit http://pulkitgoyal.in/documenting-jquery-plugins-jsdoc-toolkit/
Dependancies for DEMO
jquery-2.0.3.min.js
jquery-2.0.3.min.map
jquery.cookie.js
jquery.dataTables.js
jquery.json-2.4.min.js
jquery.mobile-1.3.2.min.js

306
html5/verto/js/jsmin.c Normal file
View File

@ -0,0 +1,306 @@
/* jsmin.c
2013-03-29
Copyright (c) 2002 Douglas Crockford (www.crockford.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
static int theA;
static int theB;
static int theLookahead = EOF;
static int theX = EOF;
static int theY = EOF;
static void
error(char* s)
{
fputs("JSMIN Error: ", stderr);
fputs(s, stderr);
fputc('\n', stderr);
exit(1);
}
/* isAlphanum -- return true if the character is a letter, digit, underscore,
dollar sign, or non-ASCII character.
*/
static int
isAlphanum(int c)
{
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
c > 126);
}
/* get -- return the next character from stdin. Watch out for lookahead. If
the character is a control character, translate it to a space or
linefeed.
*/
static int
get()
{
int c = theLookahead;
theLookahead = EOF;
if (c == EOF) {
c = getc(stdin);
}
if (c >= ' ' || c == '\n' || c == EOF) {
return c;
}
if (c == '\r') {
return '\n';
}
return ' ';
}
/* peek -- get the next character without getting it.
*/
static int
peek()
{
theLookahead = get();
return theLookahead;
}
/* next -- get the next character, excluding comments. peek() is used to see
if a '/' is followed by a '/' or '*'.
*/
static int
next()
{
int c = get();
if (c == '/') {
switch (peek()) {
case '/':
for (;;) {
c = get();
if (c <= '\n') {
break;
}
}
break;
case '*':
get();
while (c != ' ') {
switch (get()) {
case '*':
if (peek() == '/') {
get();
c = ' ';
}
break;
case EOF:
error("Unterminated comment.");
}
}
break;
}
}
theY = theX;
theX = c;
return c;
}
/* action -- do something! What you do is determined by the argument:
1 Output A. Copy B to A. Get the next B.
2 Copy B to A. Get the next B. (Delete A).
3 Get the next B. (Delete B).
action treats a string as a single character. Wow!
action recognizes a regular expression if it is preceded by ( or , or =.
*/
static void
action(int d)
{
switch (d) {
case 1:
putc(theA, stdout);
if (
(theY == '\n' || theY == ' ') &&
(theA == '+' || theA == '-' || theA == '*' || theA == '/') &&
(theB == '+' || theB == '-' || theB == '*' || theB == '/')
) {
putc(theY, stdout);
}
case 2:
theA = theB;
if (theA == '\'' || theA == '"' || theA == '`') {
for (;;) {
putc(theA, stdout);
theA = get();
if (theA == theB) {
break;
}
if (theA == '\\') {
putc(theA, stdout);
theA = get();
}
if (theA == EOF) {
error("Unterminated string literal.");
}
}
}
case 3:
theB = next();
if (theB == '/' && (
theA == '(' || theA == ',' || theA == '=' || theA == ':' ||
theA == '[' || theA == '!' || theA == '&' || theA == '|' ||
theA == '?' || theA == '+' || theA == '-' || theA == '~' ||
theA == '*' || theA == '/' || theA == '{' || theA == '\n'
)) {
putc(theA, stdout);
if (theA == '/' || theA == '*') {
putc(' ', stdout);
}
putc(theB, stdout);
for (;;) {
theA = get();
if (theA == '[') {
for (;;) {
putc(theA, stdout);
theA = get();
if (theA == ']') {
break;
}
if (theA == '\\') {
putc(theA, stdout);
theA = get();
}
if (theA == EOF) {
error("Unterminated set in Regular Expression literal.");
}
}
} else if (theA == '/') {
switch (peek()) {
case '/':
case '*':
error("Unterminated set in Regular Expression literal.");
}
break;
} else if (theA =='\\') {
putc(theA, stdout);
theA = get();
}
if (theA == EOF) {
error("Unterminated Regular Expression literal.");
}
putc(theA, stdout);
}
theB = next();
}
}
}
/* jsmin -- Copy the input to the output, deleting the characters which are
insignificant to JavaScript. Comments will be removed. Tabs will be
replaced with spaces. Carriage returns will be replaced with linefeeds.
Most spaces and linefeeds will be removed.
*/
static void
jsmin()
{
if (peek() == 0xEF) {
get();
get();
get();
}
theA = '\n';
action(3);
while (theA != EOF) {
switch (theA) {
case ' ':
action(isAlphanum(theB) ? 1 : 2);
break;
case '\n':
switch (theB) {
case '{':
case '[':
case '(':
case '+':
case '-':
case '!':
case '~':
action(1);
break;
case ' ':
action(3);
break;
default:
action(isAlphanum(theB) ? 1 : 2);
}
break;
default:
switch (theB) {
case ' ':
action(isAlphanum(theA) ? 1 : 3);
break;
case '\n':
switch (theA) {
case '}':
case ']':
case ')':
case '+':
case '-':
case '"':
case '\'':
case '`':
action(1);
break;
default:
action(isAlphanum(theA) ? 1 : 3);
}
break;
default:
action(1);
break;
}
}
}
}
/* main -- Output any command line arguments as comments
and then minify the input.
*/
extern int
main(int argc, char* argv[])
{
int i;
for (i = 1; i < argc; i += 1) {
fprintf(stdout, "// %s\n", argv[i]);
}
jsmin();
return 0;
}

View File

@ -0,0 +1,758 @@
/*
* Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
*
* The Initial Developer of the Original Code is
* Anthony Minessale II <anthm@freeswitch.org>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Anthony Minessale II <anthm@freeswitch.org>
*
* jquery.FSRTC.js - WebRTC Glue code
*
*/
(function($) {
// 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;
}
// Returns a new m= line with the specified codec as the first one.
function setDefaultCodec(mLine, payload) {
var elements = mLine.split(' ');
var newLine = [];
var index = 0;
for (var i = 0; i < elements.length; i++) {
if (index === 3) // Format of media starts from the fourth.
newLine[index++] = payload; // Put target payload to the first.
if (elements[i] !== payload) newLine[index++] = elements[i];
}
return newLine.join(' ');
}
$.FSRTC = function(options) {
this.options = $.extend({
useVideo: null,
useStereo: false,
userData: null,
videoParams: {},
callbacks: {
onICEComplete: function() {},
onICE: function() {},
onOfferSDP: function() {}
}
},
options);
this.mediaData = {
SDP: null,
profile: {},
candidateList: []
};
this.constraints = {
optional: [{
'DtlsSrtpKeyAgreement': 'true'
}],
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: this.options.useVideo ? true : false,
}
};
if (self.options.useVideo) {
self.options.useVideo.style.display = 'none';
}
setCompat();
checkCompat();
};
$.FSRTC.prototype.useVideo = function(obj) {
var self = this;
if (obj) {
self.options.useVideo = obj;
self.constraints.mandatory.OfferToReceiveVideo = true;
} else {
self.options.useVideo = null;
self.constraints.mandatory.OfferToReceiveVideo = false;
}
if (self.options.useVideo) {
self.options.useVideo.style.display = 'none';
}
};
$.FSRTC.prototype.useStereo = function(on) {
var self = this;
self.options.useStereo = on;
};
// Sets Opus in stereo if stereo is enabled, by adding the stereo=1 fmtp param.
$.FSRTC.prototype.stereoHack = function(sdp) {
var self = this;
if (!self.options.useStereo) {
return sdp;
}
var sdpLines = sdp.split('\r\n');
// Find opus payload.
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000'),
opusPayload;
if (opusIndex) {
opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
}
// Find the payload in fmtp line.
var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
if (fmtpLineIndex === null) return sdp;
// Append stereo=1 to fmtp line.
sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1');
sdp = sdpLines.join('\r\n');
return sdp;
};
function setCompat() {
$.FSRTC.moz = !!navigator.mozGetUserMedia;
//navigator.getUserMedia || (navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia);
if (!navigator.getUserMedia) {
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia;
}
}
function checkCompat() {
if (!navigator.getUserMedia) {
alert('This application cannot function in this browser.');
return false;
}
return true;
}
function onStreamError(self) {
console.log('There has been a problem retrieving the streams - did you allow access?');
}
function onStreamSuccess(self) {
console.log("Stream Success");
}
function onICE(self, candidate) {
self.mediaData.candidate = candidate;
self.mediaData.candidateList.push(self.mediaData.candidate);
doCallback(self, "onICE");
}
function doCallback(self, func, arg) {
if (func in self.options.callbacks) {
self.options.callbacks[func](self, arg);
}
}
function onICEComplete(self, candidate) {
console.log("ICE Complete");
doCallback(self, "onICEComplete");
}
function onChannelError(self, e) {
console.error("Channel Error", e);
doCallback(self, "onError", e);
}
function onICESDP(self, sdp) {
self.mediaData.SDP = self.stereoHack(sdp.sdp);
console.log("ICE SDP");
doCallback(self, "onICESDP");
}
function onAnswerSDP(self, sdp) {
self.answer.SDP = self.stereoHack(sdp.sdp);
console.log("ICE ANSWER SDP");
doCallback(self, "onAnswerSDP", self.answer.SDP);
}
function onMessage(self, msg) {
console.log("Message");
doCallback(self, "onICESDP", msg);
}
function onRemoteStream(self, stream) {
if (self.options.useVideo) {
self.options.useVideo.style.display = 'block';
}
var element = self.options.useAudio;
console.log("REMOTE STREAM", stream, element);
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.error('Error attaching stream to element.');
}
self.options.useAudio.play();
self.remoteStream = stream;
}
function onOfferSDP(self, sdp) {
self.mediaData.SDP = self.stereoHack(sdp.sdp);
console.log("Offer SDP");
doCallback(self, "onOfferSDP");
}
$.FSRTC.prototype.answer = function(sdp, onSuccess, onError) {
this.peer.addAnswerSDP({
type: "answer",
sdp: sdp
},
onSuccess, onError);
};
$.FSRTC.prototype.stop = function() {
var self = this;
if (self.options.useVideo) {
self.options.useVideo.style.display = 'none';
}
if (self.localStream) {
self.localStream.stop();
self.localStream = null;
}
if (self.peer) {
console.log("stopping peer");
self.peer.stop();
}
};
$.FSRTC.prototype.createAnswer = function(sdp) {
var self = this;
self.type = "answer";
self.remoteSDP = sdp;
console.debug("inbound sdp: ", sdp);
function onSuccess(stream) {
self.localStream = stream;
self.peer = RTCPeerConnection({
type: self.type,
attachStream: self.localStream,
onICE: function(candidate) {
return onICE(self, candidate);
},
onICEComplete: function() {
return onICEComplete(self);
},
onRemoteStream: function(stream) {
return onRemoteStream(self, stream);
},
onICESDP: function(sdp) {
return onICESDP(self, sdp);
},
onChannelError: function(e) {
return onChannelError(self, e);
},
constraints: self.constraints,
offerSDP: {
type: "offer",
sdp: self.remoteSDP
}
});
onStreamSuccess(self);
}
function onError() {
onStreamError(self);
}
getUserMedia({
constraints: {
audio: true,
video: this.options.useVideo ? {
mandatory: this.options.videoParams,
optional: []
} : null
},
video: this.options.useVideo ? true : false,
onsuccess: onSuccess,
onerror: onError
});
};
$.FSRTC.prototype.call = function(profile) {
checkCompat();
var self = this;
self.type = "offer";
function onSuccess(stream) {
self.localStream = stream;
self.peer = RTCPeerConnection({
type: self.type,
attachStream: self.localStream,
onICE: function(candidate) {
return onICE(self, candidate);
},
onICEComplete: function() {
return onICEComplete(self);
},
onRemoteStream: function(stream) {
return onRemoteStream(self, stream);
},
onOfferSDP: function(sdp) {
return onOfferSDP(self, sdp);
},
onICESDP: function(sdp) {
return onICESDP(self, sdp);
},
onChannelError: function(e) {
return onChannelError(self, e);
},
constraints: self.constraints
});
onStreamSuccess(self);
}
function onError() {
onStreamError(self);
}
getUserMedia({
constraints: {
audio: true,
video: this.options.useVideo ? {
mandatory: this.options.videoParams,
optional: []
} : null
},
video: this.options.useVideo ? true : false,
onsuccess: onSuccess,
onerror: onError
});
/*
navigator.getUserMedia({
video: this.options.useVideo,
audio: true
}, onSuccess, onError);
*/
};
// DERIVED from RTCPeerConnection-v1.5
// 2013, @muazkh - github.com/muaz-khan
// MIT License - https://www.webrtc-experiment.com/licence/
// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/RTCPeerConnection
window.moz = !!navigator.mozGetUserMedia;
function RTCPeerConnection(options) {
var w = window,
PeerConnection = w.mozRTCPeerConnection || w.webkitRTCPeerConnection,
SessionDescription = w.mozRTCSessionDescription || w.RTCSessionDescription,
IceCandidate = w.mozRTCIceCandidate || w.RTCIceCandidate;
var STUN = {
url: !moz ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121'
};
var TURN = {
url: 'turn:homeo@turn.bistri.com:80',
credential: 'homeo'
};
var iceServers = {
iceServers: options.iceServers || [STUN]
};
if (!moz && !options.iceServers) {
if (parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 28) TURN = {
url: 'turn:turn.bistri.com:80',
credential: 'homeo',
username: 'homeo'
};
iceServers.iceServers = [STUN];
}
var optional = {
optional: []
};
if (!moz) {
optional.optional = [{
DtlsSrtpKeyAgreement: true
},
{
RtpDataChannels: options.onChannelMessage ? true : false
}];
}
var peer = new PeerConnection(iceServers, optional);
openOffererChannel();
var x = 0;
peer.onicecandidate = function(event) {
console.log("WTF ICE", event);
if (event.candidate) {
options.onICE(event.candidate);
} else {
if (options.onICEComplete) {
options.onICEComplete();
}
if (options.type == "offer") {
if (!moz && !x && options.onICESDP) {
options.onICESDP(peer.localDescription);
//x = 1;
/*
x = 1;
peer.createOffer(function(sessionDescription) {
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
if (options.onICESDP) {
options.onICESDP(sessionDescription);
}
}, onSdpError, constraints);
*/
}
} else {
if (!x && options.onICESDP) {
options.onICESDP(peer.localDescription);
//x = 1;
/*
x = 1;
peer.createAnswer(function(sessionDescription) {
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
if (options.onICESDP) {
options.onICESDP(sessionDescription);
}
}, onSdpError, constraints);
*/
}
}
}
};
// attachStream = MediaStream;
if (options.attachStream) peer.addStream(options.attachStream);
// attachStreams[0] = audio-stream;
// attachStreams[1] = video-stream;
// attachStreams[2] = screen-capturing-stream;
if (options.attachStreams && options.attachStream.length) {
var streams = options.attachStreams;
for (var i = 0; i < streams.length; i++) {
peer.addStream(streams[i]);
}
}
peer.onaddstream = function(event) {
var remoteMediaStream = event.stream;
// onRemoteStreamEnded(MediaStream)
remoteMediaStream.onended = function() {
if (options.onRemoteStreamEnded) options.onRemoteStreamEnded(remoteMediaStream);
};
// onRemoteStream(MediaStream)
if (options.onRemoteStream) options.onRemoteStream(remoteMediaStream);
//console.debug('on:add:stream', remoteMediaStream);
};
var constraints = options.constraints || {
optional: [],
mandatory: {
OfferToReceiveAudio: true,
OfferToReceiveVideo: true
}
};
// onOfferSDP(RTCSessionDescription)
function createOffer() {
if (!options.onOfferSDP) return;
peer.createOffer(function(sessionDescription) {
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
options.onOfferSDP(sessionDescription);
if (moz && options.onICESDP) {
options.onICESDP(sessionDescription);
}
},
onSdpError, constraints);
}
// onAnswerSDP(RTCSessionDescription)
function createAnswer() {
if (options.type != "answer") return;
//options.offerSDP.sdp = addStereo(options.offerSDP.sdp);
peer.setRemoteDescription(new SessionDescription(options.offerSDP), onSdpSuccess, onSdpError);
peer.createAnswer(function(sessionDescription) {
sessionDescription.sdp = serializeSdp(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
if (options.onAnswerSDP) {
options.onAnswerSDP(sessionDescription);
}
},
onSdpError, constraints);
}
// if Mozilla Firefox & DataChannel; offer/answer will be created later
if ((options.onChannelMessage && !moz) || !options.onChannelMessage) {
createOffer();
createAnswer();
}
// DataChannel Bandwidth
function setBandwidth(sdp) {
// remove existing bandwidth lines
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
sdp = sdp.replace(/a=mid:data\r\n/g, 'a=mid:data\r\nb=AS:1638400\r\n');
return sdp;
}
// old: FF<>Chrome interoperability management
function getInteropSDP(sdp) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
extractedChars = '';
function getChars() {
extractedChars += chars[parseInt(Math.random() * 40)] || '';
if (extractedChars.length < 40) getChars();
return extractedChars;
}
// usually audio-only streaming failure occurs out of audio-specific crypto line
// a=crypto:1 AES_CM_128_HMAC_SHA1_32 --------- kAttributeCryptoVoice
if (options.onAnswerSDP) sdp = sdp.replace(/(a=crypto:0 AES_CM_128_HMAC_SHA1_32)(.*?)(\r\n)/g, '');
// video-specific crypto line i.e. SHA1_80
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 --------- kAttributeCryptoVideo
var inline = getChars() + '\r\n' + (extractedChars = '');
sdp = sdp.indexOf('a=crypto') == -1 ? sdp.replace(/c=IN/g, 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:' + inline + 'c=IN') : sdp;
return sdp;
}
function serializeSdp(sdp) {
//if (!moz) sdp = setBandwidth(sdp);
//sdp = getInteropSDP(sdp);
//console.debug(sdp);
return sdp;
}
// DataChannel management
var channel;
function openOffererChannel() {
if (!options.onChannelMessage || (moz && !options.onOfferSDP)) return;
_openOffererChannel();
if (!moz) return;
navigator.mozGetUserMedia({
audio: true,
fake: true
},
function(stream) {
peer.addStream(stream);
createOffer();
},
useless);
}
function _openOffererChannel() {
channel = peer.createDataChannel(options.channel || 'RTCDataChannel', moz ? {} : {
reliable: false
});
if (moz) channel.binaryType = 'blob';
setChannelEvents();
}
function setChannelEvents() {
channel.onmessage = function(event) {
if (options.onChannelMessage) options.onChannelMessage(event);
};
channel.onopen = function() {
if (options.onChannelOpened) options.onChannelOpened(channel);
};
channel.onclose = function(event) {
if (options.onChannelClosed) options.onChannelClosed(event);
console.warn('WebRTC DataChannel closed', event);
};
channel.onerror = function(event) {
if (options.onChannelError) options.onChannelError(event);
console.error('WebRTC DataChannel error', event);
};
}
if (options.onAnswerSDP && moz && options.onChannelMessage) openAnswererChannel();
function openAnswererChannel() {
peer.ondatachannel = function(event) {
channel = event.channel;
channel.binaryType = 'blob';
setChannelEvents();
};
if (!moz) return;
navigator.mozGetUserMedia({
audio: true,
fake: true
},
function(stream) {
peer.addStream(stream);
createAnswer();
},
useless);
}
// fake:true is also available on chrome under a flag!
function useless() {
log('Error in fake:true');
}
function onSdpSuccess() {}
function onSdpError(e) {
if (options.onChannelError) {
options.onChannelError(e);
}
console.error('sdp error:', e);
}
return {
addAnswerSDP: function(sdp, cbSuccess, cbError) {
peer.setRemoteDescription(new SessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError);
},
addICE: function(candidate) {
peer.addIceCandidate(new IceCandidate({
sdpMLineIndex: candidate.sdpMLineIndex,
candidate: candidate.candidate
}));
},
peer: peer,
channel: channel,
sendData: function(message) {
if (channel) {
channel.send(message);
}
},
stop: function() {
peer.close();
if (options.attachStream) {
options.attachStream.stop();
}
}
};
}
// getUserMedia
var video_constraints = {
mandatory: {},
optional: []
};
function getUserMedia(options) {
var n = navigator,
media;
n.getMedia = n.webkitGetUserMedia || n.mozGetUserMedia;
n.getMedia(options.constraints || {
audio: true,
video: video_constraints
},
streaming, options.onerror ||
function(e) {
console.error(e);
});
function streaming(stream) {
var video = options.video;
if (video) {
video[moz ? 'mozSrcObject' : 'src'] = moz ? stream : window.webkitURL.createObjectURL(stream);
//video.play();
}
if (options.onsuccess) {
options.onsuccess(stream);
}
media = stream;
}
return media;
}
})(jQuery);

View File

@ -0,0 +1,653 @@
/*
* Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
* Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is jquery.jsonrpclient.js modified for Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH
*
* The Initial Developer of the Original Code is
* Textalk AB http://textalk.se/
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Anthony Minessale II <anthm@freeswitch.org>
*
* jquery.jsonrpclient.js - JSON RPC client code
*
*/
/**
* This plugin requires jquery.json.js to be available, or at least the methods $.toJSON and
* $.parseJSON.
*
* The plan is to make use of websockets if they are available, but work just as well with only
* http if not.
*
* Usage example:
*
* var foo = new $.JsonRpcClient({ ajaxUrl: '/backend/jsonrpc' });
* foo.call(
* 'bar', [ 'A parameter', 'B parameter' ],
* function(result) { alert('Foo bar answered: ' + result.my_answer); },
* function(error) { console.log('There was an error', error); }
* );
*
* More examples are available in README.md
*/
(function($) {
/**
* @fn new
* @memberof $.JsonRpcClient
*
* @param options An object stating the backends:
* ajaxUrl A url (relative or absolute) to a http(s) backend.
* socketUrl A url (relative of absolute) to a ws(s) backend.
* onmessage A socket message handler for other messages (non-responses).
* getSocket A function returning a WebSocket or null.
* It must take an onmessage_cb and bind it to the onmessage event
* (or chain it before/after some other onmessage handler).
* Or, it could return null if no socket is available.
* The returned instance must have readyState <= 1, and if less than 1,
* react to onopen binding.
*/
$.JsonRpcClient = function(options) {
var self = this;
this.options = $.extend({
ajaxUrl : null,
socketUrl : null, ///< The ws-url for default getSocket.
onmessage : null, ///< Other onmessage-handler.
login : null, /// auth login
passwd : null, /// auth passwd
sessid : null,
getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); }
}, options);
// Declare an instance version of the onmessage callback to wrap 'this'.
this.wsOnMessage = function(event) { self._wsOnMessage(event); };
};
/// Holding the WebSocket on default getsocket.
$.JsonRpcClient.prototype._ws_socket = null;
/// Object <id>: { success_cb: cb, error_cb: cb }
$.JsonRpcClient.prototype._ws_callbacks = {};
/// The next JSON-RPC request id.
$.JsonRpcClient.prototype._current_id = 1;
/**
* @fn call
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
* @param success_cb A callback for successful request.
* @param error_cb A callback for error.
*/
$.JsonRpcClient.prototype.call = function(method, params, success_cb, error_cb) {
// Construct the JSON-RPC 2.0 request.
if (!params) {
params = {};
}
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
var request = {
jsonrpc : '2.0',
method : method,
params : params,
id : this._current_id++ // Increase the id counter to match request/response
};
if (!success_cb) {
success_cb = function(e){console.log("Success: ", e);};
}
if (!error_cb) {
error_cb = function(e){console.log("Error: ", e);};
}
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request, success_cb, error_cb);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.call used with no websocket and no http endpoint.";
}
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
cache : false,
success : function(data) {
if ('error' in data) error_cb(data.error, this);
success_cb(data.result, this);
},
// JSON-RPC Server could return non-200 on error
error : function(jqXHR, textStatus, errorThrown) {
try {
var response = $.parseJSON(jqXHR.responseText);
if ('console' in window) console.log(response);
error_cb(response.error, this);
}
catch (err) {
// Perhaps the responseText wasn't really a jsonrpc-error.
error_cb({ error: jqXHR.responseText }, this);
}
}
});
};
/**
* Notify sends a command to the server that won't need a response. In http, there is probably
* an empty response - that will be dropped, but in ws there should be no response at all.
*
* This is very similar to call, but has no id and no handling of callbacks.
*
* @fn notify
* @memberof $.JsonRpcClient
*
* @param method The method to run on JSON-RPC server.
* @param params The params; an array or object.
*/
$.JsonRpcClient.prototype.notify = function(method, params) {
// Construct the JSON-RPC 2.0 request.
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
var request = {
jsonrpc: '2.0',
method: method,
params: params
};
// Try making a WebSocket call.
var socket = this.options.getSocket(this.wsOnMessage);
if (socket !== null) {
this._wsCall(socket, request);
return;
}
// No WebSocket, and no HTTP backend? This won't work.
if (this.options.ajaxUrl === null) {
throw "$.JsonRpcClient.notify used with no websocket and no http endpoint.";
}
$.ajax({
type : 'POST',
url : this.options.ajaxUrl,
data : $.toJSON(request),
dataType : 'json',
cache : false
});
};
/**
* Make a batch-call by using a callback.
*
* The callback will get an object "batch" as only argument. On batch, you can call the methods
* "call" and "notify" just as if it was a normal $.JsonRpcClient object, and all calls will be
* sent as a batch call then the callback is done.
*
* @fn batch
* @memberof $.JsonRpcClient
*
* @param callback The main function which will get a batch handler to run call and notify on.
* @param all_done_cb A callback function to call after all results have been handled.
* @param error_cb A callback function to call if there is an error from the server.
* Note, that batch calls should always get an overall success, and the
* only error
*/
$.JsonRpcClient.prototype.batch = function(callback, all_done_cb, error_cb) {
var batch = new $.JsonRpcClient._batchObject(this, all_done_cb, error_cb);
callback(batch);
batch._execute();
};
/**
* The default getSocket handler.
*
* @param onmessage_cb The callback to be bound to onmessage events on the socket.
*
* @fn _getSocket
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype.socketReady = function() {
if (this._ws_socket === null || this._ws_socket.readyState > 1) {
return false;
}
return true;
}
$.JsonRpcClient.prototype.closeSocket = function() {
if (self.socketReady()) {
this._ws_socket.onclose = function (w) {console.log("Closing Socket")}
this._ws_socket.close();
}
}
$.JsonRpcClient.prototype.loginData = function(params) {
self.options.login = params.login;
self.options.passwd = params.passwd;
}
$.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) {
var self = this;
if (!self.socketReady()) {
self.authing = false;
// No socket, or dying socket, let's get a new one.
this._ws_socket = new WebSocket(this.options.socketUrl);
if (this._ws_socket) {
// Set up onmessage handler.
this._ws_socket.onmessage = onmessage_cb;
this._ws_socket.onclose = function (w) {
if (!self.ws_sleep) {
self.ws_sleep = 2;
}
self.ws_cnt = 0;
if (self.options.onWSClose) {
self.options.onWSClose(self);
}
console.error("Websocket Lost sleep: " + self.ws_sleep + "sec");
setTimeout(function() {
console.log("Attempting Reconnection....");
self.connectSocket(onmessage_cb);
}, self.ws_sleep * 1000);
if (++self.ws_cnt >= 150) {
self.ws_sleep = 30;
}
}
// Set up sending of message for when the socket is open.
this._ws_socket.onopen = function() {
this.ws_sleep = 2;
this.ws_cnt = 0;
if (self.options.onWSConnect) {
self.options.onWSConnect(self);
}
var req;
// Send the requests.
while (req = $.JsonRpcClient.q.pop()) {
self._ws_socket.send(req);
}
}
}
}
return this._ws_socket ? true : false;
}
$.JsonRpcClient.prototype._getSocket = function(onmessage_cb) {
// If there is no ws url set, we don't have a socket.
// Likewise, if there is no window.WebSocket.
if (this.options.socketUrl === null || !("WebSocket" in window)) return null;
this.connectSocket(onmessage_cb);
return this._ws_socket;
};
/**
* Queue to save messages delivered when websocket is not ready
*/
$.JsonRpcClient.q = [];
/**
* Internal handler to dispatch a JRON-RPC request through a websocket.
*
* @fn _wsCall
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient.prototype._wsCall = function(socket, request, success_cb, error_cb) {
var request_json = $.toJSON(request);
if (socket.readyState < 1) {
// The websocket is not open yet; we have to set sending of the message in onopen.
self = this; // In closure below, this is set to the WebSocket. Use self instead.
$.JsonRpcClient.q.push(request_json);
}
else {
// We have a socket and it should be ready to send on.
socket.send(request_json);
}
// Setup callbacks. If there is an id, this is a call and not a notify.
if ('id' in request && typeof success_cb !== 'undefined') {
this._ws_callbacks[request.id] = { request: request_json, request_obj: request, success_cb: success_cb, error_cb: error_cb };
}
};
/**
* Internal handler for the websocket messages. It determines if the message is a JSON-RPC
* response, and if so, tries to couple it with a given callback. Otherwise, it falls back to
* given external onmessage-handler, if any.
*
* @param event The websocket onmessage-event.
*/
$.JsonRpcClient.prototype._wsOnMessage = function(event) {
// Check if this could be a JSON RPC message.
var response;
try {
response = $.parseJSON(event.data);
/// @todo Make using the jsonrcp 2.0 check optional, to use this on JSON-RPC 1 backends.
if (typeof response === 'object'
&& 'jsonrpc' in response
&& response.jsonrpc === '2.0') {
/// @todo Handle bad response (without id).
// If this is an object with result, it is a response.
if ('result' in response && this._ws_callbacks[response.id]) {
// Get the success callback.
var success_cb = this._ws_callbacks[response.id].success_cb;
/*
// set the sessid if present
if ('sessid' in response.result && !this.options.sessid || (this.options.sessid != response.result.sessid)) {
this.options.sessid = response.result.sessid;
if (this.options.sessid) {
console.log("setting session UUID to: " + this.options.sessid);
}
}
*/
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with result as parameter.
success_cb(response.result, this);
return;
}
// If this is an object with error, it is an error response.
else if ('error' in response && this._ws_callbacks[response.id]) {
// Get the error callback.
var error_cb = this._ws_callbacks[response.id].error_cb;
var orig_req = this._ws_callbacks[response.id].request;
// if this is an auth request, send the credentials and resend the failed request
if (!self.authing && response.error.code == -32000 && self.options.login && self.options.passwd) {
self.authing = true;
this.call("login", { login: self.options.login, passwd: self.options.passwd},
this._ws_callbacks[response.id].request_obj.method == "login"
?
function(e) {
self.authing = false;
console.log("logged in");
delete self._ws_callbacks[response.id];
if (self.options.onWSLogin) {
self.options.onWSLogin(true, self);
}
}
:
function(e) {
self.authing = false;
console.log("logged in, resending request id: " + response.id);
var socket = self.options.getSocket(self.wsOnMessage);
if (socket !== null) {
socket.send(orig_req);
}
if (self.options.onWSLogin) {
self.options.onWSLogin(true, self);
}
},
function(e) {
console.log("error logging in, request id:", response.id);
delete self._ws_callbacks[response.id];
error_cb(response.error, this);
if (self.options.onWSLogin) {
self.options.onWSLogin(false, self);
}
}
);
return;
}
// Delete the callback from the storage.
delete this._ws_callbacks[response.id];
// Run callback with the error object as parameter.
error_cb(response.error, this);
return;
}
}
}
catch (err) {
// Probably an error while parsing a non json-string as json. All real JSON-RPC cases are
// handled above, and the fallback method is called below.
console.log("ERROR: "+ err);
return;
}
// This is not a JSON-RPC response. Call the fallback message handler, if given.
if (typeof this.options.onmessage === 'function') {
event.eventData = response;
if (!event.eventData) {
event.eventData = {};
}
var reply = this.options.onmessage(event);
if (reply && typeof reply === "object" && event.eventData.id) {
var msg = {
jsonrpc: "2.0",
id: event.eventData.id,
result: reply
};
var socket = self.options.getSocket(self.wsOnMessage);
if (socket !== null) {
socket.send($.toJSON(msg));
}
}
}
};
/************************************************************************************************
* Batch object with methods
************************************************************************************************/
/**
* Handling object for batch calls.
*/
$.JsonRpcClient._batchObject = function(jsonrpcclient, all_done_cb, error_cb) {
// Array of objects to hold the call and notify requests. Each objects will have the request
// object, and unless it is a notify, success_cb and error_cb.
this._requests = [];
this.jsonrpcclient = jsonrpcclient;
this.all_done_cb = all_done_cb;
this.error_cb = typeof error_cb === 'function' ? error_cb : function() {};
};
/**
* @sa $.JsonRpcClient.prototype.call
*/
$.JsonRpcClient._batchObject.prototype.call = function(method, params, success_cb, error_cb) {
if (!params) {
params = {};
}
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
if (!success_cb) {
success_cb = function(e){console.log("Success: ", e);};
}
if (!error_cb) {
error_cb = function(e){console.log("Error: ", e);};
}
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params,
id : this.jsonrpcclient._current_id++ // Use the client's id series.
},
success_cb : success_cb,
error_cb : error_cb
});
};
/**
* @sa $.JsonRpcClient.prototype.notify
*/
$.JsonRpcClient._batchObject.prototype.notify = function(method, params) {
if (this.options.sessid) {
params.sessid = this.options.sessid;
}
this._requests.push({
request : {
jsonrpc : '2.0',
method : method,
params : params
}
});
};
/**
* Executes the batched up calls.
*/
$.JsonRpcClient._batchObject.prototype._execute = function() {
var self = this;
if (this._requests.length === 0) return; // All done :P
// Collect all request data and sort handlers by request id.
var batch_request = [];
var handlers = {};
// If we have a WebSocket, just send the requests individually like normal calls.
var socket = self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);
if (socket !== null) {
for (var i = 0; i < this._requests.length; i++) {
var call = this._requests[i];
var success_cb = ('success_cb' in call) ? call.success_cb : undefined;
var error_cb = ('error_cb' in call) ? call.error_cb : undefined;
self.jsonrpcclient._wsCall(socket, call.request, success_cb, error_cb);
}
if (typeof all_done_cb === 'function') all_done_cb(result);
return;
}
for (var i = 0; i < this._requests.length; i++) {
var call = this._requests[i];
batch_request.push(call.request);
// If the request has an id, it should handle returns (otherwise it's a notify).
if ('id' in call.request) {
handlers[call.request.id] = {
success_cb : call.success_cb,
error_cb : call.error_cb
};
}
}
var success_cb = function(data) { self._batchCb(data, handlers, self.all_done_cb); };
// No WebSocket, and no HTTP backend? This won't work.
if (self.jsonrpcclient.options.ajaxUrl === null) {
throw "$.JsonRpcClient.batch used with no websocket and no http endpoint.";
}
// Send request
$.ajax({
url : self.jsonrpcclient.options.ajaxUrl,
data : $.toJSON(batch_request),
dataType : 'json',
cache : false,
type : 'POST',
// Batch-requests should always return 200
error : function(jqXHR, textStatus, errorThrown) {
self.error_cb(jqXHR, textStatus, errorThrown);
},
success : success_cb
});
};
/**
* Internal helper to match the result array from a batch call to their respective callbacks.
*
* @fn _batchCb
* @memberof $.JsonRpcClient
*/
$.JsonRpcClient._batchObject.prototype._batchCb = function(result, handlers, all_done_cb) {
for (var i = 0; i < result.length; i++) {
var response = result[i];
// Handle error
if ('error' in response) {
if (response.id === null || !(response.id in handlers)) {
// An error on a notify? Just log it to the console.
if ('console' in window) console.log(response);
}
else handlers[response.id].error_cb(response.error, this);
}
else {
// Here we should always have a correct id and no error.
if (!(response.id in handlers) && 'console' in window) console.log(response);
else handlers[response.id].success_cb(response.result, this);
}
}
if (typeof all_done_cb === 'function') all_done_cb(result);
};
})(jQuery);

File diff suppressed because it is too large Load Diff