Revision 58fb2cdc

View differences:

Public/assets/PluginDetect_VLC.js
1
/*
2
PluginDetect v0.9.1
3
www.pinlady.net/PluginDetect/license/
4
[ VLC ]
5
[ getVersion hasMimeType ]
6
[ AllowActiveX ]
7
*/
8
(function(){var j={version:"0.9.1",name:"PluginDetect",addPlugin:function(p,q){if(p&&j.isString(p)&&q&&j.isFunc(q.getVersion)){p=p.replace(/\s/g,"").toLowerCase();j.Plugins[p]=q;if(!j.isDefined(q.getVersionDone)){q.installed=null;q.version=null;q.version0=null;q.getVersionDone=null;q.pluginName=p;}}},uniqueName:function(){return j.name+"998"},openTag:"<",hasOwnPROP:({}).constructor.prototype.hasOwnProperty,hasOwn:function(s,t){var p;try{p=j.hasOwnPROP.call(s,t)}catch(q){}return !!p},rgx:{str:/string/i,num:/number/i,fun:/function/i,arr:/array/i},toString:({}).constructor.prototype.toString,isDefined:function(p){return typeof p!="undefined"},isArray:function(p){return j.rgx.arr.test(j.toString.call(p))},isString:function(p){return j.rgx.str.test(j.toString.call(p))},isNum:function(p){return j.rgx.num.test(j.toString.call(p))},isStrNum:function(p){return j.isString(p)&&(/\d/).test(p)},isFunc:function(p){return j.rgx.fun.test(j.toString.call(p))},getNumRegx:/[\d][\d\.\_,\-]*/,splitNumRegx:/[\.\_,\-]/g,getNum:function(q,r){var p=j.isStrNum(q)?(r&&j.isString(r)?new RegExp(r):j.getNumRegx).exec(q):null;return p?p[0]:null},compareNums:function(w,u,t){var s,r,q,v=parseInt;if(j.isStrNum(w)&&j.isStrNum(u)){if(j.isDefined(t)&&t.compareNums){return t.compareNums(w,u)}s=w.split(j.splitNumRegx);r=u.split(j.splitNumRegx);for(q=0;q<Math.min(s.length,r.length);q++){if(v(s[q],10)>v(r[q],10)){return 1}if(v(s[q],10)<v(r[q],10)){return -1}}}return 0},formatNum:function(q,r){var p,s;if(!j.isStrNum(q)){return null}if(!j.isNum(r)){r=4}r--;s=q.replace(/\s/g,"").split(j.splitNumRegx).concat(["0","0","0","0"]);for(p=0;p<4;p++){if(/^(0+)(.+)$/.test(s[p])){s[p]=RegExp.$2}if(p>r||!(/\d/).test(s[p])){s[p]="0"}}return s.slice(0,4).join(",")},pd:{getPROP:function(s,q,p){try{if(s){p=s[q]}}catch(r){this.errObj=r;}return p},findNavPlugin:function(u){if(u.dbug){return u.dbug}var A=null;if(window.navigator){var z={Find:j.isString(u.find)?new RegExp(u.find,"i"):u.find,Find2:j.isString(u.find2)?new RegExp(u.find2,"i"):u.find2,Avoid:u.avoid?(j.isString(u.avoid)?new RegExp(u.avoid,"i"):u.avoid):0,Num:u.num?/\d/:0},s,r,t,y,x,q,p=navigator.mimeTypes,w=navigator.plugins;if(u.mimes&&p){y=j.isArray(u.mimes)?[].concat(u.mimes):(j.isString(u.mimes)?[u.mimes]:[]);for(s=0;s<y.length;s++){r=0;try{if(j.isString(y[s])&&/[^\s]/.test(y[s])){r=p[y[s]].enabledPlugin}}catch(v){}if(r){t=this.findNavPlugin_(r,z);if(t.obj){A=t.obj}if(A&&!j.dbug){return A}}}}if(u.plugins&&w){x=j.isArray(u.plugins)?[].concat(u.plugins):(j.isString(u.plugins)?[u.plugins]:[]);for(s=0;s<x.length;s++){r=0;try{if(x[s]&&j.isString(x[s])){r=w[x[s]]}}catch(v){}if(r){t=this.findNavPlugin_(r,z);if(t.obj){A=t.obj}if(A&&!j.dbug){return A}}}q=w.length;if(j.isNum(q)){for(s=0;s<q;s++){r=0;try{r=w[s]}catch(v){}if(r){t=this.findNavPlugin_(r,z);if(t.obj){A=t.obj}if(A&&!j.dbug){return A}}}}}}return A},findNavPlugin_:function(t,s){var r=t.description||"",q=t.name||"",p={};if((s.Find.test(r)&&(!s.Find2||s.Find2.test(q))&&(!s.Num||s.Num.test(RegExp.leftContext+RegExp.rightContext)))||(s.Find.test(q)&&(!s.Find2||s.Find2.test(r))&&(!s.Num||s.Num.test(RegExp.leftContext+RegExp.rightContext)))){if(!s.Avoid||!(s.Avoid.test(r)||s.Avoid.test(q))){p.obj=t}}return p},getVersionDelimiter:",",findPlugin:function(r){var q,p={status:-3,plugin:0};if(!j.isString(r)){return p}if(r.length==1){this.getVersionDelimiter=r;return p}r=r.toLowerCase().replace(/\s/g,"");q=j.Plugins[r];if(!q||!q.getVersion){return p}p.plugin=q;p.status=1;return p}},getPluginFileVersion:function(s,u,w,r){var p,q,v,y,t=-1;if(!s){return u}r=r||"version";if(s[r]){p=j.getNum(s[r]+"",w)}if(!p||!u){return u||p||null}q=(j.formatNum(u)).split(j.splitNumRegx);v=(j.formatNum(p)).split(j.splitNumRegx);for(y=0;y<q.length;y++){if(t>-1&&y>t&&q[y]!="0"){return u}if(v[y]!=q[y]){if(t==-1){t=y}if(q[y]!="0"){return u}}}return p},AXO:(function(){var q;try{q=new window.ActiveXObject()}catch(p){}return q?null:window.ActiveXObject})(),getAXO:function(p){var r=null;try{r=new j.AXO(p)}catch(q){j.errObj=q;}if(r){j.browser.ActiveXEnabled=!0}return r},browser:{detectPlatform:function(){var r=this,q,p=window.navigator?navigator.platform||"":"";j.OS=100;if(p){var s=["Win",1,"Mac",2,"Linux",3,"FreeBSD",4,"iPhone",21.1,"iPod",21.2,"iPad",21.3,"Win.*CE",22.1,"Win.*Mobile",22.2,"Pocket\\s*PC",22.3,"",100];for(q=s.length-2;q>=0;q=q-2){if(s[q]&&new RegExp(s[q],"i").test(p)){j.OS=s[q+1];break}}}},detectIE:function(){var r=this,u=document,t,q,v=window.navigator?navigator.userAgent||"":"",w,p,y;r.ActiveXFilteringEnabled=!1;r.ActiveXEnabled=!1;try{r.ActiveXFilteringEnabled=!!window.external.msActiveXFilteringEnabled()}catch(s){}p=["Msxml2.XMLHTTP","Msxml2.DOMDocument","Microsoft.XMLDOM","TDCCtl.TDCCtl","Shell.UIHelper","HtmlDlgSafeHelper.HtmlDlgSafeHelper","Scripting.Dictionary"];y=["WMPlayer.OCX","ShockwaveFlash.ShockwaveFlash","AgControl.AgControl"];w=p.concat(y);for(t=0;t<w.length;t++){if(j.getAXO(w[t])&&!j.dbug){break}}if(r.ActiveXEnabled&&r.ActiveXFilteringEnabled){for(t=0;t<y.length;t++){if(j.getAXO(y[t])){r.ActiveXFilteringEnabled=!1;break}}}q=u.documentMode;try{u.documentMode=""}catch(s){}r.isIE=r.ActiveXEnabled;r.isIE=r.isIE||j.isNum(u.documentMode)||new Function("return/*@cc_on!@*/!1")();try{u.documentMode=q}catch(s){}r.verIE=null;if(r.isIE){r.verIE=(j.isNum(u.documentMode)&&u.documentMode>=7?u.documentMode:0)||((/^(?:.*?[^a-zA-Z])??(?:MSIE|rv\s*\:)\s*(\d+\.?\d*)/i).test(v)?parseFloat(RegExp.$1,10):7)}},detectNonIE:function(){var q=this,p=0,t=window.navigator?navigator:{},s=q.isIE?"":t.userAgent||"",u=t.vendor||"",r=t.product||"";q.isGecko=!p&&(/Gecko/i).test(r)&&(/Gecko\s*\/\s*\d/i).test(s);p=p||q.isGecko;q.verGecko=q.isGecko?j.formatNum((/rv\s*\:\s*([\.\,\d]+)/i).test(s)?RegExp.$1:"0.9"):null;q.isOpera=!p&&(/(OPR\s*\/|Opera\s*\/\s*\d.*\s*Version\s*\/|Opera\s*[\/]?)\s*(\d+[\.,\d]*)/i).test(s);p=p||q.isOpera;q.verOpera=q.isOpera?j.formatNum(RegExp.$2):null;q.isEdge=!p&&(/(Edge)\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isEdge;q.verEdgeHTML=q.isEdge?j.formatNum(RegExp.$2):null;q.isChrome=!p&&(/(Chrome|CriOS)\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isChrome;q.verChrome=q.isChrome?j.formatNum(RegExp.$2):null;q.isSafari=!p&&((/Apple/i).test(u)||!u)&&(/Safari\s*\/\s*(\d[\d\.]*)/i).test(s);p=p||q.isSafari;q.verSafari=q.isSafari&&(/Version\s*\/\s*(\d[\d\.]*)/i).test(s)?j.formatNum(RegExp.$1):null;},init:function(){var p=this;p.detectPlatform();p.detectIE();p.detectNonIE()}},init:{hasRun:0,library:function(){window[j.name]=j;var q=this,p=document;j.win.init();j.head=p.getElementsByTagName("head")[0]||p.getElementsByTagName("body")[0]||p.body||null;j.browser.init();q.hasRun=1;}},ev:{addEvent:function(r,q,p){if(r&&q&&p){if(r.addEventListener){r.addEventListener(q,p,false)}else{if(r.attachEvent){r.attachEvent("on"+q,p)}else{r["on"+q]=this.concatFn(p,r["on"+q])}}}},removeEvent:function(r,q,p){if(r&&q&&p){if(r.removeEventListener){r.removeEventListener(q,p,false)}else{if(r.detachEvent){r.detachEvent("on"+q,p)}}}},concatFn:function(q,p){return function(){q();if(typeof p=="function"){p()}}},handler:function(t,s,r,q,p){return function(){t(s,r,q,p)}},handlerOnce:function(s,r,q,p){return function(){var u=j.uniqueName();if(!s[u]){s[u]=1;s(r,q,p)}}},handlerWait:function(s,u,r,q,p){var t=this;return function(){t.setTimeout(t.handler(u,r,q,p),s)}},setTimeout:function(q,p){if(j.win&&j.win.unload){return}setTimeout(q,p)},fPush:function(q,p){if(j.isArray(p)&&(j.isFunc(q)||(j.isArray(q)&&q.length>0&&j.isFunc(q[0])))){p.push(q)}},call0:function(q){var p=j.isArray(q)?q.length:-1;if(p>0&&j.isFunc(q[0])){q[0](j,p>1?q[1]:0,p>2?q[2]:0,p>3?q[3]:0)}else{if(j.isFunc(q)){q(j)}}},callArray0:function(p){var q=this,r;if(j.isArray(p)){while(p.length){r=p[0];p.splice(0,1);if(j.win&&j.win.unload&&p!==j.win.unloadHndlrs){}else{q.call0(r)}}}},call:function(q){var p=this;p.call0(q);p.ifDetectDoneCallHndlrs()},callArray:function(p){var q=this;q.callArray0(p);q.ifDetectDoneCallHndlrs()},allDoneHndlrs:[],ifDetectDoneCallHndlrs:function(){var r=this,p,q;if(!r.allDoneHndlrs.length){return}if(j.win){if(!j.win.loaded||j.win.loadPrvtHndlrs.length||j.win.loadPblcHndlrs.length){return}}if(j.Plugins){for(p in j.Plugins){if(j.hasOwn(j.Plugins,p)){q=j.Plugins[p];if(q&&j.isFunc(q.getVersion)){if(q.OTF==3||(q.DoneHndlrs&&q.DoneHndlrs.length)||(q.BIHndlrs&&q.BIHndlrs.length)){return}}}}}r.callArray0(r.allDoneHndlrs);}},getVersion:function(u,r,q){var s=j.pd.findPlugin(u),t,p;if(s.status<0){return null}t=s.plugin;if(t.getVersionDone!=1){t.getVersion(null,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}p=(t.version||t.version0);p=p?p.replace(j.splitNumRegx,j.pd.getVersionDelimiter):p;return p},hasMimeType:function(t){if(t&&window.navigator&&navigator.mimeTypes){var w,v,q,s,p=navigator.mimeTypes,r=j.isArray(t)?[].concat(t):(j.isString(t)?[t]:[]);s=r.length;for(q=0;q<s;q++){w=0;try{if(j.isString(r[q])&&/[^\s]/.test(r[q])){w=p[r[q]]}}catch(u){}v=w?w.enabledPlugin:0;if(v&&(v.name||v.description)){return w}}}return null},codebase:{isDisabled:function(){if(j.browser.ActiveXEnabled&&j.isDefined(j.pd.getPROP(document.createElement("object"),"object"))){return 0}return 1},isMin:function(v,u,s){var t=this,r,q,p=0;if(!j.isStrNum(u)||t.isDisabled()){return p}t.init(v);if(!s||t.isActiveXObject(v,j.formatNum(v.DIGITMIN.join(",")))){if(!v.L){v.L={};for(r=0;r<v.Lower.length;r++){if(t.isActiveXObject(v,v.Lower[r])){v.L=t.convert(v,v.Lower[r]);break}}}if(v.L.v){q=t.convert(v,u,1);if(q.x>=0){p=(v.L.x==q.x?t.isActiveXObject(v,q.v):j.compareNums(u,v.L.v)<=0)?1:-1}}}return p},search:function(v,D){var B=this,w=v.$$,q=0,r;r=v.searchHasRun||B.isDisabled()?1:0;v.searchHasRun=1;if(r){return v.version||null}B.init(v);if(!D||B.isActiveXObject(v,j.formatNum(v.DIGITMIN.join(",")))){var G,F,E,s=v.DIGITMAX,t,p,C=99999999,u=[0,0,0,0],H=[0,0,0,0];var A=function(y,K){var I=[].concat(u),J;I[y]=K;J=B.isActiveXObject(v,I.join(","));if(J){q=1;u[y]=K}else{H[y]=K}return J};for(G=0;G<H.length;G++){u[G]=Math.floor(v.DIGITMIN[G])||0;t=u.join(",");p=u.slice(0,G).concat([C,C,C,C]).slice(0,u.length).join(",");for(E=0;E<s.length;E++){if(j.isArray(s[E])){s[E].push(0);if(s[E][G]>H[G]&&j.compareNums(p,v.Lower[E])>=0&&j.compareNums(t,v.Upper[E])<0){H[G]=Math.floor(s[E][G])}}}for(F=0;F<30;F++){if(H[G]-u[G]<=16){for(E=H[G];E>=u[G]+(G?1:0);E--){if(A(G,E)){break}}break}A(G,Math.round((H[G]+u[G])/2))}if(!q){break}H[G]=u[G];}if(q){v.version=B.convert(v,u.join(",")).v}}return v.version||null},emptyNode:function(p){try{p.innerHTML=""}catch(q){}},HTML:[],len:0,onUnload:function(r,q){var p,t=q.HTML,s;for(p=0;p<t.length;p++){s=t[p];if(s){t[p]=0;q.emptyNode(s.span());s.span=0;s.spanObj=0;s=0}}q.iframe=0},init:function(u){var t=this;if(!t.iframe){var s=j.DOM,q;q=s.iframe.insert(0,"$.codebase{ }");t.iframe=q;s.iframe.write(q," ");s.iframe.close(q);}if(!u.init){u.init=1;var p,r;j.ev.fPush([t.onUnload,t],j.win.unloadHndlrs);u.tagA='<object width="1" height="1" style="display:none;" codebase="#version=';r=u.classID||u.$$.classID||"";u.tagB='" '+((/clsid\s*:/i).test(r)?'classid="':'type="')+r+'">'+(u.ParamTags?u.ParamTags:"")+j.openTag+"/object>";for(p=0;p<u.Lower.length;p++){u.Lower[p]=j.formatNum(u.Lower[p]);u.Upper[p]=j.formatNum(u.Upper[p]);}}},isActiveXObject:function(u,q){var t=this,p=0,s=u.$$,r=(j.DOM.iframe.doc(t.iframe)||document).createElement("span");if(u.min&&j.compareNums(q,u.min)<=0){return 1}if(u.max&&j.compareNums(q,u.max)>=0){return 0}r.innerHTML=u.tagA+q+u.tagB;if(j.pd.getPROP(r.firstChild,"object")){p=1}if(p){u.min=q;t.HTML.push({spanObj:r,span:t.span})}else{u.max=q;r.innerHTML=""}return p},span:function(){return this.spanObj},convert_:function(t,p,q,s){var r=t.convert[p];return r?(j.isFunc(r)?j.formatNum(r(q.split(j.splitNumRegx),s).join(",")):q):r},convert:function(v,r,u){var t=this,q,p,s;r=j.formatNum(r);p={v:r,x:-1};if(r){for(q=0;q<v.Lower.length;q++){s=t.convert_(v,q,v.Lower[q]);if(s&&j.compareNums(r,u?s:v.Lower[q])>=0&&(!q||j.compareNums(r,u?t.convert_(v,q,v.Upper[q]):v.Upper[q])<0)){p.v=t.convert_(v,q,r,u);p.x=q;break}}}return p},z:0},win:{disable:function(){this.cancel=true},cancel:false,loaded:false,unload:false,hasRun:0,init:function(){var p=this;if(!p.hasRun){p.hasRun=1;if((/complete/i).test(document.readyState||"")){p.loaded=true;}else{j.ev.addEvent(window,"load",p.onLoad)}j.ev.addEvent(window,"unload",p.onUnload)}},loadPrvtHndlrs:[],loadPblcHndlrs:[],unloadHndlrs:[],onUnload:function(){var p=j.win;if(p.unload){return}p.unload=true;j.ev.removeEvent(window,"load",p.onLoad);j.ev.removeEvent(window,"unload",p.onUnload);j.ev.callArray(p.unloadHndlrs)},onLoad:function(){var p=j.win;if(p.loaded||p.unload||p.cancel){return}p.loaded=true;j.ev.callArray(p.loadPrvtHndlrs);j.ev.callArray(p.loadPblcHndlrs);}},DOM:{isEnabled:{objectTag:function(){var q=j.browser,p=q.isIE?0:1;if(q.ActiveXEnabled){p=1}return !!p},objectTagUsingActiveX:function(){var p=0;if(j.browser.ActiveXEnabled){p=1}return !!p},objectProperty:function(p){if(p&&p.tagName&&j.browser.isIE){if((/applet/i).test(p.tagName)){return(!this.objectTag()||j.isDefined(j.pd.getPROP(document.createElement("object"),"object"))?1:0)}return j.isDefined(j.pd.getPROP(document.createElement(p.tagName),"object"))?1:0}return 0}},HTML:[],div:null,divID:"plugindetect",divWidth:500,getDiv:function(){return this.div||document.getElementById(this.divID)||null},initDiv:function(){var q=this,p;if(!q.div){p=q.getDiv();if(p){q.div=p;}else{q.div=document.createElement("div");q.div.id=q.divID;q.setStyle(q.div,q.getStyle.div());q.insertDivInBody(q.div)}j.ev.fPush([q.onUnload,q],j.win.unloadHndlrs)}p=0},pluginSize:1,iframeWidth:40,iframeHeight:10,altHTML:"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;",emptyNode:function(q){var p=this;if(q&&(/div|span/i).test(q.tagName||"")){if(j.browser.isIE){p.setStyle(q,["display","none"])}try{q.innerHTML=""}catch(r){}}},removeNode:function(p){try{if(p&&p.parentNode){p.parentNode.removeChild(p)}}catch(q){}},onUnload:function(u,t){var r,q,s,v,w=t.HTML,p=w.length;if(p){for(q=p-1;q>=0;q--){v=w[q];if(v){w[q]=0;t.emptyNode(v.span());t.removeNode(v.span());v.span=0;v.spanObj=0;v.doc=0;v.objectProperty=0}}}r=t.getDiv();t.emptyNode(r);t.removeNode(r);v=0;s=0;r=0;t.div=0},span:function(){var p=this;if(!p.spanObj){p.spanObj=p.doc.getElementById(p.spanId)}return p.spanObj||null},width:function(){var t=this,s=t.span(),q,r,p=-1;q=s&&j.isNum(s.scrollWidth)?s.scrollWidth:p;r=s&&j.isNum(s.offsetWidth)?s.offsetWidth:p;s=0;return r>0?r:(q>0?q:Math.max(r,q))},obj:function(){var p=this.span();return p?p.firstChild||null:null},readyState:function(){var p=this;return j.browser.isIE&&j.isDefined(j.pd.getPROP(p.span(),"readyState"))?j.pd.getPROP(p.obj(),"readyState"):j.UNDEFINED},objectProperty:function(){var r=this,q=r.DOM,p;if(q.isEnabled.objectProperty(r)){p=j.pd.getPROP(r.obj(),"object")}return p},onLoadHdlr:function(p,q){q.loaded=1},getTagStatus:function(q,A,E,D,t,H,v){var F=this;if(!q||!q.span()){return -2}var y=q.width(),r=q.obj()?1:0,s=q.readyState(),p=q.objectProperty();if(p){return 1.5}var u=/clsid\s*\:/i,C=E&&u.test(E.outerHTML||"")?E:(D&&u.test(D.outerHTML||"")?D:0),w=E&&!u.test(E.outerHTML||"")?E:(D&&!u.test(D.outerHTML||"")?D:0),z=q&&u.test(q.outerHTML||"")?C:w;if(!A||!A.span()||!z||!z.span()){return -2}var x=z.width(),B=A.width(),G=z.readyState();if(y<0||x<0||B<=F.pluginSize){return 0}if(v&&!q.pi&&j.isDefined(p)&&j.browser.isIE&&q.tagName==z.tagName&&q.time<=z.time&&y===x&&s===0&&G!==0){q.pi=1}if(x<B||!q.loaded||!A.loaded||!z.loaded){return q.pi?-0.1:0}if(y==B||!r){return q.pi?-0.5:-1}else{if(y==F.pluginSize&&r&&(!j.isNum(s)||s===4)){return 1}}return q.pi?-0.5:-1},setStyle:function(q,t){var s=q.style,p;if(s&&t){for(p=0;p<t.length;p=p+2){try{s[t[p]]=t[p+1]}catch(r){}}}q=0;s=0},getStyle:{iframe:function(){return this.span()},span:function(r){var q=j.DOM,p;p=r?this.plugin():([].concat(this.Default).concat(["display","inline","fontSize",(q.pluginSize+3)+"px","lineHeight",(q.pluginSize+3)+"px"]));return p},div:function(){var p=j.DOM;return[].concat(this.Default).concat(["display","block","width",p.divWidth+"px","height",(p.pluginSize+3)+"px","fontSize",(p.pluginSize+3)+"px","lineHeight",(p.pluginSize+3)+"px","position","absolute","right","9999px","top","-9999px"])},plugin:function(q){var p=j.DOM;return"background-color:transparent;background-image:none;vertical-align:baseline;outline-style:none;border-style:none;padding:0px;margin:0px;visibility:"+(q?"hidden;":"visible;")+"display:inline;font-size:"+(p.pluginSize+3)+"px;line-height:"+(p.pluginSize+3)+"px;"},Default:["backgroundColor","transparent","backgroundImage","none","verticalAlign","baseline","outlineStyle","none","borderStyle","none","padding","0px","margin","0px","visibility","visible"]},insertDivInBody:function(v,t){var u="pd33993399",q=null,s=t?window.top.document:window.document,p=s.getElementsByTagName("body")[0]||s.body;if(!p){try{s.write('<div id="'+u+'">.'+j.openTag+"/div>");q=s.getElementById(u)}catch(r){}}p=s.getElementsByTagName("body")[0]||s.body;if(p){p.insertBefore(v,p.firstChild);if(q){p.removeChild(q)}}v=0},iframe:{onLoad:function(p,q){j.ev.callArray(p);},insert:function(s,v){var q=this,w=j.DOM,p,r=document.createElement("iframe"),x,t;w.setStyle(r,w.getStyle.iframe());r.width=w.iframeWidth;r.height=w.iframeHeight;w.initDiv();p=w.getDiv();p.appendChild(r);try{q.doc(r).open()}catch(u){}r[j.uniqueName()]=[];x=j.ev.handlerOnce(j.isNum(s)&&s>0?j.ev.handlerWait(s,q.onLoad,r[j.uniqueName()],v):j.ev.handler(q.onLoad,r[j.uniqueName()],v));j.ev.addEvent(r,"load",x);if(!r.onload){r.onload=x}t=q.win(r);j.ev.addEvent(t,"load",x);if(t&&!t.onload){t.onload=x}return r},addHandler:function(q,p){if(q){j.ev.fPush(p,q[j.uniqueName()])}},close:function(p){try{this.doc(p).close()}catch(q){}},write:function(q,u){var t=this.doc(q),p=-1,s;try{s=new Date().getTime();t.write(u);p=new Date().getTime()-s}catch(r){}return p},win:function(p){try{return p.contentWindow}catch(q){}return null},doc:function(p){var r;try{r=p.contentWindow.document}catch(q){}try{if(!r){r=p.contentDocument}}catch(q){}return r||null}},insert:function(t,s,u,p,z,y,v){var E=this,G,F,D,C,B,w;if(!v){E.initDiv();v=E.getDiv()}if(v){if((/div/i).test(v.tagName)){C=v.ownerDocument}if((/iframe/i).test(v.tagName)){C=E.iframe.doc(v)}}if(C&&C.createElement){}else{C=document}if(!j.isDefined(p)){p=""}if(j.isString(t)&&(/[^\s]/).test(t)){t=t.toLowerCase().replace(/\s/g,"");G=j.openTag+t+" ";G+='style="'+E.getStyle.plugin(y)+'" ';var r=1,q=1;for(B=0;B<s.length;B=B+2){if(/[^\s]/.test(s[B+1])){G+=s[B]+'="'+s[B+1]+'" '}if((/width/i).test(s[B])){r=0}if((/height/i).test(s[B])){q=0}}G+=(r?'width="'+E.pluginSize+'" ':"")+(q?'height="'+E.pluginSize+'" ':"");if(t=="embed"||t=="img"){G+=" />"}else{G+=">";for(B=0;B<u.length;B=B+2){if(/[^\s]/.test(u[B+1])){G+=j.openTag+'param name="'+u[B]+'" value="'+u[B+1]+'" />'}}G+=p+j.openTag+"/"+t+">"}}else{t="";G=p}F={spanId:"",spanObj:null,span:E.span,loaded:null,tagName:t,outerHTML:G,DOM:E,time:new Date().getTime(),insertDomDelay:-1,width:E.width,obj:E.obj,readyState:E.readyState,objectProperty:E.objectProperty,doc:C};if(v&&v.parentNode){if((/iframe/i).test(v.tagName)){E.iframe.addHandler(v,[E.onLoadHdlr,F]);F.loaded=0;F.spanId=j.name+"Span"+E.HTML.length;D='<span id="'+F.spanId+'" style="'+E.getStyle.span(1)+'">'+G+"</span>";F.time=new Date().getTime();w=E.iframe.write(v,D);if(w>=0){F.insertDomDelay=w}}else{if((/div/i).test(v.tagName)){D=C.createElement("span");E.setStyle(D,E.getStyle.span());v.appendChild(D);try{F.time=new Date().getTime();D.innerHTML=G;F.insertDomDelay=new Date().getTime()-F.time}catch(A){}F.spanObj=D}}}D=0;v=0;E.HTML.push(F);return F}},Plugins:{}};j.init.library();var f={compareNums:function(s,r){var A=s.split(j.splitNumRegx),y=r.split(j.splitNumRegx),w,q,p,v,u,z;for(w=0;w<Math.min(A.length,y.length);w++){z=/([\d]+)([a-z]?)/.test(A[w]);q=parseInt(RegExp.$1,10);v=(w==2&&RegExp.$2.length>0)?RegExp.$2.charCodeAt(0):-1;z=/([\d]+)([a-z]?)/.test(y[w]);p=parseInt(RegExp.$1,10);u=(w==2&&RegExp.$2.length>0)?RegExp.$2.charCodeAt(0):-1;if(q!=p){return(q>p?1:-1)}if(w==2&&v!=u){return(v>u?1:-1)}}return 0},setPluginStatus:function(r,p,s){var q=this;q.installed=p?1:(s?(s>0?0.7:-0.1):(r?0:-1));if(p){q.version=j.formatNum(p)}q.getVersionDone=q.installed==1||q.installed==-1||q.instance.hasRun?1:0;},getVersion:function(t,q){var u=this,s,p=null,r;if((!s||j.dbug)&&u.nav.query().installed){s=1}if((!p||j.dbug)&&u.nav.query().version){p=u.nav.version}if((!s||j.dbug)&&u.axo.query().installed){s=1}if((!p||j.dbug)&&u.axo.query().version){p=u.axo.version}if(!p||j.dbug){r=u.codebase.isMin(t);if(r){u.setPluginStatus(0,0,r);return}}if(!p||j.dbug){r=u.codebase.search();if(r){s=1;p=r}}if((!p&&q)||j.dbug){r=u.instance.query().version;if(r){s=1;p=r}}u.setPluginStatus(s,p,0)},nav:{hasRun:0,installed:0,version:null,mimeType:["application/x-vlc-plugin","application/x-google-vlc-plugin","application/mpeg4-muxcodetable","application/x-matroska","application/xspf+xml","video/divx","video/webm","video/x-mpeg","video/x-msvideo","video/ogg","audio/x-flac","audio/amr","audio/amr"],find:"VLC.*Plug-?in",find2:"VLC|VideoLAN",avoid:"Totem|Helix",plugins:["VLC Web Plugin","VLC Multimedia Plug-in","VLC Multimedia Plugin","VLC multimedia plugin"],query:function(){var s=this,p,r,q=s.hasRun||!j.hasMimeType(s.mimeType);s.hasRun=1;if(q){return s}r=j.pd.findNavPlugin({find:s.find,avoid:s.avoid,mimes:s.mimeType,plugins:s.plugins});if(r){s.installed=1;if(r.description){p=j.getNum(r.description+"","[\\d][\\d\\.]*[a-z]*")}p=j.getPluginFileVersion(r,p);if(p){s.version=p}}return s}},instance:{hasRun:0,installed:0,version:null,mimeType:"application/x-vlc-plugin",HTML:0,isDisabled:function(){var q=this,p=1;if(q.hasRun){}else{if(j.dbug){p=0}else{if(f.nav.installed&&j.hasMimeType(q.mimeType)){p=0}}}return p},query:function(){var p=this,s=j.DOM.altHTML,r=null,u=0,q=p.isDisabled();p.hasRun=1;if(q){return p}p.HTML=j.DOM.insert("object",["type",p.mimeType],["autoplay","no","loop","no","volume","0"],s,f);u=p.HTML.obj();if(u){try{if(!r){r=j.getNum(u.VersionInfo)}}catch(t){}try{if(!r){r=j.getNum(u.versionInfo())}}catch(t){}}if(r){p.version=r;p.installed=1}return p}},axo:{hasRun:0,installed:0,version:null,progID:"VideoLAN.VLCPlugin",query:function(){var q=this,s,p,r=q.hasRun;q.hasRun=1;if(r){return q}s=j.getAXO(q.progID);if(s){q.installed=1;p=j.getNum(j.pd.getPROP(s,"VersionInfo"),"[\\d][\\d\\.]*[a-z]*");if(p){q.version=p}}return q}},codebase:{classID:"clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921",isMin:function(p){this.$$=f;return j.codebase.isMin(this,p)},search:function(){this.$$=f;return j.codebase.search(this)},DIGITMAX:[[11,11,16]],DIGITMIN:[0,0,0,0],Upper:["999"],Lower:["0"],convert:[1]}};j.addPlugin("vlc",f);})();
Public/assets/janus.js
1
/*
2
	The MIT License (MIT)
3

  
4
	Copyright (c) 2016 Meetecho
5

  
6
	Permission is hereby granted, free of charge, to any person obtaining
7
	a copy of this software and associated documentation files (the "Software"),
8
	to deal in the Software without restriction, including without limitation
9
	the rights to use, copy, modify, merge, publish, distribute, sublicense,
10
	and/or sell copies of the Software, and to permit persons to whom the
11
	Software is furnished to do so, subject to the following conditions:
12

  
13
	The above copyright notice and this permission notice shall be included
14
	in all copies or substantial portions of the Software.
15

  
16
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
	THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
	OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
	ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
	OTHER DEALINGS IN THE SOFTWARE.
23
 */
24

  
25
// List of sessions
26
Janus.sessions = {};
27

  
28
// Screensharing Chrome Extension ID
29
Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj";
30
Janus.isExtensionEnabled = function() {
31
	if(window.navigator.userAgent.match('Chrome')) {
32
		var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
33
		var maxver = 33;
34
		if(window.navigator.userAgent.match('Linux'))
35
			maxver = 35;	// "known" crash in chrome 34 and 35 on linux
36
		if(chromever >= 26 && chromever <= maxver) {
37
			// Older versions of Chrome don't support this extension-based approach, so lie
38
			return true;
39
		}
40
		return Janus.checkJanusExtension();
41
	} else {
42
		// Firefox of others, no need for the extension (but this doesn't mean it will work)
43
		return true;
44
	}
45
};
46

  
47
Janus.useDefaultDependencies = function (deps) {
48
	var f = (deps && deps.fetch) || fetch;
49
	var p = (deps && deps.Promise) || Promise;
50
	var socketCls = (deps && deps.WebSocket) || WebSocket;
51
	return {
52
		newWebSocket: function(server, proto) { return new socketCls(server, proto); },
53
		isArray: function(arr) { return Array.isArray(arr); },
54
		checkJanusExtension: function() { return document.querySelector('#janus-extension-installed') !== null; },
55
		webRTCAdapter: (deps && deps.adapter) || adapter,
56
		httpAPICall: function(url, options) {
57
			var fetchOptions = {method: options.verb, cache: 'no-cache'};
58
			if(options.withCredentials !== undefined) {
59
				fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit');
60
			}
61
			if(options.body !== undefined) {
62
				fetchOptions.body = JSON.stringify(options.body);
63
			}
64

  
65
			var fetching = f(url, fetchOptions).catch(function(error) {
66
				return p.reject({message: 'Probably a network error, is the gateway down?', error: error});
67
			});
68

  
69
			/*
70
			 * fetch() does not natively support timeouts.
71
			 * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first.
72
			 */
73

  
74
			if(options.timeout !== undefined) {
75
				var timeout = new p(function(resolve, reject) {
76
					var timerId = setTimeout(function() {
77
						clearTimeout(timerId);
78
						return reject({message: 'Request timed out', timeout: options.timeout});
79
					}, options.timeout);
80
				});
81
				fetching = p.race([fetching,timeout]);
82
			}
83

  
84
			fetching.then(function(response) {
85
				if(response.ok) {
86
					if(typeof(options.success) === typeof(Janus.noop)) {
87
						return response.json().then(function(parsed) {
88
							options.success(parsed);
89
						}).catch(function(error) {
90
							return p.reject({message: 'Failed to parse response body', error: error, response: response});
91
						});
92
					}
93
				}
94
				else {
95
					return p.reject({message: 'API call failed', response: response});
96
				}
97
			}).catch(function(error) {
98
				if(typeof(options.error) === typeof(Janus.noop)) {
99
					options.error(error.message || '<< internal error >>', error);
100
				}
101
			});
102

  
103
			return fetching;
104
		}
105
	}
106
};
107

  
108
Janus.useOldDependencies = function (deps) {
109
	var jq = (deps && deps.jQuery) || jQuery;
110
	var socketCls = (deps && deps.WebSocket) || WebSocket;
111
	return {
112
		newWebSocket: function(server, proto) { return new socketCls(server, proto); },
113
		isArray: function(arr) { return jq.isArray(arr); },
114
		checkJanusExtension: function() { return jq('#janus-extension-installed').length > 0; },
115
		webRTCAdapter: (deps && deps.adapter) || adapter,
116
		httpAPICall: function(url, options) {
117
			var payload = options.body !== undefined ? {
118
				contentType: 'application/json',
119
				data: JSON.stringify(options.body)
120
			} : {};
121
			var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {};
122

  
123
			return jq.ajax(jq.extend(payload, credentials, {
124
				url: url,
125
				type: options.verb,
126
				cache: false,
127
				dataType: 'json',
128
				async: options.async,
129
				timeout: options.timeout,
130
				success: function(result) {
131
					if(typeof(options.success) === typeof(Janus.noop)) {
132
						options.success(result);
133
					}
134
				},
135
				error: function(xhr, status, err) {
136
					if(typeof(options.error) === typeof(Janus.noop)) {
137
						options.error(status, err);
138
					}
139
				}
140
			}));
141
		},
142
	};
143
};
144

  
145
Janus.noop = function() {};
146

  
147
// Initialization
148
Janus.init = function(options) {
149
	options = options || {};
150
	options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
151
	if(Janus.initDone === true) {
152
		// Already initialized
153
		options.callback();
154
	} else {
155
		if(typeof console == "undefined" || typeof console.log == "undefined")
156
			console = { log: function() {} };
157
		// Console logging (all debugging disabled by default)
158
		Janus.trace = Janus.noop;
159
		Janus.debug = Janus.noop;
160
		Janus.vdebug = Janus.noop;
161
		Janus.log = Janus.noop;
162
		Janus.warn = Janus.noop;
163
		Janus.error = Janus.noop;
164
		if(options.debug === true || options.debug === "all") {
165
			// Enable all debugging levels
166
			Janus.trace = console.trace.bind(console);
167
			Janus.debug = console.debug.bind(console);
168
			Janus.vdebug = console.debug.bind(console);
169
			Janus.log = console.log.bind(console);
170
			Janus.warn = console.warn.bind(console);
171
			Janus.error = console.error.bind(console);
172
		} else if(Array.isArray(options.debug)) {
173
			for(var i in options.debug) {
174
				var d = options.debug[i];
175
				switch(d) {
176
					case "trace":
177
						Janus.trace = console.trace.bind(console);
178
						break;
179
					case "debug":
180
						Janus.debug = console.debug.bind(console);
181
						break;
182
					case "vdebug":
183
						Janus.vdebug = console.debug.bind(console);
184
						break;
185
					case "log":
186
						Janus.log = console.log.bind(console);
187
						break;
188
					case "warn":
189
						Janus.warn = console.warn.bind(console);
190
						break;
191
					case "error":
192
						Janus.error = console.error.bind(console);
193
						break;
194
					default:
195
						console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')");
196
						break;
197
				}
198
			}
199
		}
200
		Janus.log("Initializing library");
201

  
202
		var usedDependencies = options.dependencies || Janus.useDefaultDependencies();
203
		Janus.isArray = usedDependencies.isArray;
204
		Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
205
		Janus.httpAPICall = usedDependencies.httpAPICall;
206
		Janus.checkJanusExtension = usedDependencies.checkJanusExtension;
207
		Janus.newWebSocket = usedDependencies.newWebSocket;
208

  
209
		// Helper method to enumerate devices
210
		Janus.listDevices = function(callback, config) {
211
			callback = (typeof callback == "function") ? callback : Janus.noop;
212
			if (config == null) config = { audio: true, video: true };
213
			if(navigator.mediaDevices) {
214
				navigator.mediaDevices.getUserMedia(config)
215
				.then(function(stream) {
216
					navigator.mediaDevices.enumerateDevices().then(function(devices) {
217
						Janus.debug(devices);
218
						callback(devices);
219
						// Get rid of the now useless stream
220
						try {
221
							var tracks = stream.getTracks();
222
							for(var i in tracks) {
223
								var mst = tracks[i];
224
								if(mst !== null && mst !== undefined)
225
									mst.stop();
226
							}
227
						} catch(e) {}
228
					});
229
				})
230
				.catch(function(err) {
231
					Janus.error(err);
232
					callback([]);
233
				});
234
			} else {
235
				Janus.warn("navigator.mediaDevices unavailable");
236
				callback([]);
237
			}
238
		}
239
		// Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
240
		Janus.attachMediaStream = function(element, stream) {
241
			if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
242
				var chromever = Janus.webRTCAdapter.browserDetails.version;
243
				if(chromever >= 43) {
244
					element.srcObject = stream;
245
				} else if(typeof element.src !== 'undefined') {
246
					element.src = URL.createObjectURL(stream);
247
				} else {
248
					Janus.error("Error attaching stream to element");
249
				}
250
			} else {
251
				element.srcObject = stream;
252
			}
253
		};
254
		Janus.reattachMediaStream = function(to, from) {
255
			if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {
256
				var chromever = Janus.webRTCAdapter.browserDetails.version;
257
				if(chromever >= 43) {
258
					to.srcObject = from.srcObject;
259
				} else if(typeof to.src !== 'undefined') {
260
					to.src = from.src;
261
				} else {
262
					Janus.error("Error reattaching stream to element");
263
				}
264
			} else {
265
				to.srcObject = from.srcObject;
266
			}
267
		};
268
		// Detect tab close: make sure we don't loose existing onbeforeunload handlers
269
		var oldOBF = window.onbeforeunload;
270
		window.onbeforeunload = function() {
271
			Janus.log("Closing window");
272
			for(var s in Janus.sessions) {
273
				if(Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
274
						Janus.sessions[s].destroyOnUnload) {
275
					Janus.log("Destroying session " + s);
276
					Janus.sessions[s].destroy({asyncRequest: false});
277
				}
278
			}
279
			if(oldOBF && typeof oldOBF == "function")
280
				oldOBF();
281
		}
282
		Janus.initDone = true;
283
		options.callback();
284
	}
285
};
286

  
287
// Helper method to check whether WebRTC is supported by this browser
288
Janus.isWebrtcSupported = function() {
289
	return window.RTCPeerConnection !== undefined && window.RTCPeerConnection !== null &&
290
		navigator.getUserMedia !== undefined && navigator.getUserMedia !== null;
291
};
292

  
293
// Helper method to create random identifiers (e.g., transaction)
294
Janus.randomString = function(len) {
295
	var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
296
	var randomString = '';
297
	for (var i = 0; i < len; i++) {
298
		var randomPoz = Math.floor(Math.random() * charSet.length);
299
		randomString += charSet.substring(randomPoz,randomPoz+1);
300
	}
301
	return randomString;
302
}
303

  
304

  
305
function Janus(gatewayCallbacks) {
306
	if(Janus.initDone === undefined) {
307
		gatewayCallbacks.error("Library not initialized");
308
		return {};
309
	}
310
	if(!Janus.isWebrtcSupported()) {
311
		gatewayCallbacks.error("WebRTC not supported by this browser");
312
		return {};
313
	}
314
	Janus.log("Library initialized: " + Janus.initDone);
315
	gatewayCallbacks = gatewayCallbacks || {};
316
	gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop;
317
	gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop;
318
	gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop;
319
	if(gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
320
		gatewayCallbacks.error("Invalid gateway url");
321
		return {};
322
	}
323
	var websockets = false;
324
	var ws = null;
325
	var wsHandlers = {};
326
	var wsKeepaliveTimeoutId = null;
327

  
328
	var servers = null, serversIndex = 0;
329
	var server = gatewayCallbacks.server;
330
	if(Janus.isArray(server)) {
331
		Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
332
		server = null;
333
		servers = gatewayCallbacks.server;
334
		Janus.debug(servers);
335
	} else {
336
		if(server.indexOf("ws") === 0) {
337
			websockets = true;
338
			Janus.log("Using WebSockets to contact Janus: " + server);
339
		} else {
340
			websockets = false;
341
			Janus.log("Using REST API to contact Janus: " + server);
342
		}
343
	}
344
	var iceServers = gatewayCallbacks.iceServers;
345
	if(iceServers === undefined || iceServers === null)
346
		iceServers = [{urls: "stun:stun.l.google.com:19302"}];
347
	var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
348
	var bundlePolicy = gatewayCallbacks.bundlePolicy;
349
	// Whether IPv6 candidates should be gathered
350
	var ipv6Support = gatewayCallbacks.ipv6;
351
	if(ipv6Support === undefined || ipv6Support === null)
352
		ipv6Support = false;
353
	// Whether we should enable the withCredentials flag for XHR requests
354
	var withCredentials = false;
355
	if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)
356
		withCredentials = gatewayCallbacks.withCredentials === true;
357
	// Optional max events
358
	var maxev = null;
359
	if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
360
		maxev = gatewayCallbacks.max_poll_events;
361
	if(maxev < 1)
362
		maxev = 1;
363
	// Token to use (only if the token based authentication mechanism is enabled)
364
	var token = null;
365
	if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
366
		token = gatewayCallbacks.token;
367
	// API secret to use (only if the shared API secret is enabled)
368
	var apisecret = null;
369
	if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
370
		apisecret = gatewayCallbacks.apisecret;
371
	// Whether we should destroy this session when onbeforeunload is called
372
	this.destroyOnUnload = true;
373
	if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
374
		this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
375

  
376
	var connected = false;
377
	var sessionId = null;
378
	var pluginHandles = {};
379
	var that = this;
380
	var retries = 0;
381
	var transactions = {};
382
	createSession(gatewayCallbacks);
383

  
384
	// Public methods
385
	this.getServer = function() { return server; };
386
	this.isConnected = function() { return connected; };
387
	this.getSessionId = function() { return sessionId; };
388
	this.destroy = function(callbacks) { destroySession(callbacks); };
389
	this.attach = function(callbacks) { createHandle(callbacks); };
390

  
391
	function eventHandler() {
392
		if(sessionId == null)
393
			return;
394
		Janus.debug('Long poll...');
395
		if(!connected) {
396
			Janus.warn("Is the gateway down? (connected=false)");
397
			return;
398
		}
399
		var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
400
		if(maxev !== undefined && maxev !== null)
401
			longpoll = longpoll + "&maxev=" + maxev;
402
		if(token !== null && token !== undefined)
403
			longpoll = longpoll + "&token=" + token;
404
		if(apisecret !== null && apisecret !== undefined)
405
			longpoll = longpoll + "&apisecret=" + apisecret;
406
		Janus.httpAPICall(longpoll, {
407
			verb: 'GET',
408
			withCredentials: withCredentials,
409
			success: handleEvent,
410
			timeout: 60000,	// FIXME
411
			error: function(textStatus, errorThrown) {
412
				Janus.error(textStatus + ": " + errorThrown);
413
				retries++;
414
				if(retries > 3) {
415
					// Did we just lose the gateway? :-(
416
					connected = false;
417
					gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
418
					return;
419
				}
420
				eventHandler();
421
			}
422
		});
423
	}
424

  
425
	// Private event handler: this will trigger plugin callbacks, if set
426
	function handleEvent(json, skipTimeout) {
427
		retries = 0;
428
		if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true)
429
			setTimeout(eventHandler, 200);
430
		if(!websockets && Janus.isArray(json)) {
431
			// We got an array: it means we passed a maxev > 1, iterate on all objects
432
			for(var i=0; i<json.length; i++) {
433
				handleEvent(json[i], true);
434
			}
435
			return;
436
		}
437
		if(json["janus"] === "keepalive") {
438
			// Nothing happened
439
			Janus.vdebug("Got a keepalive on session " + sessionId);
440
			return;
441
		} else if(json["janus"] === "ack") {
442
			// Just an ack, we can probably ignore
443
			Janus.debug("Got an ack on session " + sessionId);
444
			Janus.debug(json);
445
			var transaction = json["transaction"];
446
			if(transaction !== null && transaction !== undefined) {
447
				var reportSuccess = transactions[transaction];
448
				if(reportSuccess !== null && reportSuccess !== undefined) {
449
					reportSuccess(json);
450
				}
451
				delete transactions[transaction];
452
			}
453
			return;
454
		} else if(json["janus"] === "success") {
455
			// Success!
456
			Janus.debug("Got a success on session " + sessionId);
457
			Janus.debug(json);
458
			var transaction = json["transaction"];
459
			if(transaction !== null && transaction !== undefined) {
460
				var reportSuccess = transactions[transaction];
461
				if(reportSuccess !== null && reportSuccess !== undefined) {
462
					reportSuccess(json);
463
				}
464
				delete transactions[transaction];
465
			}
466
			return;
467
		} else if(json["janus"] === "webrtcup") {
468
			// The PeerConnection with the gateway is up! Notify this
469
			Janus.debug("Got a webrtcup event on session " + sessionId);
470
			Janus.debug(json);
471
			var sender = json["sender"];
472
			if(sender === undefined || sender === null) {
473
				Janus.warn("Missing sender...");
474
				return;
475
			}
476
			var pluginHandle = pluginHandles[sender];
477
			if(pluginHandle === undefined || pluginHandle === null) {
478
				Janus.debug("This handle is not attached to this session");
479
				return;
480
			}
481
			pluginHandle.webrtcState(true);
482
			return;
483
		} else if(json["janus"] === "hangup") {
484
			// A plugin asked the core to hangup a PeerConnection on one of our handles
485
			Janus.debug("Got a hangup event on session " + sessionId);
486
			Janus.debug(json);
487
			var sender = json["sender"];
488
			if(sender === undefined || sender === null) {
489
				Janus.warn("Missing sender...");
490
				return;
491
			}
492
			var pluginHandle = pluginHandles[sender];
493
			if(pluginHandle === undefined || pluginHandle === null) {
494
				Janus.debug("This handle is not attached to this session");
495
				return;
496
			}
497
			pluginHandle.webrtcState(false, json["reason"]);
498
			pluginHandle.hangup();
499
		} else if(json["janus"] === "detached") {
500
			// A plugin asked the core to detach one of our handles
501
			Janus.debug("Got a detached event on session " + sessionId);
502
			Janus.debug(json);
503
			var sender = json["sender"];
504
			if(sender === undefined || sender === null) {
505
				Janus.warn("Missing sender...");
506
				return;
507
			}
508
			var pluginHandle = pluginHandles[sender];
509
			if(pluginHandle === undefined || pluginHandle === null) {
510
				// Don't warn here because destroyHandle causes this situation.
511
				return;
512
			}
513
			pluginHandle.detached = true;
514
			pluginHandle.ondetached();
515
			pluginHandle.detach();
516
		} else if(json["janus"] === "media") {
517
			// Media started/stopped flowing
518
			Janus.debug("Got a media event on session " + sessionId);
519
			Janus.debug(json);
520
			var sender = json["sender"];
521
			if(sender === undefined || sender === null) {
522
				Janus.warn("Missing sender...");
523
				return;
524
			}
525
			var pluginHandle = pluginHandles[sender];
526
			if(pluginHandle === undefined || pluginHandle === null) {
527
				Janus.debug("This handle is not attached to this session");
528
				return;
529
			}
530
			pluginHandle.mediaState(json["type"], json["receiving"]);
531
		} else if(json["janus"] === "slowlink") {
532
			Janus.debug("Got a slowlink event on session " + sessionId);
533
			Janus.debug(json);
534
			// Trouble uplink or downlink
535
			var sender = json["sender"];
536
			if(sender === undefined || sender === null) {
537
				Janus.warn("Missing sender...");
538
				return;
539
			}
540
			var pluginHandle = pluginHandles[sender];
541
			if(pluginHandle === undefined || pluginHandle === null) {
542
				Janus.debug("This handle is not attached to this session");
543
				return;
544
			}
545
			pluginHandle.slowLink(json["uplink"], json["nacks"]);
546
		} else if(json["janus"] === "error") {
547
			// Oops, something wrong happened
548
			Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
549
			Janus.debug(json);
550
			var transaction = json["transaction"];
551
			if(transaction !== null && transaction !== undefined) {
552
				var reportSuccess = transactions[transaction];
553
				if(reportSuccess !== null && reportSuccess !== undefined) {
554
					reportSuccess(json);
555
				}
556
				delete transactions[transaction];
557
			}
558
			return;
559
		} else if(json["janus"] === "event") {
560
			Janus.debug("Got a plugin event on session " + sessionId);
561
			Janus.debug(json);
562
			var sender = json["sender"];
563
			if(sender === undefined || sender === null) {
564
				Janus.warn("Missing sender...");
565
				return;
566
			}
567
			var plugindata = json["plugindata"];
568
			if(plugindata === undefined || plugindata === null) {
569
				Janus.warn("Missing plugindata...");
570
				return;
571
			}
572
			Janus.debug("  -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
573
			var data = plugindata["data"];
574
			Janus.debug(data);
575
			var pluginHandle = pluginHandles[sender];
576
			if(pluginHandle === undefined || pluginHandle === null) {
577
				Janus.warn("This handle is not attached to this session");
578
				return;
579
			}
580
			var jsep = json["jsep"];
581
			if(jsep !== undefined && jsep !== null) {
582
				Janus.debug("Handling SDP as well...");
583
				Janus.debug(jsep);
584
			}
585
			var callback = pluginHandle.onmessage;
586
			if(callback !== null && callback !== undefined) {
587
				Janus.debug("Notifying application...");
588
				// Send to callback specified when attaching plugin handle
589
				callback(data, jsep);
590
			} else {
591
				// Send to generic callback (?)
592
				Janus.debug("No provided notification callback");
593
			}
594
		} else {
595
			Janus.warn("Unkown message/event  '" + json["janus"] + "' on session " + sessionId);
596
			Janus.debug(json);
597
		}
598
	}
599

  
600
	// Private helper to send keep-alive messages on WebSockets
601
	function keepAlive() {
602
		if(server === null || !websockets || !connected)
603
			return;
604
		wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
605
		var request = { "janus": "keepalive", "session_id": sessionId, "transaction": Janus.randomString(12) };
606
		if(token !== null && token !== undefined)
607
			request["token"] = token;
608
		if(apisecret !== null && apisecret !== undefined)
609
			request["apisecret"] = apisecret;
610
		ws.send(JSON.stringify(request));
611
	}
612

  
613
	// Private method to create a session
614
	function createSession(callbacks) {
615
		var transaction = Janus.randomString(12);
616
		var request = { "janus": "create", "transaction": transaction };
617
		if(token !== null && token !== undefined)
618
			request["token"] = token;
619
		if(apisecret !== null && apisecret !== undefined)
620
			request["apisecret"] = apisecret;
621
		if(server === null && Janus.isArray(servers)) {
622
			// We still need to find a working server from the list we were given
623
			server = servers[serversIndex];
624
			if(server.indexOf("ws") === 0) {
625
				websockets = true;
626
				Janus.log("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
627
			} else {
628
				websockets = false;
629
				Janus.log("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
630
			}
631
		}
632
		if(websockets) {
633
			ws = Janus.newWebSocket(server, 'janus-protocol');
634
			wsHandlers = {
635
				'error': function() {
636
					Janus.error("Error connecting to the Janus WebSockets server... " + server);
637
					if (Janus.isArray(servers)) {
638
						serversIndex++;
639
						if (serversIndex == servers.length) {
640
							// We tried all the servers the user gave us and they all failed
641
							callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
642
							return;
643
						}
644
						// Let's try the next server
645
						server = null;
646
						setTimeout(function() {
647
							createSession(callbacks);
648
						}, 200);
649
						return;
650
					}
651
					callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?");
652
				},
653

  
654
				'open': function() {
655
					// We need to be notified about the success
656
					transactions[transaction] = function(json) {
657
						Janus.debug(json);
658
						if (json["janus"] !== "success") {
659
							Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
660
							callbacks.error(json["error"].reason);
661
							return;
662
						}
663
						wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
664
						connected = true;
665
						sessionId = json.data["id"];
666
						Janus.log("Created session: " + sessionId);
667
						Janus.sessions[sessionId] = that;
668
						callbacks.success();
669
					};
670
					ws.send(JSON.stringify(request));
671
				},
672

  
673
				'message': function(event) {
674
					handleEvent(JSON.parse(event.data));
675
				},
676

  
677
				'close': function() {
678
					if (server === null || !connected) {
679
						return;
680
					}
681
					connected = false;
682
					// FIXME What if this is called when the page is closed?
683
					gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
684
				}
685
			};
686

  
687
			for(var eventName in wsHandlers) {
688
				ws.addEventListener(eventName, wsHandlers[eventName]);
689
			}
690

  
691
			return;
692
		}
693
		Janus.httpAPICall(server, {
694
			verb: 'POST',
695
			withCredentials: withCredentials,
696
			body: request,
697
			success: function(json) {
698
				Janus.debug(json);
699
				if(json["janus"] !== "success") {
700
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
701
					callbacks.error(json["error"].reason);
702
					return;
703
				}
704
				connected = true;
705
				sessionId = json.data["id"];
706
				Janus.log("Created session: " + sessionId);
707
				Janus.sessions[sessionId] = that;
708
				eventHandler();
709
				callbacks.success();
710
			},
711
			error: function(textStatus, errorThrown) {
712
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
713
				if(Janus.isArray(servers)) {
714
					serversIndex++;
715
					if(serversIndex == servers.length) {
716
						// We tried all the servers the user gave us and they all failed
717
						callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
718
						return;
719
					}
720
					// Let's try the next server
721
					server = null;
722
					setTimeout(function() { createSession(callbacks); }, 200);
723
					return;
724
				}
725
				if(errorThrown === "")
726
					callbacks.error(textStatus + ": Is the gateway down?");
727
				else
728
					callbacks.error(textStatus + ": " + errorThrown);
729
			}
730
		});
731
	}
732

  
733
	// Private method to destroy a session
734
	function destroySession(callbacks) {
735
		callbacks = callbacks || {};
736
		// FIXME This method triggers a success even when we fail
737
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
738
		var asyncRequest = true;
739
		if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
740
			asyncRequest = (callbacks.asyncRequest === true);
741
		Janus.log("Destroying session " + sessionId + " (async=" + asyncRequest + ")");
742
		if(!connected) {
743
			Janus.warn("Is the gateway down? (connected=false)");
744
			callbacks.success();
745
			return;
746
		}
747
		if(sessionId === undefined || sessionId === null) {
748
			Janus.warn("No session to destroy");
749
			callbacks.success();
750
			gatewayCallbacks.destroyed();
751
			return;
752
		}
753
		delete Janus.sessions[sessionId];
754
		// No need to destroy all handles first, Janus will do that itself
755
		var request = { "janus": "destroy", "transaction": Janus.randomString(12) };
756
		if(token !== null && token !== undefined)
757
			request["token"] = token;
758
		if(apisecret !== null && apisecret !== undefined)
759
			request["apisecret"] = apisecret;
760
		if(websockets) {
761
			request["session_id"] = sessionId;
762

  
763
			var unbindWebSocket = function() {
764
				for(var eventName in wsHandlers) {
765
					ws.removeEventListener(eventName, wsHandlers[eventName]);
766
				}
767
				ws.removeEventListener('message', onUnbindMessage);
768
				ws.removeEventListener('error', onUnbindError);
769
				if(wsKeepaliveTimeoutId) {
770
					clearTimeout(wsKeepaliveTimeoutId);
771
				}
772
			};
773

  
774
			var onUnbindMessage = function(event){
775
				var data = JSON.parse(event.data);
776
				if(data.session_id == request.session_id && data.transaction == request.transaction) {
777
					unbindWebSocket();
778
					callbacks.success();
779
					gatewayCallbacks.destroyed();
780
				}
781
			};
782
			var onUnbindError = function(event) {
783
				unbindWebSocket();
784
				callbacks.error("Failed to destroy the gateway: Is the gateway down?");
785
				gatewayCallbacks.destroyed();
786
			};
787

  
788
			ws.addEventListener('message', onUnbindMessage);
789
			ws.addEventListener('error', onUnbindError);
790

  
791
			ws.send(JSON.stringify(request));
792
			return;
793
		}
794
		Janus.httpAPICall(server + "/" + sessionId, {
795
			verb: 'POST',
796
			async: asyncRequest,	// Sometimes we need false here, or destroying in onbeforeunload won't work
797
			withCredentials: withCredentials,
798
			body: request,
799
			success: function(json) {
800
				Janus.log("Destroyed session:");
801
				Janus.debug(json);
802
				sessionId = null;
803
				connected = false;
804
				if(json["janus"] !== "success") {
805
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
806
				}
807
				callbacks.success();
808
				gatewayCallbacks.destroyed();
809
			},
810
			error: function(textStatus, errorThrown) {
811
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
812
				// Reset everything anyway
813
				sessionId = null;
814
				connected = false;
815
				callbacks.success();
816
				gatewayCallbacks.destroyed();
817
			}
818
		});
819
	}
820

  
821
	// Private method to create a plugin handle
822
	function createHandle(callbacks) {
823
		callbacks = callbacks || {};
824
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
825
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
826
		callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop;
827
		callbacks.iceState = (typeof callbacks.iceState == "function") ? callbacks.iceState : Janus.noop;
828
		callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop;
829
		callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop;
830
		callbacks.slowLink = (typeof callbacks.slowLink == "function") ? callbacks.slowLink : Janus.noop;
831
		callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop;
832
		callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop;
833
		callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop;
834
		callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop;
835
		callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop;
836
		callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop;
837
		callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop;
838
		if(!connected) {
839
			Janus.warn("Is the gateway down? (connected=false)");
840
			callbacks.error("Is the gateway down? (connected=false)");
841
			return;
842
		}
843
		var plugin = callbacks.plugin;
844
		if(plugin === undefined || plugin === null) {
845
			Janus.error("Invalid plugin");
846
			callbacks.error("Invalid plugin");
847
			return;
848
		}
849
		var opaqueId = callbacks.opaqueId;
850
		var transaction = Janus.randomString(12);
851
		var request = { "janus": "attach", "plugin": plugin, "opaque_id": opaqueId, "transaction": transaction };
852
		if(token !== null && token !== undefined)
853
			request["token"] = token;
854
		if(apisecret !== null && apisecret !== undefined)
855
			request["apisecret"] = apisecret;
856
		// If we know the browser supports BUNDLE and/or rtcp-mux, let's advertise those right away
857
		if(Janus.webRTCAdapter.browserDetails.browser == "chrome" || Janus.webRTCAdapter.browserDetails.browser == "firefox" ||
858
				Janus.webRTCAdapter.browserDetails.browser == "safari") {
859
			request["force-bundle"] = true;
860
			request["force-rtcp-mux"] = true;
861
		}
862
		if(websockets) {
863
			transactions[transaction] = function(json) {
864
				Janus.debug(json);
865
				if(json["janus"] !== "success") {
866
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
867
					callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
868
					return;
869
				}
870
				var handleId = json.data["id"];
871
				Janus.log("Created handle: " + handleId);
872
				var pluginHandle =
873
					{
874
						session : that,
875
						plugin : plugin,
876
						id : handleId,
877
						detached : false,
878
						webrtcStuff : {
879
							started : false,
880
							myStream : null,
881
							streamExternal : false,
882
							remoteStream : null,
883
							mySdp : null,
884
							pc : null,
885
							dataChannel : null,
886
							dtmfSender : null,
887
							trickle : true,
888
							iceDone : false,
889
							sdpSent : false,
890
							volume : {
891
								value : null,
892
								timer : null
893
							},
894
							bitrate : {
895
								value : null,
896
								bsnow : null,
897
								bsbefore : null,
898
								tsnow : null,
899
								tsbefore : null,
900
								timer : null
901
							}
902
						},
903
						getId : function() { return handleId; },
904
						getPlugin : function() { return plugin; },
905
						getVolume : function() { return getVolume(handleId); },
906
						isAudioMuted : function() { return isMuted(handleId, false); },
907
						muteAudio : function() { return mute(handleId, false, true); },
908
						unmuteAudio : function() { return mute(handleId, false, false); },
909
						isVideoMuted : function() { return isMuted(handleId, true); },
910
						muteVideo : function() { return mute(handleId, true, true); },
911
						unmuteVideo : function() { return mute(handleId, true, false); },
912
						getBitrate : function() { return getBitrate(handleId); },
913
						send : function(callbacks) { sendMessage(handleId, callbacks); },
914
						data : function(callbacks) { sendData(handleId, callbacks); },
915
						dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
916
						consentDialog : callbacks.consentDialog,
917
						iceState : callbacks.iceState,
918
						mediaState : callbacks.mediaState,
919
						webrtcState : callbacks.webrtcState,
920
						slowLink : callbacks.slowLink,
921
						onmessage : callbacks.onmessage,
922
						createOffer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
923
						createAnswer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
924
						handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
925
						onlocalstream : callbacks.onlocalstream,
926
						onremotestream : callbacks.onremotestream,
927
						ondata : callbacks.ondata,
928
						ondataopen : callbacks.ondataopen,
929
						oncleanup : callbacks.oncleanup,
930
						ondetached : callbacks.ondetached,
931
						hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
932
						detach : function(callbacks) { destroyHandle(handleId, callbacks); }
933
					}
934
				pluginHandles[handleId] = pluginHandle;
935
				callbacks.success(pluginHandle);
936
			};
937
			request["session_id"] = sessionId;
938
			ws.send(JSON.stringify(request));
939
			return;
940
		}
941
		Janus.httpAPICall(server + "/" + sessionId, {
942
			verb: 'POST',
943
			withCredentials: withCredentials,
944
			body: request,
945
			success: function(json) {
946
				Janus.debug(json);
947
				if(json["janus"] !== "success") {
948
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
949
					callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
950
					return;
951
				}
952
				var handleId = json.data["id"];
953
				Janus.log("Created handle: " + handleId);
954
				var pluginHandle =
955
					{
956
						session : that,
957
						plugin : plugin,
958
						id : handleId,
959
						detached : false,
960
						webrtcStuff : {
961
							started : false,
962
							myStream : null,
963
							streamExternal : false,
964
							remoteStream : null,
965
							mySdp : null,
966
							pc : null,
967
							dataChannel : null,
968
							dtmfSender : null,
969
							trickle : true,
970
							iceDone : false,
971
							sdpSent : false,
972
							volume : {
973
								value : null,
974
								timer : null
975
							},
976
							bitrate : {
977
								value : null,
978
								bsnow : null,
979
								bsbefore : null,
980
								tsnow : null,
981
								tsbefore : null,
982
								timer : null
983
							}
984
						},
985
						getId : function() { return handleId; },
986
						getPlugin : function() { return plugin; },
987
						getVolume : function() { return getVolume(handleId); },
988
						isAudioMuted : function() { return isMuted(handleId, false); },
989
						muteAudio : function() { return mute(handleId, false, true); },
990
						unmuteAudio : function() { return mute(handleId, false, false); },
991
						isVideoMuted : function() { return isMuted(handleId, true); },
992
						muteVideo : function() { return mute(handleId, true, true); },
993
						unmuteVideo : function() { return mute(handleId, true, false); },
994
						getBitrate : function() { return getBitrate(handleId); },
995
						send : function(callbacks) { sendMessage(handleId, callbacks); },
996
						data : function(callbacks) { sendData(handleId, callbacks); },
997
						dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
998
						consentDialog : callbacks.consentDialog,
999
						iceState : callbacks.iceState,
1000
						mediaState : callbacks.mediaState,
1001
						webrtcState : callbacks.webrtcState,
1002
						slowLink : callbacks.slowLink,
1003
						onmessage : callbacks.onmessage,
1004
						createOffer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
1005
						createAnswer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
1006
						handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
1007
						onlocalstream : callbacks.onlocalstream,
1008
						onremotestream : callbacks.onremotestream,
1009
						ondata : callbacks.ondata,
1010
						ondataopen : callbacks.ondataopen,
1011
						oncleanup : callbacks.oncleanup,
1012
						ondetached : callbacks.ondetached,
1013
						hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
1014
						detach : function(callbacks) { destroyHandle(handleId, callbacks); }
1015
					}
1016
				pluginHandles[handleId] = pluginHandle;
1017
				callbacks.success(pluginHandle);
1018
			},
1019
			error: function(textStatus, errorThrown) {
1020
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
1021
			}
1022
		});
1023
	}
1024

  
1025
	// Private method to send a message
1026
	function sendMessage(handleId, callbacks) {
1027
		callbacks = callbacks || {};
1028
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
1029
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
1030
		if(!connected) {
1031
			Janus.warn("Is the gateway down? (connected=false)");
1032
			callbacks.error("Is the gateway down? (connected=false)");
1033
			return;
1034
		}
1035
		var message = callbacks.message;
1036
		var jsep = callbacks.jsep;
1037
		var transaction = Janus.randomString(12);
1038
		var request = { "janus": "message", "body": message, "transaction": transaction };
1039
		if(token !== null && token !== undefined)
1040
			request["token"] = token;
1041
		if(apisecret !== null && apisecret !== undefined)
1042
			request["apisecret"] = apisecret;
1043
		if(jsep !== null && jsep !== undefined)
1044
			request.jsep = jsep;
1045
		Janus.debug("Sending message to plugin (handle=" + handleId + "):");
1046
		Janus.debug(request);
1047
		if(websockets) {
1048
			request["session_id"] = sessionId;
1049
			request["handle_id"] = handleId;
1050
			transactions[transaction] = function(json) {
1051
				Janus.debug("Message sent!");
1052
				Janus.debug(json);
1053
				if(json["janus"] === "success") {
1054
					// We got a success, must have been a synchronous transaction
1055
					var plugindata = json["plugindata"];
1056
					if(plugindata === undefined || plugindata === null) {
1057
						Janus.warn("Request succeeded, but missing plugindata...");
1058
						callbacks.success();
1059
						return;
1060
					}
1061
					Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
1062
					var data = plugindata["data"];
1063
					Janus.debug(data);
1064
					callbacks.success(data);
1065
					return;
1066
				} else if(json["janus"] !== "ack") {
1067
					// Not a success and not an ack, must be an error
1068
					if(json["error"] !== undefined && json["error"] !== null) {
1069
						Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
1070
						callbacks.error(json["error"].code + " " + json["error"].reason);
1071
					} else {
1072
						Janus.error("Unknown error");	// FIXME
1073
						callbacks.error("Unknown error");
1074
					}
1075
					return;
1076
				}
1077
				// If we got here, the plugin decided to handle the request asynchronously
1078
				callbacks.success();
1079
			};
1080
			ws.send(JSON.stringify(request));
1081
			return;
1082
		}
1083
		Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
1084
			verb: 'POST',
1085
			withCredentials: withCredentials,
1086
			body: request,
1087
			success: function(json) {
1088
				Janus.debug("Message sent!");
1089
				Janus.debug(json);
1090
				if(json["janus"] === "success") {
1091
					// We got a success, must have been a synchronous transaction
1092
					var plugindata = json["plugindata"];
1093
					if(plugindata === undefined || plugindata === null) {
1094
						Janus.warn("Request succeeded, but missing plugindata...");
1095
						callbacks.success();
1096
						return;
1097
					}
1098
					Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
1099
					var data = plugindata["data"];
1100
					Janus.debug(data);
1101
					callbacks.success(data);
1102
					return;
1103
				} else if(json["janus"] !== "ack") {
1104
					// Not a success and not an ack, must be an error
1105
					if(json["error"] !== undefined && json["error"] !== null) {
1106
						Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
1107
						callbacks.error(json["error"].code + " " + json["error"].reason);
1108
					} else {
1109
						Janus.error("Unknown error");	// FIXME
1110
						callbacks.error("Unknown error");
1111
					}
1112
					return;
1113
				}
1114
				// If we got here, the plugin decided to handle the request asynchronously
1115
				callbacks.success();
1116
			},
1117
			error: function(textStatus, errorThrown) {
1118
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
1119
				callbacks.error(textStatus + ": " + errorThrown);
1120
			}
1121
		});
1122
	}
1123

  
1124
	// Private method to send a trickle candidate
1125
	function sendTrickleCandidate(handleId, candidate) {
1126
		if(!connected) {
1127
			Janus.warn("Is the gateway down? (connected=false)");
1128
			return;
1129
		}
1130
		var request = { "janus": "trickle", "candidate": candidate, "transaction": Janus.randomString(12) };
1131
		if(token !== null && token !== undefined)
1132
			request["token"] = token;
1133
		if(apisecret !== null && apisecret !== undefined)
1134
			request["apisecret"] = apisecret;
1135
		Janus.vdebug("Sending trickle candidate (handle=" + handleId + "):");
1136
		Janus.vdebug(request);
1137
		if(websockets) {
1138
			request["session_id"] = sessionId;
1139
			request["handle_id"] = handleId;
1140
			ws.send(JSON.stringify(request));
1141
			return;
1142
		}
1143
		Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
1144
			verb: 'POST',
1145
			withCredentials: withCredentials,
1146
			body: request,
1147
			success: function(json) {
1148
				Janus.vdebug("Candidate sent!");
1149
				Janus.vdebug(json);
1150
				if(json["janus"] !== "ack") {
1151
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
1152
					return;
1153
				}
1154
			},
1155
			error: function(textStatus, errorThrown) {
1156
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
1157
			}
1158
		});
1159
	}
1160

  
1161
	// Private method to send a data channel message
1162
	function sendData(handleId, callbacks) {
1163
		callbacks = callbacks || {};
1164
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
1165
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
1166
		var pluginHandle = pluginHandles[handleId];
1167
		if(pluginHandle === null || pluginHandle === undefined ||
1168
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
1169
			Janus.warn("Invalid handle");
1170
			callbacks.error("Invalid handle");
1171
			return;
1172
		}
1173
		var config = pluginHandle.webrtcStuff;
1174
		var text = callbacks.text;
1175
		if(text === null || text === undefined) {
1176
			Janus.warn("Invalid text");
1177
			callbacks.error("Invalid text");
1178
			return;
1179
		}
1180
		Janus.log("Sending string on data channel: " + text);
1181
		config.dataChannel.send(text);
1182
		callbacks.success();
1183
	}
1184

  
1185
	// Private method to send a DTMF tone
1186
	function sendDtmf(handleId, callbacks) {
1187
		callbacks = callbacks || {};
1188
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
1189
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
1190
		var pluginHandle = pluginHandles[handleId];
1191
		if(pluginHandle === null || pluginHandle === undefined ||
1192
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
1193
			Janus.warn("Invalid handle");
1194
			callbacks.error("Invalid handle");
1195
			return;
1196
		}
1197
		var config = pluginHandle.webrtcStuff;
1198
		if(config.dtmfSender === null || config.dtmfSender === undefined) {
1199
			// Create the DTMF sender, if possible
1200
			if(config.myStream !== undefined && config.myStream !== null) {
1201
				var tracks = config.myStream.getAudioTracks();
1202
				if(tracks !== null && tracks !== undefined && tracks.length > 0) {
1203
					var local_audio_track = tracks[0];
1204
					config.dtmfSender = config.pc.createDTMFSender(local_audio_track);
1205
					Janus.log("Created DTMF Sender");
1206
					config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); };
1207
				}
1208
			}
1209
			if(config.dtmfSender === null || config.dtmfSender === undefined) {
1210
				Janus.warn("Invalid DTMF configuration");
1211
				callbacks.error("Invalid DTMF configuration");
1212
				return;
1213
			}
1214
		}
1215
		var dtmf = callbacks.dtmf;
1216
		if(dtmf === null || dtmf === undefined) {
1217
			Janus.warn("Invalid DTMF parameters");
1218
			callbacks.error("Invalid DTMF parameters");
1219
			return;
1220
		}
1221
		var tones = dtmf.tones;
1222
		if(tones === null || tones === undefined) {
1223
			Janus.warn("Invalid DTMF string");
1224
			callbacks.error("Invalid DTMF string");
1225
			return;
1226
		}
1227
		var duration = dtmf.duration;
1228
		if(duration === null || duration === undefined)
1229
			duration = 500;	// We choose 500ms as the default duration for a tone
1230
		var gap = dtmf.gap;
1231
		if(gap === null || gap === undefined)
1232
			gap = 50;	// We choose 50ms as the default gap between tones
1233
		Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)");
1234
		config.dtmfSender.insertDTMF(tones, duration, gap);
1235
	}
1236

  
1237
	// Private method to destroy a plugin handle
1238
	function destroyHandle(handleId, callbacks) {
1239
		callbacks = callbacks || {};
1240
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
1241
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
1242
		Janus.warn(callbacks);
1243
		var asyncRequest = true;
1244
		if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)
1245
			asyncRequest = (callbacks.asyncRequest === true);
1246
		Janus.log("Destroying handle " + handleId + " (async=" + asyncRequest + ")");
1247
		cleanupWebrtc(handleId);
1248
		if (pluginHandles[handleId].detached) {
1249
			// Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here
1250
			delete pluginHandles[handleId];
1251
			callbacks.success();
1252
			return;
1253
		}
1254
		if(!connected) {
1255
			Janus.warn("Is the gateway down? (connected=false)");
1256
			callbacks.error("Is the gateway down? (connected=false)");
1257
			return;
1258
		}
1259
		var request = { "janus": "detach", "transaction": Janus.randomString(12) };
1260
		if(token !== null && token !== undefined)
1261
			request["token"] = token;
1262
		if(apisecret !== null && apisecret !== undefined)
1263
			request["apisecret"] = apisecret;
1264
		if(websockets) {
1265
			request["session_id"] = sessionId;
1266
			request["handle_id"] = handleId;
1267
			ws.send(JSON.stringify(request));
1268
			delete pluginHandles[handleId];
1269
			callbacks.success();
1270
			return;
1271
		}
1272
		Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
1273
			verb: 'POST',
1274
			async: asyncRequest,	// Sometimes we need false here, or destroying in onbeforeunload won't work
1275
			withCredentials: withCredentials,
1276
			body: request,
1277
			success: function(json) {
1278
				Janus.log("Destroyed handle:");
1279
				Janus.debug(json);
1280
				if(json["janus"] !== "success") {
1281
					Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);	// FIXME
1282
				}
1283
				delete pluginHandles[handleId];
1284
				callbacks.success();
1285
			},
1286
			error: function(textStatus, errorThrown) {
1287
				Janus.error(textStatus + ": " + errorThrown);	// FIXME
1288
				// We cleanup anyway
1289
				delete pluginHandles[handleId];
1290
				callbacks.success();
1291
			}
1292
		});
1293
	}
1294

  
1295
	// WebRTC stuff
1296
	function streamsDone(handleId, jsep, media, callbacks, stream) {
1297
		var pluginHandle = pluginHandles[handleId];
1298
		if(pluginHandle === null || pluginHandle === undefined ||
1299
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
1300
			Janus.warn("Invalid handle");
1301
			callbacks.error("Invalid handle");
1302
			return;
1303
		}
1304
		var config = pluginHandle.webrtcStuff;
1305
		Janus.debug("streamsDone:", stream);
1306
		config.myStream = stream;
1307
		var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy};
1308
		//~ var pc_constraints = {'mandatory': {'MozDontOfferDataChannel':true}};
1309
		var pc_constraints = {
1310
			"optional": [{"DtlsSrtpKeyAgreement": true}]
1311
		};
1312
		if(ipv6Support === true) {
1313
			// FIXME This is only supported in Chrome right now
1314
			// For support in Firefox track this: https://bugzilla.mozilla.org/show_bug.cgi?id=797262
1315
			pc_constraints.optional.push({"googIPv6":true});
1316
		}
1317
		// Any custom constraint to add?
1318
		if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') {
1319
			Janus.debug("Adding custom PeerConnection constraints:", callbacks.rtcConstraints);
1320
			for(var i in callbacks.rtcConstraints) {
1321
				pc_constraints.optional.push(callbacks.rtcConstraints[i]);
1322
			}
1323
		}
1324
		if(Janus.webRTCAdapter.browserDetails.browser === "edge") {
1325
			// This is Edge, enable BUNDLE explicitly
1326
			pc_config.bundlePolicy = "max-bundle";
1327
		}
1328
		Janus.log("Creating PeerConnection");
1329
		Janus.debug(pc_constraints);
1330
		config.pc = new RTCPeerConnection(pc_config, pc_constraints);
1331
		Janus.debug(config.pc);
1332
		if(config.pc.getStats) {	// FIXME
1333
			config.volume.value = 0;
1334
			config.bitrate.value = "0 kbits/sec";
1335
		}
1336
		Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
1337
		config.pc.oniceconnectionstatechange = function(e) {
1338
			if(config.pc)
1339
				pluginHandle.iceState(config.pc.iceConnectionState);
1340
		};
1341
		config.pc.onicecandidate = function(event) {
1342
			if (event.candidate == null ||
1343
					(Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) {
1344
				Janus.log("End of candidates.");
1345
				config.iceDone = true;
1346
				if(config.trickle === true) {
1347
					// Notify end of candidates
1348
					sendTrickleCandidate(handleId, {"completed": true});
1349
				} else {
1350
					// No trickle, time to send the complete SDP (including all candidates)
1351
					sendSDP(handleId, callbacks);
1352
				}
1353
			} else {
1354
				// JSON.stringify doesn't work on some WebRTC objects anymore
1355
				// See https://code.google.com/p/chromium/issues/detail?id=467366
1356
				var candidate = {
1357
					"candidate": event.candidate.candidate,
1358
					"sdpMid": event.candidate.sdpMid,
1359
					"sdpMLineIndex": event.candidate.sdpMLineIndex
1360
				};
1361
				if(config.trickle === true) {
1362
					// Send candidate
1363
					sendTrickleCandidate(handleId, candidate);
1364
				}
1365
			}
1366
		};
1367
		if(stream !== null && stream !== undefined) {
1368
			Janus.log('Adding local stream');
1369
			stream.getTracks().forEach(function(track) { config.pc.addTrack(track, stream); });
1370
			pluginHandle.onlocalstream(stream);
1371
		}
1372
		config.pc.ontrack = function(event) {
1373
			Janus.log("Handling Remote Track");
1374
			Janus.debug(event);
1375
			if(!event.streams)
1376
				return;
1377
			config.remoteStream = event.streams[0];
1378
			pluginHandle.onremotestream(config.remoteStream);
1379
		};
1380
		// Any data channel to create?
1381
		if(isDataEnabled(media)) {
1382
			Janus.log("Creating data channel");
1383
			var onDataChannelMessage = function(event) {
1384
				Janus.log('Received message on data channel: ' + event.data);
1385
				pluginHandle.ondata(event.data);	// FIXME
1386
			}
1387
			var onDataChannelStateChange = function() {
1388
				var dcState = config.dataChannel !== null ? config.dataChannel.readyState : "null";
1389
				Janus.log('State change on data channel: ' + dcState);
1390
				if(dcState === 'open') {
1391
					pluginHandle.ondataopen();	// FIXME
1392
				}
1393
			}
1394
			var onDataChannelError = function(error) {
1395
				Janus.error('Got error on data channel:', error);
1396
				// TODO
1397
			}
1398
			// Until we implement the proxying of open requests within the Janus core, we open a channel ourselves whatever the case
1399
			config.dataChannel = config.pc.createDataChannel("JanusDataChannel", {ordered:false});	// FIXME Add options (ordered, maxRetransmits, etc.)
1400
			config.dataChannel.onmessage = onDataChannelMessage;
1401
			config.dataChannel.onopen = onDataChannelStateChange;
1402
			config.dataChannel.onclose = onDataChannelStateChange;
1403
			config.dataChannel.onerror = onDataChannelError;
1404
		}
1405
		// Create offer/answer now
1406
		if(jsep === null || jsep === undefined) {
1407
			createOffer(handleId, media, callbacks);
1408
		} else {
1409
			config.pc.setRemoteDescription(
1410
					new RTCSessionDescription(jsep),
1411
					function() {
1412
						Janus.log("Remote description accepted!");
1413
						createAnswer(handleId, media, callbacks);
1414
					}, callbacks.error);
1415
		}
1416
	}
1417

  
1418
	function prepareWebrtc(handleId, callbacks) {
1419
		callbacks = callbacks || {};
1420
		callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
1421
		callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
1422
		var jsep = callbacks.jsep;
1423
		var media = callbacks.media;
1424
		var pluginHandle = pluginHandles[handleId];
1425
		if(pluginHandle === null || pluginHandle === undefined ||
1426
				pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
1427
			Janus.warn("Invalid handle");
1428
			callbacks.error("Invalid handle");
1429
			return;
1430
		}
1431
		var config = pluginHandle.webrtcStuff;
1432
		// Are we updating a session?
1433
		if(config.pc !== undefined && config.pc !== null) {
1434
			Janus.log("Updating existing media session");
1435
			// Create offer/answer now
1436
			if(jsep === null || jsep === undefined) {
1437
				createOffer(handleId, media, callbacks);
1438
			} else {
1439
				config.pc.setRemoteDescription(
1440
						new RTCSessionDescription(jsep),
1441
						function() {
1442
							Janus.log("Remote description accepted!");
1443
							createAnswer(handleId, media, callbacks);
1444
						}, callbacks.error);
1445
			}
1446
			return;
1447
		}
1448
		config.trickle = isTrickleEnabled(callbacks.trickle);
1449
		// Was a MediaStream object passed, or do we need to take care of that?
1450
		if(callbacks.stream !== null && callbacks.stream !== undefined) {
1451
			var stream = callbacks.stream;
1452
			Janus.log("MediaStream provided by the application");
1453
			Janus.debug(stream);
1454
			// Skip the getUserMedia part
1455
			config.streamExternal = true;
1456
			streamsDone(handleId, jsep, media, callbacks, stream);
1457
			return;
1458
		}
1459
		if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) {
1460
			var constraints = { mandatory: {}, optional: []};
1461
			pluginHandle.consentDialog(true);
1462
			var audioSupport = isAudioSendEnabled(media);
1463
			if(audioSupport === true && media != undefined && media != null) {
1464
				if(typeof media.audio === 'object') {
1465
					audioSupport = media.audio;
1466
				}
1467
			}
1468
			var videoSupport = isVideoSendEnabled(media);
1469
			if(videoSupport === true && media != undefined && media != null) {
1470
				var simulcast = callbacks.simulcast === true ? true : false;
1471
				if(simulcast && !jsep && (media.video === undefined || media.video === false))
1472
					media.video = "hires";
1473
				if(media.video && media.video != 'screen' && media.video != 'window') {
1474
					var width = 0;
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff