From b2447967c6d7319e66e2a40c973c2beb0e8b1ffe Mon Sep 17 00:00:00 2001 From: Mike Jerris Date: Wed, 5 Sep 2018 20:22:47 +0000 Subject: [PATCH] FS-11287: [verto] update verto-min.js --- html5/verto/demo/js/verto-min.js | 10067 ++++++++++++++- .../video_demo-live_canvas/js/verto-min.js | 10136 +++++++++++++++- html5/verto/video_demo/js/verto-min.js | 10136 +++++++++++++++- 3 files changed, 28521 insertions(+), 1818 deletions(-) diff --git a/html5/verto/demo/js/verto-min.js b/html5/verto/demo/js/verto-min.js index 2f60ba200c..2ac8e89e7e 100644 --- a/html5/verto/demo/js/verto-min.js +++ b/html5/verto/demo/js/verto-min.js @@ -1,561 +1,9508 @@ +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.FSRTC.js - WebRTC Glue code + * + */ -(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{self.options.useVideo.removeAttribute("controls");});} -var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);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.stopPeer=function(){if(self.peer){console.log("stopping peer");self.peer.stop();}} -$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';self.options.useVideo['src']='';} -if(self.localStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}} -self.localStream=null;} -if(self.options.localVideo){self.options.localVideo.style.display='none';self.options.localVideo['src']='';} -if(self.options.localVideoStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}} -if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.getMute=function(){var self=this;return self.audioEnabled;} -$.FSRTC.prototype.setMute=function(what){var self=this;var audioTracks=self.localStream.getAudioTracks();for(var i=0,len=audioTracks.length;i-1){default_ice={urls:['turn:turn-testdrive.cloudapp.net:3478?transport=udp'],username:"redmond",credential:"redmond123"};}else{default_ice={urls:['stun:stun.l.google.com:19302']}} -if(options.iceServers){if(typeof(options.iceServers)==="boolean"){config.iceServers=[default_ice];}else{config.iceServers=options.iceServers;}} -var peer=new window.RTCPeerConnection(config);openOffererChannel();var x=0;function ice_handler(){done=true;gathering=null;if(options.onICEComplete){options.onICEComplete();} -if(options.type=="offer"){options.onICESDP(peer.localDescription);}else{if(!x&&options.onICESDP){options.onICESDP(peer.localDescription);}}} -peer.onicecandidate=function(event){if(done){return;} -if(!gathering){gathering=setTimeout(ice_handler,1000);} -if(event){if(event.candidate){options.onICE(event.candidate);}}else{done=true;if(gathering){clearTimeout(gathering);gathering=null;} -ice_handler();}};if(options.attachStream)peer.addStream(options.attachStream);if(options.attachStreams&&options.attachStream.length){var streams=options.attachStreams;for(var i=0;i=w&&$.FSRTC.validRes[i][1]>=h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}} -return[w,h];} -var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} -var video={} -if(cam){video.deviceId={exact:cam};} -w=resList[resI][0];h=resList[resI][1];resI++;video={width:{exact:w},height:{exact:h}};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.warn(w+"x"+h+" not supported.");checkRes(cam,func);}});} -$.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");} -return func?func(cache):null;} -$.FSRTC.validRes=[];resI=0;checkRes(cam,func);} -$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);} -console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i1){return false;} -return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);} -if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;} -self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;} -if(self.options.onWSClose){self.options.onWSClose(self);} -console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);} -self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);} -var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}} -return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to) -clearTimeout(self.to);} -$.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;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);this.speedCB(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});this.speedCB=null;} -return;} -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,loginParams:self.options.loginParams,userVariables:self.options.userVariables},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 i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i0){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);dialog.setState($.verto.enum.state.recovering);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.punt':verto.purge();verto.logout();break;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&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else 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.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);console.debug("CLIENT READY",data.params);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=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i":"\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;} -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.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",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);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;} -return data;} -function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);} -return data;} -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;}} -if(config.onChange){config.onChange(obj,args);} -try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;} -dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;} -if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));} -dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;} -dt.fnUpdate(genRow(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(genArray(obj));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;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);} -if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);} -if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';} -if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};} -$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="

"+""+""+""+""+ -(confMan.params.hasVid?"":"")+"

";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j
"+"Video Layout Canvas "+(j+1)+" "+"

";jq.append(vlhtml);} -$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});} -$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});} -function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="
";html+="General Controls
";html+=""+""+""+""+""+""+"";if(confMan.params.hasVid){html+="

Video Controls
";html+=""+""+""+"";if(confMan.canvasCount>1){html+="

Canvas Controls
"+""+""+""+"
"+""+""+"";} -html+="
"+""+""+""+"
";} -jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();} -jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});} -$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;} -var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready

");}else{$(confMan.params.mainModID).html("");} -verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);} -if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;jb)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}} -if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"

");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;} -confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready

");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}} -var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};} -confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();} -if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);} -if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);} -if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;} -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;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useMic=dialog.params.useMic;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();} -if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();} -if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}} -if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);} -dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_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){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} -if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{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.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);} -console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();} -console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});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;} -if((obj.noDialogParams&&i!="callID")){continue;} -obj.dialogParams[i]=dialog.params[i];} -delete obj.noDialogParams;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;} -function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}} -return id;} -$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;} -if(callback){callback(false,null,arg);} -console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}} -$.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.log("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.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);} -break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;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:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();} -delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();} -break;} -return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;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(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;} -if(params.cause){dialog.cause=params.cause;}} -if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";} -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,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.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.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;} -pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;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){params={};} -params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);} -dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;} -if(params.useMic){dialog.useMic=params.useMic;} -if(params.useSpeak){dialog.useSpeak=params.useSpeak;}} -dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;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{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" <":" <")+dialog.params.remote_caller_id_number+(enc?">":">");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);};$.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(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;} -dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.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,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,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 recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();} -if($.verto.haltClosure) -return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}} -return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}} -$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});} -if(runtime){runtime(true);}} -function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);} -function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}} -navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});} -navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.verto.refreshDevices=function(runtime){checkDevices(runtime);} -$.verto.init=function(obj,runtime){if(!obj){obj={};} -if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}} -$.verto.genUUID=function(){return generateGUID();}})(jQuery);(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');} -var candidate={foundation:parts[0],component:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+ -(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+headerExtension.uri+'\r\n';};SDPUtils.parseFmtp=function(line){var parsed={};var kv;var parts=line.substr(line.indexOf(' ')+1).split(';');for(var j=0;j-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);} -return parts;};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}} -SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;} -return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';} -sdp+='a=rtcp-mux\r\n';caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];} -description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});} -var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10)*1000*0.95 --(50*40*8);}else{bandwidth=undefined;} -encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});} -return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;} -var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};} -var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId){var sessionId;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();} -return'v=0\r\n'+'o=thisisadapterortc '+sessionId+' 2 IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.direction){sdp+='a='+transceiver.direction+'\r\n';}else if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';} -if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+ -transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+ -transceiver.sendEncodingParameters[0].ssrc+' '+ -transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}} -sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';} -return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i0&&typeof selector==='function'){return origGetStats.apply(this,arguments);} -if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);} -var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);} -return new Promise(function(resolve,reject){origGetStats.apply(self,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var args=arguments;var self=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(self,[args[0],resolve,reject]);});if(args.length<2){return promise;} -return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});} -if(browserDetails.version<52){['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var self=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(self,[resolve,reject,opts]);});} -return nativeMethod.apply(this,arguments);};});} -['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);} -return Promise.resolve();} -return nativeAddIceCandidate.apply(this,arguments);};}};module.exports={shimMediaStream:chromeShim.shimMediaStream,shimOnTrack:chromeShim.shimOnTrack,shimGetSendersWithDtmf:chromeShim.shimGetSendersWithDtmf,shimSourceObject:chromeShim.shimSourceObject,shimPeerConnection:chromeShim.shimPeerConnection,shimGetUserMedia:require('./getusermedia')};},{"../utils.js":12,"./getusermedia":5}],5:[function(require,module,exports){'use strict';var utils=require('../utils.js');var logging=utils.log;module.exports=function(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;} -var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;} -var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;} -var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);} -return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}} -if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);} -return cc;};var shimConstraints_=function(constraints,func){constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&typeof constraints.audio==='object'){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);} -if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<61;if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];} -if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().indexOf(match)!==-1;});});if(!dev&&devices.length&&matches.indexOf('back')!==-1){dev=devices[devices.length-1];} -if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.deviceId};} -constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}} -constraints.video=constraintsToChrome_(constraints.video);} -logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',InvalidStateError:'NotReadableError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotReadableError',MediaDeviceKillSwitchOn:'NotReadableError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){onError(shimError_(e));});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return window.MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});},getSupportedConstraints:function(){return{deviceId:true,echoCancellation:true,facingMode:true,frameRate:true,height:true,width:true};}};} -if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');} -return stream;},function(e){return Promise.reject(shimError_(e));});});};} -if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};} -if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":12}],6:[function(require,module,exports){'use strict';var utils=require('../utils');var shimRTCPeerConnection=require('./rtcpeerconnection_shim');module.exports={shimGetUserMedia:require('./getusermedia'),shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};} -if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};} -if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}} -window.RTCPeerConnection=shimRTCPeerConnection(window,browserDetails.version);},shimReplaceTrack:function(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}};},{"../utils":12,"./getusermedia":7,"./rtcpeerconnection_shim":8}],7:[function(require,module,exports){'use strict';module.exports=function(window){var navigator=window&&window.navigator;var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],8:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');function sortTracks(tracks){var audioTracks=tracks.filter(function(track){return track.kind==='audio';});var videoTracks=tracks.filter(function(track){return track.kind==='video';});tracks=[];while(audioTracks.length||videoTracks.length){if(audioTracks.length){tracks.push(audioTracks.shift());} -if(videoTracks.length){tracks.push(videoTracks.shift());}} -return tracks;} -function filterIceServers(iceServers,edgeVersion){var hasTurn=false;iceServers=JSON.parse(JSON.stringify(iceServers));return iceServers.filter(function(server){if(server&&(server.urls||server.url)){var urls=server.urls||server.url;if(server.url&&!server.urls){console.warn('RTCIceServer.url is deprecated! Use urls instead.');} -var isString=typeof urls==='string';if(isString){urls=[urls];} -urls=urls.filter(function(url){var validTurn=url.indexOf('turn:')===0&&url.indexOf('transport=udp')!==-1&&url.indexOf('turn:[')===-1&&!hasTurn;if(validTurn){hasTurn=true;return true;} -return url.indexOf('stun:')===0&&edgeVersion>=14393;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;} -return false;});} -function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;} -this.transceivers.push(transceiver);return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){var transceiver;for(var i=0;i=15025){this.localStreams.push(stream);stream.getTracks().forEach(function(track){self.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){self.addTrack(track,clonedStream);});this.localStreams.push(clonedStream);} -this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var idx=this.localStreams.indexOf(stream);if(idx>-1){this.localStreams.splice(idx,1);this._maybeFireNegotiationNeeded();}};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(mid,sdpMLineIndex){var self=this;var iceGatherer=new window.RTCIceGatherer(self.iceOptions);iceGatherer.onlocalcandidate=function(evt){var event=new Event('icecandidate');event.candidate={sdpMid:mid,sdpMLineIndex:sdpMLineIndex};var cand=evt.candidate;var end=!cand||Object.keys(cand).length===0;if(end){if(iceGatherer.state===undefined){iceGatherer.state='completed';}}else{cand.component=1;event.candidate.candidate=SDPUtils.writeCandidate(cand);} -var sections=SDPUtils.splitSections(self.localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex+1]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex+1]+='a=end-of-candidates\r\n';} -self.localDescription.sdp=sections.join('');var transceivers=self._pendingOffer?self._pendingOffer:self.transceivers;var complete=transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});switch(self.iceGatheringState){case'new':if(!end){self._localIceCandidatesBuffer.push(event);} -if(end&&complete){self._localIceCandidatesBuffer.push(new Event('icecandidate'));} -break;case'gathering':self._emitBufferedCandidates();if(!end){self.dispatchEvent(event);if(self.onicecandidate!==null){self.onicecandidate(event);}} -if(complete){self.dispatchEvent(new Event('icecandidate'));if(self.onicecandidate!==null){self.onicecandidate(new Event('icecandidate'));} -self.iceGatheringState='complete';self._emitGatheringStateChange();} -break;case'complete':break;default:break;}};return iceGatherer;};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var self=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){self._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){self._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});self._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;} -var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;} -var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlssttatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;} -transceiver.rtpSender.send(params);} -if(recv&&transceiver.rtpReceiver){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});} -params.encodings=transceiver.recvEncodingParameters;params.rtcp={cname:transceiver.rtcpParameters.cname,compound:transceiver.rtcpParameters.compound};if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;} -transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var self=this;if(!isActionAllowedInSignalingState('setLocalDescription',description.type,this.signalingState)){var e=new Error('Can not set local '+description.type+' in state '+this.signalingState);e.name='InvalidStateError';if(arguments.length>2&&typeof arguments[2]==='function'){window.setTimeout(arguments[2],0,e);} -return Promise.reject(e);} -var sections;var sessionpart;if(description.type==='offer'){if(this._pendingOffer){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);self._pendingOffer[sdpMLineIndex].localCapabilities=caps;});this.transceivers=this._pendingOffer;delete this._pendingOffer;}}else if(description.type==='answer'){sections=SDPUtils.splitSections(self.remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=self.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection);if(!rejected&&!transceiver.isDatachannel){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';} -if(!self.usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');dtlsTransport.start(remoteDtlsParameters);} -var params=getCommonCapabilities(localCapabilities,remoteCapabilities);self._transceive(transceiver,params.codecs.length>0,false);}});} -this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -var hasCallback=arguments.length>1&&typeof arguments[1]==='function';if(hasCallback){var cb=arguments[1];window.setTimeout(function(){cb();if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';self._emitGatheringStateChange();} -self._emitBufferedCandidates();},0);} -var p=Promise.resolve();p.then(function(){if(!hasCallback){if(self.iceGatheringState==='new'){self.iceGatheringState='gathering';self._emitGatheringStateChange();} -window.setTimeout(self._emitBufferedCandidates.bind(self),500);}});return p;};RTCPeerConnection.prototype.setRemoteDescription=function(description){var self=this;if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,this.signalingState)){var e=new Error('Can not set remote '+description.type+' in state '+this.signalingState);e.name='InvalidStateError';if(arguments.length>2&&typeof arguments[2]==='function'){window.setTimeout(arguments[2],0,e);} -return Promise.reject(e);} -var streams={};var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;this.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){this.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{this.canTrickleIceCandidates=false;} -sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection);var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(kind==='application'&&protocol==='DTLS/SCTP'){self.transceivers[sdpMLineIndex]={mid:mid,isDatachannel:true};return;} -var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';} -recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component==='1'||cand.component===1;});if((description.type==='offer'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&self.transceivers[sdpMLineIndex]){self._disposeIceAndDtlsTransports(sdpMLineIndex);self.transceivers[sdpMLineIndex].iceGatherer=self.transceivers[0].iceGatherer;self.transceivers[sdpMLineIndex].iceTransport=self.transceivers[0].iceTransport;self.transceivers[sdpMLineIndex].dtlsTransport=self.transceivers[0].dtlsTransport;if(self.transceivers[sdpMLineIndex].rtpSender){self.transceivers[sdpMLineIndex].rtpSender.setTransport(self.transceivers[0].dtlsTransport);} -if(self.transceivers[sdpMLineIndex].rtpReceiver){self.transceivers[sdpMLineIndex].rtpReceiver.setTransport(self.transceivers[0].dtlsTransport);}} -if(description.type==='offer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex]||self._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=usingBundle&&sdpMLineIndex>0?self.transceivers[0].iceGatherer:self._createIceGatherer(mid,sdpMLineIndex);} -if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);} -localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+2)*1001}];if(direction==='sendrecv'||direction==='sendonly'){rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});} -Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});streams[remoteMsid.stream].addTrack(track);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();} -streams.default.addTrack(track);receiverList.push([track,rtpReceiver,streams.default]);}} -transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;self._transceive(self.transceivers[sdpMLineIndex],false,direction==='sendrecv'||direction==='sendonly');}else if(description.type==='answer'&&!rejected){transceiver=self.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;self.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;self.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;self.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if((isIceLite||isComplete)&&cands.length){iceTransport.setRemoteCandidates(cands);} -if(!usingBundle||sdpMLineIndex===0){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');dtlsTransport.start(remoteDtlsParameters);} -self._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();} -streams[remoteMsid.stream].addTrack(track);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();} -streams.default.addTrack(track);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){self.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;self.dispatchEvent(event);if(self.onaddstream!==null){window.setTimeout(function(){self.onaddstream(event);},0);} -receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;} -var trackEvent=new Event('track');trackEvent.track=track;trackEvent.receiver=receiver;trackEvent.streams=[stream];self.dispatchEvent(trackEvent);if(self.ontrack!==null){window.setTimeout(function(){self.ontrack(trackEvent);},0);}});}});window.setTimeout(function(){if(!(self&&self.transceivers)){return;} -self.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} -return Promise.resolve();};RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();} -if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();} -if(transceiver.rtpSender){transceiver.rtpSender.stop();} -if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this.dispatchEvent(event);if(this.onsignalingstatechange!==null){this.onsignalingstatechange(event);}};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var self=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;} -this.needNegotiation=true;window.setTimeout(function(){if(self.needNegotiation===false){return;} -self.needNegotiation=false;var event=new Event('negotiationneeded');self.dispatchEvent(event);if(self.onnegotiationneeded!==null){self.onnegotiationneeded(event);}},0);};RTCPeerConnection.prototype._updateConnectionState=function(){var self=this;var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';} -if(newState!==self.iceConnectionState){self.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this.dispatchEvent(event);if(this.oniceconnectionstatechange!==null){this.oniceconnectionstatechange(event);}}};RTCPeerConnection.prototype.createOffer=function(){var self=this;if(this._pendingOffer){throw new Error('createOffer called while there is a pending offer.');} -var offerOptions;if(arguments.length===1&&typeof arguments[0]!=='function'){offerOptions=arguments[0];}else if(arguments.length===3){offerOptions=arguments[2];} -var numAudioTracks=this.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=this.transceivers.filter(function(t){return t.kind==='video';}).length;if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');} -if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}} -if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}} -this.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){this._createTransceiver('audio');numAudioTracks--;} -if(numVideoTracks>0){this._createTransceiver('video');numVideoTracks--;}} -var transceivers=sortTracks(this.transceivers);var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId);transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=self.usingBundle&&sdpMLineIndex>0?transceivers[0].iceGatherer:self._createIceGatherer(mid,sdpMLineIndex);} -var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}});var sendEncodingParameters=[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'){sendEncodingParameters[0].rtx={ssrc:(2*sdpMLineIndex+1)*1001+1};}} -if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);} -transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(this._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -sdp+='a=ice-options:trickle\r\n';transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=SDPUtils.writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream);sdp+='a=rtcp-rsize\r\n';});this._pendingOffer=transceivers;var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} -return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId);if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -this.transceivers.forEach(function(transceiver,sdpMLineIndex){if(transceiver.isDatachannel){sdp+='m=application 0 DTLS/SCTP 5000\r\n'+'c=IN IP4 0.0.0.0\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;} -if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];} -if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'){transceiver.sendEncodingParameters[0].rtx={ssrc:(2*sdpMLineIndex+2)*1001+1};}}} -var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;} -sdp+=SDPUtils.writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});if(arguments.length&&typeof arguments[0]==='function'){window.setTimeout(arguments[0],0,desc);} -return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){if(!candidate){for(var j=0;j0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return Promise.resolve();} -if(cand.component&&!(cand.component==='1'||cand.component===1)){return Promise.resolve();} -transceiver.iceTransport.addRemoteCandidate(cand);var sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[mLineIndex+1]+=(cand.type?candidate.candidate.trim():'a=end-of-candidates')+'\r\n';this.remoteDescription.sdp=sections.join('');}} -if(arguments.length>1&&typeof arguments[1]==='function'){window.setTimeout(arguments[1],0);} -return Promise.resolve();};RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var cb=arguments.length>1&&typeof arguments[1]==='function'&&arguments[1];var fixStatsType=function(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;};return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){result[id].type=fixStatsType(result[id]);results.set(id,result[id]);});});if(cb){window.setTimeout(cb,0,results);} -resolve(results);});});};return RTCPeerConnection;};},{"sdp":1}],9:[function(require,module,exports){'use strict';var utils=require('../utils');var firefoxShim={shimOnTrack:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in -window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);} -this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});}},shimSourceObject:function(window){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;} -if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if(typeof c==='object'&&typeof c.audio==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');} -return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};} -if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&typeof c==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');} -return nativeApplyConstraints.apply(this,[c]);};}} -navigator.getUserMedia=function(constraints,onSuccess,onError){if(browserDetails.version<44){return getUserMedia_(constraints,onSuccess,onError);} -console.warn('navigator.getUserMedia has been replaced by '+'navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};};},{"../utils":12}],11:[function(require,module,exports){'use strict';var utils=require('../utils');var safariShim={shimLocalStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];} -return this._localStreams;};} -if(!('getStreamById'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getStreamById=function(id){var result=null;if(this._localStreams){this._localStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -if(this._remoteStreams){this._remoteStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -return result;};} -if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){if(!this._localStreams){this._localStreams=[];} -if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);} -var self=this;stream.getTracks().forEach(function(track){_addTrack.call(self,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);}} -_addTrack.call(this,track,stream);};} -if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){if(!this._localStreams){this._localStreams=[];} -var index=this._localStreams.indexOf(stream);if(index===-1){return;} -this._localStreams.splice(index,1);var self=this;var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.indexOf(sender.track)!==-1){self.removeTrack(sender);}});};}},shimRemoteStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};} -if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function(){return this._onaddstream;},set:function(f){if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);} -this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){var stream=e.streams[0];if(!this._remoteStreams){this._remoteStreams=[];} -if(this._remoteStreams.indexOf(stream)>=0){return;} -this._remoteStreams.push(stream);var event=new Event('addstream');event.stream=e.streams[0];this.dispatchEvent(event);}.bind(this));}});}},shimCallbacksAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;},shimGetUserMedia:function(window){var navigator=window&&window.navigator;if(!navigator.getUserMedia){if(navigator.webkitGetUserMedia){navigator.getUserMedia=navigator.webkitGetUserMedia.bind(navigator);}else if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}},shimRTCIceServerUrls:function(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i=pos&&parseInt(match[pos],10);},detectBrowser:function(window){var navigator=window&&window.navigator;var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;} -if(navigator.mozGetUserMedia){result.browser='firefox';result.version=this.extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){if(window.webkitRTCPeerConnection){result.browser='chrome';result.version=this.extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else{if(navigator.userAgent.match(/Version\/(\d+).(\d+)/)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Unsupported webkit-based browser '+'with GUM support but no WebRTC support.';return result;}}}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=this.extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=this.extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;} -return result;},shimCreateObjectURL:function(window){var URL=window&&window.URL;if(!(typeof window==='object'&&window.HTMLMediaElement&&'srcObject'in window.HTMLMediaElement.prototype)){return undefined;} -var nativeCreateObjectURL=URL.createObjectURL.bind(URL);var nativeRevokeObjectURL=URL.revokeObjectURL.bind(URL);var streams=new Map(),newId=0;URL.createObjectURL=function(stream){if('getTracks'in stream){var url='polyblob:'+(++newId);streams.set(url,stream);utils.deprecated('URL.createObjectURL(stream)','elem.srcObject = stream');return url;} -return nativeCreateObjectURL(stream);};URL.revokeObjectURL=function(url){nativeRevokeObjectURL(url);streams.delete(url);};var dsc=Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,'src');Object.defineProperty(window.HTMLMediaElement.prototype,'src',{get:function(){return dsc.get.apply(this);},set:function(url){this.srcObject=streams.get(url)||null;return dsc.set.apply(this,[url]);}});var nativeSetAttribute=window.HTMLMediaElement.prototype.setAttribute;window.HTMLMediaElement.prototype.setAttribute=function(){if(arguments.length===2&&(''+arguments[0]).toLowerCase()==='src'){this.srcObject=streams.get(arguments[1])||null;} -return nativeSetAttribute.apply(this,arguments);};}};module.exports={log:utils.log,deprecated:utils.deprecated,disableLog:utils.disableLog,disableWarnings:utils.disableWarnings,extractVersion:utils.extractVersion,shimCreateObjectURL:utils.shimCreateObjectURL,detectBrowser:utils.detectBrowser.bind(utils)};},{}]},{},[2])(2)}); \ No newline at end of file +(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, + localVideo: null, + screenShare: false, + useCamera: "any", + iceServers: false, + videoParams: {}, + audioParams: {}, + callbacks: { + onICEComplete: function() {}, + onICE: function() {}, + onOfferSDP: function() {} + }, + useStream: null, + }, options); + + this.audioEnabled = true; + this.videoEnabled = true; + + + this.mediaData = { + SDP: null, + profile: {}, + candidateList: [] + }; + + this.constraints = { + offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, + offerToReceiveVideo: this.options.useVideo ? true : false, + }; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + } + + setCompat(); + checkCompat(); + }; + + $.FSRTC.validRes = []; + + $.FSRTC.prototype.useVideo = function(obj, local) { + var self = this; + + if (obj) { + self.options.useVideo = obj; + self.options.localVideo = local; + self.constraints.offerToReceiveVideo = true; + } else { + self.options.useVideo = null; + self.options.localVideo = null; + self.constraints.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) { + return sdp; + } else { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + // Find the payload in fmtp line. + var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + + if (fmtpLineIndex === null) { + // create an fmtp line + sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1" + } else { + // Append stereo=1 to fmtp line. + sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); + } + + sdp = sdpLines.join('\r\n'); + return sdp; + }; + + function setCompat() { + } + + function checkCompat() { + return true; + } + + function onStreamError(self, e) { + console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution', e); + doCallback(self, "onError", e); + } + + function onStreamSuccess(self, stream) { + console.log("Stream Success"); + doCallback(self, "onStream", stream); + } + + function onRemoteStreamSuccess(self, stream) { + console.log("Remote Stream Success"); + doCallback(self, "onRemoteStream", stream); + } + + 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); + } + + FSRTCattachMediaStream = function(element, stream) { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else { + console.error('Error attaching stream to element.'); + } + } + + function onRemoteStream(self, stream) { + if (self.options.useVideo) { + self.options.useVideo.style.display = 'block'; + + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + + if (iOS) { + self.options.useVideo.setAttribute("playsinline", true); + } + } + + var element = self.options.useAudio; + console.log("REMOTE STREAM", stream, element); + + FSRTCattachMediaStream(element, stream); + + + + //self.options.useAudio.play(); + self.remoteStream = stream; + onRemoteStreamSuccess(self, 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.stopPeer = function() { + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + } + + $.FSRTC.prototype.stop = function() { + var self = this; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + self.options.useVideo['src'] = ''; + } + + if (self.localStream && !self.options.useStream) { + if(typeof self.localStream.stop == 'function') { + self.localStream.stop(); + } else { + if (self.localStream.active){ + var tracks = self.localStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + self.localStream = null; + } + + if (self.options.localVideo) { + deactivateLocalVideo(self.options.localVideo); + } + + if (self.options.localVideoStream && !self.options.useStream) { + if(typeof self.options.localVideoStream.stop == 'function') { + self.options.localVideoStream.stop(); + } else { + if (self.options.localVideoStream.active){ + var tracks = self.options.localVideoStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + } + + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + }; + + $.FSRTC.prototype.getMute = function() { + var self = this; + return self.audioEnabled; + } + + $.FSRTC.prototype.setMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var audioTracks = self.localStream.getAudioTracks(); + + for (var i = 0, len = audioTracks.length; i < len; i++ ) { + switch(what) { + case "on": + audioTracks[i].enabled = true; + break; + case "off": + audioTracks[i].enabled = false; + break; + case "toggle": + audioTracks[i].enabled = !audioTracks[i].enabled; + default: + break; + } + + self.audioEnabled = audioTracks[i].enabled; + } + + return !self.audioEnabled; + } + + $.FSRTC.prototype.getVideoMute = function() { + var self = this; + return self.videoEnabled; + } + + $.FSRTC.prototype.setVideoMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var videoTracks = self.localStream.getVideoTracks(); + + for (var i = 0, len = videoTracks.length; i < len; i++ ) { + switch(what) { + case "on": + videoTracks[i].enabled = true; + break; + case "off": + videoTracks[i].enabled = false; + break; + case "toggle": + videoTracks[i].enabled = !videoTracks[i].enabled; + default: + break; + } + + self.videoEnabled = videoTracks[i].enabled; + } + + return !self.videoEnabled; + } + + $.FSRTC.prototype.createAnswer = function(params) { + var self = this; + self.type = "answer"; + self.remoteSDP = params.sdp; + console.debug("inbound sdp: ", params.sdp); + + function onSuccess(stream) { + self.localStream = stream; + + self.peer = FSRTCPeerConnection({ + 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, + iceServers: self.options.iceServers, + offerSDP: { + type: "offer", + sdp: self.remoteSDP + }, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useVideo && self.options.localVideo && !self.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: params.useCamera }, + }, + localVideo: self.options.localVideo, + onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else { + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + } + + }; + + function getMediaParams(obj) { + + var audio; + + if (obj.options.useMic && obj.options.useMic === "none") { + console.log("Microphone Disabled"); + audio = false; + } else if (obj.options.videoParams && obj.options.screenShare) {//obj.options.videoParams.chromeMediaSource == 'desktop') { + console.error("SCREEN SHARE", obj.options.videoParams); + audio = false; + } else { + audio = { + }; + + if (obj.options.audioParams) { + audio = obj.options.audioParams; + } + + if (obj.options.useMic !== "any") { + //audio.optional = [{sourceId: obj.options.useMic}]; + audio.deviceId = {exact: obj.options.useMic}; + } + } + + if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: obj.options.useCamera }, + }, + localVideo: obj.options.localVideo, + onsuccess: function(e) {obj.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + var video = {}; + var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; + var minFrameRate = obj.options.videoParams.minFrameRate || 15; + delete obj.options.videoParams.vertoBestFrameRate; + + if (obj.options.screenShare) { + if (!obj.options.useCamera && !!navigator.mozGetUserMedia) { + //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way + var dowin = window.confirm("Do you want to share an application window? If not you can share an entire screen."); + + video = { + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}, + mediaSource: dowin ? "window" : "screen" + } + } else { + var opt = []; + if (obj.options.useCamera) { + opt.push({sourceId: obj.options.useCamera}); + } + + if (bestFrameRate) { + opt.push({minFrameRate: bestFrameRate}); + opt.push({maxFrameRate: bestFrameRate}); + } + + video = { + mandatory: obj.options.videoParams, + optional: opt + }; + // NOTE: This is a workaround for + // https://bugs.chromium.org/p/chromium/issues/detail?id=862325 + if (!!navigator.userAgent.match(/Android/i)) { + delete video.frameRate.min; + } + } + } else { + + video = { + //mandatory: obj.options.videoParams, + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight} + }; + + + + var useVideo = obj.options.useVideo; + + if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { + //if (!video.optional) { + //video.optional = []; + //} + + + if (obj.options.useCamera !== "any") { + //video.optional.push({sourceId: obj.options.useCamera}); + video.deviceId = { + exact: obj.options.useCamera, + }; + } + + if (bestFrameRate) { + //video.optional.push({minFrameRate: bestFrameRate}); + //video.optional.push({maxFrameRate: bestFrameRate}); + video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30}; + } + + } else { + console.log("Camera Disabled"); + video = false; + useVideo = false; + } + } + + return {audio: audio, video: video, useVideo: useVideo}; + } + + $.FSRTC.prototype.call = function(profile) { + checkCompat(); + + var self = this; + var screen = false; + + self.type = "offer"; + + if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') { + screen = true; + } + + function onSuccess(stream) { + self.localStream = stream; + + if (screen) { + self.constraints.offerToReceiveVideo = false; + self.constraints.offerToReceiveAudio = false; + self.constraints.offerToSendAudio = false; + } + + self.peer = FSRTCPeerConnection({ + type: self.type, + attachStream: self.localStream, + onICE: function(candidate) { + return onICE(self, candidate); + }, + onICEComplete: function() { + return onICEComplete(self); + }, + onRemoteStream: screen ? function(stream) {} : 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, + iceServers: self.options.iceServers, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else if (mediaParams.audio || mediaParams.video) { + + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + + } else { + onSuccess(null); + } + + + + /* + navigator.getUserMedia({ + video: self.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 + + function FSRTCPeerConnection(options) { + var gathering = false, done = false; + var config = {}; + var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }]; + + if (self.options.turnServer) { + default_ice.push(self.options.turnServer) + } + + if (options.iceServers) { + if (typeof(options.iceServers) === "boolean") { + config.iceServers = default_ice; + } else { + config.iceServers = options.iceServers; + } + } + + config.bundlePolicy = "max-compat"; + + var peer = new window.RTCPeerConnection(config); + + openOffererChannel(); + var x = 0; + + function ice_handler() { + + done = true; + gathering = null; + + if (options.onICEComplete) { + options.onICEComplete(); + } + + if (options.type == "offer") { + options.onICESDP(peer.localDescription); + } else { + if (!x && options.onICESDP) { + options.onICESDP(peer.localDescription); + } + } + } + + peer.onicecandidate = function(event) { + + if (done) { + return; + } + + if (!gathering) { + gathering = setTimeout(ice_handler, 1000); + } + + if (event) { + if (event.candidate) { + options.onICE(event.candidate); + } + } else { + done = true; + + if (gathering) { + clearTimeout(gathering); + gathering = null; + } + + ice_handler(); + } + }; + + // 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.oninactive = 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 || { + // 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); + }, + onSdpError, options.constraints); + } + + // onAnswerSDP(RTCSessionDescription) + function createAnswer() { + if (options.type != "answer") return; + + //options.offerSDP.sdp = addStereo(options.offerSDP.sdp); + peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP), onSdpSuccess, onSdpError); + peer.createAnswer(function(sessionDescription) { + sessionDescription.sdp = serializeSdp(sessionDescription.sdp); + peer.setLocalDescription(sessionDescription); + if (options.onAnswerSDP) { + options.onAnswerSDP(sessionDescription); + } + }, + onSdpError); + } + + + if ((options.onChannelMessage) || !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) { + return sdp; + } + + // DataChannel management + var channel; + + function openOffererChannel() { + if (!options.onChannelMessage) return; + + _openOffererChannel(); + + return; + } + + function _openOffererChannel() { + channel = peer.createDataChannel(options.channel || 'RTCDataChannel', { + reliable: false + }); + + 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); + }; + } + + function openAnswererChannel() { + peer.ondatachannel = function(event) { + channel = event.channel; + channel.binaryType = 'blob'; + setChannelEvents(); + }; + + return; + } + + // 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 window.RTCSessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); + }, + addICE: function(candidate) { + peer.addIceCandidate(new window.RTCIceCandidate({ + 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) { + if(typeof options.attachStream.stop == 'function') { + options.attachStream.stop(); + } else { + options.attachStream.active = false; + } + } + } + + }; + } + + // getUserMedia + var video_constraints = { + //mandatory: {}, + //optional: [] + }; + + function activateLocalVideo(el, stream) { + el.srcObject = stream; + el.style.display = 'block'; + } + + function deactivateLocalVideo(el) { + el.srcObject = null; + el.style.display = 'none'; + } + + function getUserMedia(options) { + var n = navigator, + media; + n.getMedia = n.getUserMedia; + n.getMedia(options.constraints || { + audio: true, + video: video_constraints + }, + streaming, options.onerror || + function(e) { + console.error(e); + }); + + function streaming(stream) { + if (options.localVideo) { + activateLocalVideo(options.localVideo, stream); + } + + if (options.onsuccess) { + options.onsuccess(stream); + } + + media = stream; + } + + return media; + } + + $.FSRTC.resSupported = function(w, h) { + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) { + return true; + } + } + + return false; + } + + $.FSRTC.bestResSupported = function() { + var w = 0, h = 0; + + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] >= w && $.FSRTC.validRes[i][1] >= h) { + w = $.FSRTC.validRes[i][0]; + h = $.FSRTC.validRes[i][1]; + } + } + + return [w, h]; + } + + var resList = [[160, 120], [320, 180], [320, 240], [640, 360], [640, 480], [1280, 720], [1920, 1080]]; + var resI = 0; + var ttl = 0; + + var checkRes = function (cam, func) { + + if (resI >= resList.length) { + var res = { + 'validRes': $.FSRTC.validRes, + 'bestResSupported': $.FSRTC.bestResSupported() + }; + + localStorage.setItem("res_" + cam, $.toJSON(res)); + + if (func) return func(res); + return; + } + + w = resList[resI][0]; + h = resList[resI][1]; + resI++; + + var video = { + width: {exact: w}, + height: {exact: h} + }; + + if (cam !== "any") { + video.deviceId = { + exact: cam, + }; + } + + getUserMedia({ + constraints: { + audio: ttl++ == 0, + video: video + }, + onsuccess: function(e) { + e.getTracks().forEach(function(track) {track.stop();}); + console.info(w + "x" + h + " supported."); $.FSRTC.validRes.push([w, h]); checkRes(cam, func);}, + onerror: function(e) {console.warn( w + "x" + h + " not supported."); checkRes(cam, func);} + }); + } + + + $.FSRTC.getValidRes = function (cam, func) { + var used = []; + var cached = localStorage.getItem("res_" + cam); + + if (cached) { + var cache = $.parseJSON(cached); + + if (cache) { + $.FSRTC.validRes = cache.validRes; + console.log("CACHED RES FOR CAM " + cam, cache); + } else { + console.error("INVALID CACHE"); + } + return func ? func(cache) : null; + } + + + $.FSRTC.validRes = []; + resI = 0; + + checkRes(cam, func); + } + + $.FSRTC.checkPerms = function (runtime, check_audio, check_video) { + getUserMedia({ + constraints: { + audio: check_audio, + video: check_video, + }, + onsuccess: function(e) { + + e.getTracks().forEach(function(track) {track.stop();}); + + console.info("media perm init complete"); + if (runtime) { + setTimeout(runtime, 100, true); + } + }, + onerror: function(e) { + if (check_video && check_audio) { + console.error("error, retesting with audio params only"); + return $.FSRTC.checkPerms(runtime, check_audio, false); + } + + console.error("media perm init error"); + + if (runtime) { + runtime(false) + } + } + }); + } + +})(jQuery); +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * + * 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, + loginParams : null, + userVariables : null, + getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); } + }, options); + + self.ws_cnt = 0; + + // 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 : { success_cb: cb, error_cb: cb } + $.JsonRpcClient.prototype._ws_callbacks = {}; + + /// The next JSON-RPC request id. + $.JsonRpcClient.prototype._current_id = 1; + + + $.JsonRpcClient.prototype.speedTest = function (bytes, cb) { + var socket = this.options.getSocket(this.wsOnMessage); + if (socket !== null) { + this.speedCB = cb; + this.speedBytes = bytes; + socket.send("#SPU " + bytes); + + var loops = bytes / 1024; + var rem = bytes % 1024; + var i; + var data = new Array(1024).join("."); + for (i = 0; i < loops; i++) { + socket.send("#SPB " + data); + } + + if (rem) { + socket.send("#SPB " + data); + } + + socket.send("#SPE"); + } + }; + + + + /** + * @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() { + var self = this; + if (self.socketReady()) { + self._ws_socket.onclose = function (w) {console.log("Closing Socket");}; + self._ws_socket.close(); + } + }; + + $.JsonRpcClient.prototype.loginData = function(params) { + var self = this; + self.options.login = params.login; + self.options.passwd = params.passwd; + self.options.loginParams = params.loginParams; + self.options.userVariables = params.userVariables; + }; + + $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) { + var self = this; + + if (self.to) { + clearTimeout(self.to); + } + + if (!self.socketReady()) { + self.authing = false; + + if (self._ws_socket) { + delete self._ws_socket; + } + + // No socket, or dying socket, let's get a new one. + self._ws_socket = new WebSocket(self.options.socketUrl); + + if (self._ws_socket) { + // Set up onmessage handler. + self._ws_socket.onmessage = onmessage_cb; + self._ws_socket.onclose = function (w) { + if (!self.ws_sleep) { + self.ws_sleep = 1000; + } + + if (self.options.onWSClose) { + self.options.onWSClose(self); + } + + if (self.ws_cnt > 10 && self.options.wsFallbackURL) { + self.options.socketUrl = self.options.wsFallbackURL; + } + + console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec"); + + self.to = setTimeout(function() { + console.log("Attempting Reconnection...."); + self.connectSocket(onmessage_cb); + }, self.ws_sleep); + + self.ws_cnt++; + + if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) { + self.ws_sleep += 1000; + } + }; + + // Set up sending of message for when the socket is open. + self._ws_socket.onopen = function() { + if (self.to) { + clearTimeout(self.to); + } + self.ws_sleep = 1000; + self.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 self._ws_socket ? true : false; + }; + + $.JsonRpcClient.prototype.stopRetrying = function() { + if (self.to) + clearTimeout(self.to); + } + + $.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; + + // Special sub proto + if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") { + if (event.data[3] == "U") { + this.up_dur = parseInt(event.data.substring(4)); + } else if (this.speedCB && event.data[3] == "D") { + this.down_dur = parseInt(event.data.substring(4)); + + var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0); + var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0); + + console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps); + var cb = this.speedCB; + this.speedCB = null; + cb(event, { + upDur: this.up_dur, + downDur: this.down_dur, + upKPS: up_kps, + downKPS: down_kps + }); + } + + return; + } + + + 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; + } else if ('error' in response && this._ws_callbacks[response.id]) { + // If this is an object with error, it is an error response. + + // 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, loginParams: self.options.loginParams, + userVariables: self.options.userVariables}, + 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 = {}; + var i = 0; + var call; + var success_cb; + var error_cb; + + // 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 (i = 0; i < this._requests.length; i++) { + call = this._requests[i]; + success_cb = ('success_cb' in call) ? call.success_cb : undefined; + 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 (i = 0; i < this._requests.length; i++) { + 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 + }; + } + } + + 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); + +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.verto.js - Main interface + * + */ + +(function($) { + var sources = []; + + var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ? + function() { + // If we have a cryptographically secure PRNG, use that + // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript + 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() { + // Otherwise, just use Math.random + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + 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); + }); + }; + + /// MASTER OBJ + $.verto = function(options, callbacks) { + var verto = this; + + $.verto.saved.push(verto); + + verto.options = $.extend({ + login: null, + passwd: null, + socketUrl: null, + tag: null, + localTag: null, + videoParams: {}, + audioParams: {}, + loginParams: {}, + deviceParams: {onResCheck: null}, + userVariables: {}, + iceServers: false, + ringSleep: 6000, + sessid: null, + useStream: null + }, options); + + if (verto.options.deviceParams.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck); + } + + if (!verto.options.deviceParams.useMic) { + verto.options.deviceParams.useMic = "any"; + } + + if (!verto.options.deviceParams.useSpeak) { + verto.options.deviceParams.useSpeak = "any"; + } + + if (verto.options.sessid) { + verto.sessid = verto.options.sessid; + } else { + verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID(); + localStorage.setItem("verto_session_uuid", verto.sessid); + } + + verto.dialogs = {}; + verto.callbacks = callbacks || {}; + verto.eventSUBS = {}; + + verto.rpcClient = new $.JsonRpcClient({ + login: verto.options.login, + passwd: verto.options.passwd, + socketUrl: verto.options.socketUrl, + wsFallbackURL: verto.options.wsFallbackURL, + turnServer: verto.options.turnServer, + loginParams: verto.options.loginParams, + userVariables: verto.options.userVariables, + 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(); + } + }); + + var tag = verto.options.tag; + if (typeof(tag) === "function") { + tag = tag(); + } + + if (verto.options.ringFile && verto.options.tag) { + verto.ringer = $("#" + tag); + } + + verto.rpcClient.call('login', {}); + + }; + + + $.verto.prototype.deviceParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.deviceParams[i] = obj[i]; + } + + if (obj.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined); + } + }; + + $.verto.prototype.videoParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.videoParams[i] = obj[i]; + } + }; + + $.verto.prototype.iceServers = function(obj) { + var verto = this; + verto.options.iceServers = obj; + }; + + $.verto.prototype.loginData = function(params) { + var verto = this; + 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(); + if (verto.callbacks.onWSClose) { + verto.callbacks.onWSClose(verto, false); + } + 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": + //console.error(e); + break; + } + }; + + $.verto.prototype.sendMethod = function(method, params) { + var verto = this; + + verto.rpcClient.call(method, params, + + function(e) { + /* Success */ + verto.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + if (args["useCamera"]) { + verto.options.deviceParams["useCamera"] = args["useCamera"]; + } + + var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args); + + if (callbacks) { + dialog.callbacks = callbacks; + } + + dialog.invite(); + + return dialog; + }; + + $.verto.prototype.handleMessage = function(data) { + var verto = this; + + if (!(data && data.method)) { + console.error("Invalid Data", data); + return; + } + + if (data.params.callID) { + var dialog = verto.dialogs[data.params.callID]; + + if (data.method === "verto.attach" && dialog) { + delete dialog.verto.dialogs[dialog.callID]; + dialog.rtc.stop(); + dialog = null; + } + + if (dialog) { + + switch (data.method) { + case 'verto.bye': + dialog.hangup(data.params); + break; + case 'verto.answer': + dialog.handleAnswer(data.params); + break; + case 'verto.media': + dialog.handleMedia(data.params); + 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", dialog, 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); + dialog.setState($.verto.enum.state.recovering); + + 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.punt': + verto.purge(); + verto.logout(); + break; + 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 && key === verto.sessid) { + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params); + } + } else 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; + + case 'verto.clientReady': + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params); + } + console.debug("CLIENT READY", data.params); + 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 ? "
" : "\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; + + // Inherit methods of hashArray + hashArray.call(la); + + // Save the hashArray add, del, reorder, clear methods so we can make our own. + 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)) { + + //la.clear(); + 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 + }); + } + } + }; + + // @param serno La is the serial number for la particular request. + // @param key If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object. + // @param index If looking at it as an array, la represents the position in the array where you want to store the val object. + // @param val La is the object you want to store at the key or index location in the hash table / array. + 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; + + //console.error("READ:", packet); + + if (packet.name != la.name) { + return; + } + + 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.sendCommand = function(cmd, obj) { + var self = la; + self.broadcast(self.context, { + liveArray: { + command: cmd, + context: self.context, + name: self.name, + obj: obj + } + }); + }; + + la.bootstrap = function(obj) { + var self = la; + la.sendCommand("bootstrap", obj); + //self.heartbeat(); + }; + + 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); + }; + + /* back compat so jsonstatus can always be enabled */ + function genRow(data) { + if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) { + var tmp = $.parseJSON(data[4]); + data[4] = tmp.oldStatus; + data[5] = null; + } + return data; + } + + function genArray(obj) { + var data = obj.asArray(); + + for (var i in data) { + data[i] = genRow(data[i]); + } + + return data; + } + + + 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; + } + } + + if (config.onChange) { + config.onChange(obj, args); + } + + try { + switch (args.action) { + case "bootObj": + if (!args.data) { + console.error("missing data"); + return; + } + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + dt.fnAdjustColumnSizing(); + break; + case "add": + if (!args.data) { + console.error("missing data"); + return; + } + if (args.redraw > -1) { + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + } else { + dt.fnAddData(genRow(args.data)); + } + dt.fnAdjustColumnSizing(); + break; + case "modify": + if (!args.data) { + return; + } + //console.debug(args, index); + dt.fnUpdate(genRow(args.data), index); + dt.fnAdjustColumnSizing(); + break; + case "del": + dt.fnDeleteRow(index); + dt.fnAdjustColumnSizing(); + break; + case "clear": + dt.fnClearTable(); + break; + case "reorder": + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + 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; + } + + }; + + la.onChange(la, { + action: "init" + }); + + }; + + var CONFMAN_SERNO = 1; + + /* + Conference Manager without jQuery table. + */ + + $.verto.conf = function(verto, params) { + var conf = this; + + conf.params = $.extend({ + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + conf.verto = verto; + conf.serno = CONFMAN_SERNO++; + + createMainModeratorMethods(); + + verto.subscribe(conf.params.laData.modChannel, { + handler: function(v, e) { + if (conf.params.onBroadcast) { + conf.params.onBroadcast(verto, conf, e.data); + } + } + }); + + verto.subscribe(conf.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(conf.params.infoCallback) === "function") { + conf.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(conf.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(conf.params.chatCallback) === "function") { + conf.params.chatCallback(v,e); + } + } + }); + }; + + $.verto.conf.prototype.modCommand = function(cmd, id, value) { + var conf = this; + + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.conf.prototype.destroy = function() { + var conf = this; + + conf.destroyed = true; + conf.params.onBroadcast(conf.verto, conf, 'destroy'); + + if (conf.params.laData.modChannel) { + conf.verto.unsubscribe(conf.params.laData.modChannel); + } + + if (conf.params.laData.chatChannel) { + conf.verto.unsubscribe(conf.params.laData.chatChannel); + } + + if (conf.params.laData.infoChannel) { + conf.verto.unsubscribe(conf.params.laData.infoChannel); + } + }; + + function createMainModeratorMethods() { + $.verto.conf.prototype.listVideoLayouts = function() { + this.modCommand("list-videoLayouts", null, null); + }; + + $.verto.conf.prototype.play = function(file) { + this.modCommand("play", null, file); + }; + + $.verto.conf.prototype.stop = function() { + this.modCommand("stop", null, "all"); + }; + + $.verto.conf.prototype.deaf = function(memberID) { + this.modCommand("deaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.undeaf = function(memberID) { + this.modCommand("undeaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.record = function(file) { + this.modCommand("recording", null, ["start", file]); + }; + + $.verto.conf.prototype.stopRecord = function() { + this.modCommand("recording", null, ["stop", "all"]); + }; + + $.verto.conf.prototype.snapshot = function(file) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-write-png", null, file); + }; + + $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + if (canvasID) { + this.modCommand("vid-layout", null, [layout, canvasID]); + } else { + this.modCommand("vid-layout", null, layout); + } + }; + + $.verto.conf.prototype.kick = function(memberID) { + this.modCommand("kick", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteMic = function(memberID) { + this.modCommand("tmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteVideo = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("tvmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.presenter = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-res-id", parseInt(memberID), "presenter"); + }; + + $.verto.conf.prototype.videoFloor = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-floor", parseInt(memberID), "force"); + }; + + $.verto.conf.prototype.banner = function(memberID, text) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-banner", parseInt(memberID), escape(text)); + }; + + $.verto.conf.prototype.volumeDown = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.volumeUp = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.gainDown = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.gainUp = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.transfer = function(memberID, exten) { + this.modCommand("transfer", parseInt(memberID), exten); + }; + + $.verto.conf.prototype.sendChat = function(message, type) { + var conf = this; + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + } + + $.verto.modfuncs = {}; + + $.verto.confMan = function(verto, params) { + var confMan = this; + + confMan.params = $.extend({ + tableID: null, + statusID: null, + mainModID: null, + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + confMan.verto = verto; + confMan.serno = CONFMAN_SERNO++; + confMan.canvasCount = confMan.params.laData.canvasCount; + + function genMainMod(jq) { + var play_id = "play_" + confMan.serno; + var stop_id = "stop_" + confMan.serno; + var recording_id = "recording_" + confMan.serno; + var snapshot_id = "snapshot_" + confMan.serno; + var rec_stop_id = "recording_stop" + confMan.serno; + var div_id = "confman_" + confMan.serno; + + var html = "

" + + "" + + "" + + "" + + "" + + (confMan.params.hasVid ? "" : "") + + "

"; + + jq.html(html); + + $.verto.modfuncs.change_video_layout = function(id, canvas_id) { + var val = $("#" + id + " option:selected").text(); + if (val !== "none") { + confMan.modCommand("vid-layout", null, [val, canvas_id]); + } + }; + + if (confMan.params.hasVid) { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno; + var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno; + + + var vlhtml = "

" + + "Video Layout Canvas " + (j+1) + + " " + + "

"; + jq.append(vlhtml); + } + + $("#" + snapshot_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("vid-write-png", null, file); + } + }); + } + + $("#" + play_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("play", null, file); + } + }); + + $("#" + stop_id).click(function() { + confMan.modCommand("stop", null, "all"); + }); + + $("#" + recording_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("recording", null, ["start", file]); + } + }); + + $("#" + rec_stop_id).click(function() { + confMan.modCommand("recording", null, ["stop", "all"]); + }); + + } + + function genControls(jq, rowid) { + var x = parseInt(rowid); + var kick_id = "kick_" + x; + var canvas_in_next_id = "canvas_in_next_" + x; + var canvas_in_prev_id = "canvas_in_prev_" + x; + var canvas_out_next_id = "canvas_out_next_" + x; + var canvas_out_prev_id = "canvas_out_prev_" + x; + + var canvas_in_set_id = "canvas_in_set_" + x; + var canvas_out_set_id = "canvas_out_set_" + x; + + var layer_set_id = "layer_set_" + x; + var layer_next_id = "layer_next_" + x; + var layer_prev_id = "layer_prev_" + x; + + var tmute_id = "tmute_" + x; + var tvmute_id = "tvmute_" + x; + var vbanner_id = "vbanner_" + x; + var tvpresenter_id = "tvpresenter_" + x; + var tvfloor_id = "tvfloor_" + x; + var box_id = "box_" + x; + var gainup_id = "gain_in_up" + x; + var gaindn_id = "gain_in_dn" + x; + var volup_id = "vol_in_up" + x; + var voldn_id = "vol_in_dn" + x; + var transfer_id = "transfer" + x; + + + var html = "
"; + + html += "General Controls
"; + + html += "" + + "" + + "" + + "" + + "" + + "" + + ""; + + if (confMan.params.hasVid) { + html += "

Video Controls
"; + + + html += "" + + "" + + "" + + ""; + + if (confMan.canvasCount > 1) { + html += "

Canvas Controls
" + + "" + + "" + + "" + + + "
" + + + "" + + "" + + ""; + } + + html += "
" + + + "" + + "" + + "" + + + + + "
"; + } + + jq.html(html); + + + if (!jq.data("mouse")) { + $("#" + box_id).hide(); + } + + jq.mouseover(function(e) { + jq.data({"mouse": true}); + $("#" + box_id).show(); + }); + + jq.mouseout(function(e) { + jq.data({"mouse": false}); + $("#" + box_id).hide(); + }); + + $("#" + transfer_id).click(function() { + var xten = prompt("Enter Extension"); + if (xten) { + confMan.modCommand("transfer", x, xten); + } + }); + + $("#" + kick_id).click(function() { + confMan.modCommand("kick", x); + }); + + + $("#" + layer_set_id).click(function() { + var cid = prompt("Please enter layer ID", ""); + if (cid) { + confMan.modCommand("vid-layer", x, cid); + } + }); + + $("#" + layer_next_id).click(function() { + confMan.modCommand("vid-layer", x, "next"); + }); + $("#" + layer_prev_id).click(function() { + confMan.modCommand("vid-layer", x, "prev"); + }); + + $("#" + canvas_in_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-canvas", x, cid); + } + }); + + $("#" + canvas_out_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-watching-canvas", x, cid); + } + }); + + $("#" + canvas_in_next_id).click(function() { + confMan.modCommand("vid-canvas", x, "next"); + }); + $("#" + canvas_in_prev_id).click(function() { + confMan.modCommand("vid-canvas", x, "prev"); + }); + + + $("#" + canvas_out_next_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "next"); + }); + $("#" + canvas_out_prev_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "prev"); + }); + + $("#" + tmute_id).click(function() { + confMan.modCommand("tmute", x); + }); + + if (confMan.params.hasVid) { + $("#" + tvmute_id).click(function() { + confMan.modCommand("tvmute", x); + }); + $("#" + tvpresenter_id).click(function() { + confMan.modCommand("vid-res-id", x, "presenter"); + }); + $("#" + tvfloor_id).click(function() { + confMan.modCommand("vid-floor", x, "force"); + }); + $("#" + vbanner_id).click(function() { + var text = prompt("Please enter text", ""); + if (text) { + confMan.modCommand("vid-banner", x, escape(text)); + } + }); + } + + $("#" + gainup_id).click(function() { + confMan.modCommand("volume_in", x, "up"); + }); + + $("#" + gaindn_id).click(function() { + confMan.modCommand("volume_in", x, "down"); + }); + + $("#" + volup_id).click(function() { + confMan.modCommand("volume_out", x, "up"); + }); + + $("#" + voldn_id).click(function() { + confMan.modCommand("volume_out", x, "down"); + }); + + return html; + } + + var atitle = ""; + var awidth = 0; + + //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px"); + + verto.subscribe(confMan.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(confMan.params.infoCallback) === "function") { + confMan.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(confMan.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(confMan.params.chatCallback) === "function") { + confMan.params.chatCallback(v,e); + } + } + }); + + if (confMan.params.laData.role === "moderator") { + atitle = "Action"; + awidth = 600; + + if (confMan.params.mainModID) { + genMainMod($(confMan.params.mainModID)); + $(confMan.params.displayID).html("Moderator Controls Ready

"); + } else { + $(confMan.params.mainModID).html(""); + } + + verto.subscribe(confMan.params.laData.modChannel, { + handler: function(v, e) { + //console.error("MODDATA:", e.data); + if (confMan.params.onBroadcast) { + confMan.params.onBroadcast(verto, confMan, e.data); + } + + if (e.data["conf-command"] === "list-videoLayouts") { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno; + var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno; + + var x = 0; + var options; + + $(vlselect_id).selectmenu({}); + $(vlselect_id).selectmenu("enable"); + $(vlselect_id).empty(); + + $(vlselect_id).append(new Option("Choose a Layout", "none")); + + if (e.data.responseData) { + var rdata = []; + + for (var i in e.data.responseData) { + rdata.push(e.data.responseData[i].name); + } + + options = rdata.sort(function(a, b) { + var ga = a.substring(0, 6) == "group:" ? true : false; + var gb = b.substring(0, 6) == "group:" ? true : false; + + if ((ga || gb) && ga != gb) { + return ga ? -1 : 1; + } + + return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) ); + }); + + for (var i in options) { + $(vlselect_id).append(new Option(options[i], options[i])); + x++; + } + } + + if (x) { + $(vlselect_id).selectmenu('refresh', true); + } else { + $(vlayout_id).hide(); + } + } + } else { + + if (!confMan.destroyed && confMan.params.displayID) { + $(confMan.params.displayID).html(e.data.response + "

"); + if (confMan.lastTimeout) { + clearTimeout(confMan.lastTimeout); + confMan.lastTimeout = 0; + } + confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready

");}, 4000); + } + } + } + }); + + + if (confMan.params.hasVid) { + confMan.modCommand("list-videoLayouts", null, null); + } + } + + var row_callback = null; + + if (confMan.params.laData.role === "moderator") { + row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) { + if (!aData[5]) { + var $row = $('td:eq(5)', nRow); + genControls($row, aData); + + if (confMan.params.onLaRow) { + confMan.params.onLaRow(verto, confMan, $row, aData); + } + } + }; + } + + confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), { + subParams: { + callID: confMan.params.dialog ? confMan.params.dialog.callID : null + }, + + "onChange": function(obj, args) { + $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)"); + if (confMan.params.onLaChange) { + confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args); + } + }, + + "aaData": [], + "aoColumns": [ + { + "sTitle": "ID", + "sWidth": "50" + }, + { + "sTitle": "Number", + "sWidth": "250" + }, + { + "sTitle": "Name", + "sWidth": "250" + }, + { + "sTitle": "Codec", + "sWidth": "100" + }, + { + "sTitle": "Status", + "sWidth": confMan.params.hasVid ? "200px" : "150px" + }, + { + "sTitle": atitle, + "sWidth": awidth, + } + ], + "bAutoWidth": true, + "bDestroy": true, + "bSort": false, + "bInfo": false, + "bFilter": false, + "bLengthChange": false, + "bPaginate": false, + "iDisplayLength": 1400, + + "oLanguage": { + "sEmptyTable": "The Conference is Empty....." + }, + + "fnRowCallback": row_callback + + }); + }; + + $.verto.confMan.prototype.modCommand = function(cmd, id, value) { + var confMan = this; + + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.confMan.prototype.sendChat = function(message, type) { + var confMan = this; + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + + $.verto.confMan.prototype.destroy = function() { + var confMan = this; + + confMan.destroyed = true; + + if (confMan.lt) { + confMan.lt.destroy(); + } + + if (confMan.params.laData.chatChannel) { + confMan.verto.unsubscribe(confMan.params.laData.chatChannel); + } + + if (confMan.params.laData.modChannel) { + confMan.verto.unsubscribe(confMan.params.laData.modChannel); + } + + if (confMan.params.mainModID) { + $(confMan.params.mainModID).html(""); + } + }; + + $.verto.dialog = function(direction, verto, params) { + var dialog = this; + + dialog.params = $.extend({ + useVideo: verto.options.useVideo, + useStereo: verto.options.useStereo, + screenShare: false, + useCamera: false, + useMic: verto.options.deviceParams.useMic, + useSpeak: verto.options.deviceParams.useSpeak, + tag: verto.options.tag, + localTag: verto.options.localTag, + login: verto.options.login, + videoParams: verto.options.videoParams, + useStream: verto.options.useStream, + }, params); + + + if (!dialog.params.screenShare) { + dialog.params.useCamera = verto.options.deviceParams.useCamera; + } + + 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; + dialog.screenShare = params.screenShare || false; + dialog.useCamera = dialog.params.useCamera; + dialog.useMic = dialog.params.useMic; + dialog.useSpeak = dialog.params.useSpeak; + + if (dialog.params.callID) { + dialog.callID = dialog.params.callID; + } else { + dialog.callID = dialog.params.callID = generateGUID(); + } + + if (typeof(dialog.params.tag) === "function") { + dialog.params.tag = dialog.params.tag(); + } + + if (dialog.params.tag) { + dialog.audioStream = document.getElementById(dialog.params.tag); + + if (dialog.params.useVideo) { + dialog.videoStream = dialog.audioStream; + } + } //else conjure one TBD + + if (dialog.params.localTag) { + dialog.localVideo = document.getElementById(dialog.params.localTag); + } + + dialog.verto.dialogs[dialog.callID] = dialog; + + var RTCcallbacks = {}; + + if (dialog.direction == $.verto.enum.direction.inbound) { + if (dialog.params.display_direction === "outbound") { + dialog.params.remote_caller_id_name = dialog.params.caller_id_name; + dialog.params.remote_caller_id_number = dialog.params.caller_id_number; + } else { + dialog.params.remote_caller_id_name = dialog.params.callee_id_name; + dialog.params.remote_caller_id_number = dialog.params.callee_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) { + console.log("RECV " + rtc.type + " SDP", rtc.mediaData.SDP); + + if (dialog.state == $.verto.enum.state.requesting || dialog.state == $.verto.enum.state.answering || dialog.state == $.verto.enum.state.active) { + location.reload(); + return; + } + + if (rtc.type == "offer") { + if (dialog.state == $.verto.enum.state.active) { + dialog.setState($.verto.enum.state.requesting); + dialog.sendMethod("verto.attach", { + sdp: rtc.mediaData.SDP + }); + } else { + dialog.setState($.verto.enum.state.requesting); + + dialog.sendMethod("verto.invite", { + sdp: rtc.mediaData.SDP + }); + } + } else { //answer + dialog.setState($.verto.enum.state.answering); + + dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", { + sdp: dialog.rtc.mediaData.SDP + }); + } + }; + + RTCcallbacks.onICE = function(rtc) { + //console.log("cand", rtc.mediaData.candidate); + if (rtc.type == "offer") { + console.log("offer", rtc.mediaData.candidate); + return; + } + }; + + RTCcallbacks.onStream = function(rtc, stream) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onGranted === 'function') { + dialog.callbacks.permissionCallback.onGranted(stream); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onGranted === 'function'){ + dialog.verto.options.permissionCallback.onGranted(stream); + } + console.log("stream started"); + }; + + RTCcallbacks.onRemoteStream = function(rtc, stream) { + if (typeof dialog.callbacks.onRemoteStream === 'function') { + dialog.callbacks.onRemoteStream(stream, dialog); + } + console.log("remote stream started"); + }; + + RTCcallbacks.onError = function(e) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onDenied === 'function') { + dialog.callbacks.permissionCallback.onDenied(); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onDenied === 'function'){ + dialog.verto.options.permissionCallback.onDenied(); + } + console.error("ERROR:", e); + dialog.hangup({cause: "Device or Permission Error"}); + }; + + dialog.rtc = new $.FSRTC({ + callbacks: RTCcallbacks, + localVideo: dialog.screenShare ? null : dialog.localVideo, + useVideo: dialog.params.useVideo ? dialog.videoStream : null, + useAudio: dialog.audioStream, + useStereo: dialog.params.useStereo, + videoParams: dialog.params.videoParams, + audioParams: verto.options.audioParams, + iceServers: verto.options.iceServers, + screenShare: dialog.screenShare, + useCamera: dialog.useCamera, + useMic: dialog.useMic, + useSpeak: dialog.useSpeak, + turnServer: verto.options.turnServer, + useStream: dialog.params.useStream + }); + + 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; + } + + if ((obj.noDialogParams && i != "callID")) { + continue; + } + + obj.dialogParams[i] = dialog.params[i]; + } + + delete obj.noDialogParams; + + dialog.verto.rpcClient.call(method, obj, + + function(e) { + /* Success */ + dialog.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + + // Attach audio output device to video element using device/sink ID. + function find_name(id) { + for (var i in $.verto.audioOutDevices) { + var source = $.verto.audioOutDevices[i]; + if (source.id === id) { + return(source.label); + } + } + + return id; + } + + $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) { + var dialog = this; + var element = dialog.audioStream; + + if (typeof element.sinkId !== 'undefined') { + var devname = find_name(sinkId); + console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname); + + element.setSinkId(sinkId) + .then(function() { + console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId); + if (callback) { + callback(true, devname, arg); + } + }) + .catch(function(error) { + var errorMessage = error; + if (error.name === 'SecurityError') { + errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' + + 'device: ' + error; + } + if (callback) { + callback(false, null, arg); + } + console.error(errorMessage); + }); + } else { + console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.'); + if (callback) { + callback(false, null, arg); + } + } + } + + $.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.log("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.early: + case $.verto.enum.state.active: + + var speaker = dialog.useSpeak; + console.info("Using Speaker: ", speaker); + + if (speaker && speaker !== "any" && speaker !== "none") { + setTimeout(function() { + dialog.setAudioPlaybackDevice(speaker); + }, 500); + } + + break; + + case $.verto.enum.state.trying: + setTimeout(function() { + if (dialog.state == $.verto.enum.state.trying) { + dialog.setState($.verto.enum.state.hangup); + } + }, 30000); + break; + 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: + + if (typeof(dialog.verto.options.tag) === "function") { + $('#' + dialog.params.tag).remove(); + } + + delete dialog.verto.dialogs[dialog.callID]; + if (dialog.params.screenShare) { + dialog.rtc.stopPeer(); + } else { + 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(params) { + var dialog = this; + + if (params) { + if (params.causeCode) { + dialog.causeCode = params.causeCode; + } + + if (params.cause) { + dialog.cause = params.cause; + } + } + + if (!dialog.cause && !dialog.causeCode) { + dialog.cause = "NORMAL_CLEARING"; + } + + 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, dialog.localVideo); + + }; + + $.verto.dialog.prototype.setMute = function(what) { + var dialog = this; + return dialog.rtc.setMute(what); + }; + + $.verto.dialog.prototype.getMute = function() { + var dialog = this; + return dialog.rtc.getMute(); + }; + + $.verto.dialog.prototype.setVideoMute = function(what) { + var dialog = this; + return dialog.rtc.setVideoMute(what); + }; + + $.verto.dialog.prototype.getVideoMute = function() { + var dialog = this; + return dialog.rtc.getVideoMute(); + }; + + $.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.rtt = function(obj) { + var dialog = this; + var pobj = {}; + + if (!obj) { + return false; + } + + pobj.code = obj.code; + pobj.chars = obj.chars; + + + if (pobj.chars || pobj.code) { + dialog.sendMethod("verto.info", { + txt: obj, + noDialogParams: true + }); + } + }; + + $.verto.dialog.prototype.transfer = function(dest, params) { + var dialog = this; + if (dest) { + dialog.sendMethod("verto.modify", { + action: "transfer", + destination: dest, + params: params + }); + } + }; + + $.verto.dialog.prototype.replace = function(replaceCallID, params) { + var dialog = this; + if (replaceCallID) { + dialog.sendMethod("verto.modify", { + action: "replace", + replaceCallID: replaceCallID, + params: params + }); + } + }; + + $.verto.dialog.prototype.hold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "hold", + params: params + }); + }; + + $.verto.dialog.prototype.unhold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "unhold", + params: params + }); + }; + + $.verto.dialog.prototype.toggleHold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "toggleHold", + params: params + }); + }; + + $.verto.dialog.prototype.message = function(msg) { + var dialog = this; + var err = 0; + + msg.from = dialog.params.login; + + 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) { + params = {}; + } + + params.sdp = dialog.params.sdp; + + if (params) { + if (params.useVideo) { + dialog.useVideo(true); + } + dialog.params.callee_id_name = params.callee_id_name; + dialog.params.callee_id_number = params.callee_id_number; + + if (params.useCamera) { + dialog.useCamera = params.useCamera; + } + + if (params.useMic) { + dialog.useMic = params.useMic; + } + + if (params.useSpeak) { + dialog.useSpeak = params.useSpeak; + } + } + + dialog.rtc.createAnswer(params); + dialog.answered = true; + } + }; + + $.verto.dialog.prototype.handleAnswer = function(params) { + var dialog = this; + + dialog.gotAnswer = true; + + 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 { + if (dialog.gotEarly) { + console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying..."); + } else { + console.log("Dialog " + dialog.callID + " Answering Channel"); + dialog.rtc.answer(params.sdp, function() { + dialog.setState($.verto.enum.state.active); + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp); + } + } + + + }; + + $.verto.dialog.prototype.cidString = function(enc) { + var dialog = this; + var party = dialog.params.remote_caller_id_name + (enc ? " <" : " <") + dialog.params.remote_caller_id_number + (enc ? ">" : ">"); + 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); + + }; + + $.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(params) { + var dialog = this; + + if (dialog.state.val >= $.verto.enum.state.early.val) { + return; + } + + dialog.gotEarly = true; + + dialog.rtc.answer(params.sdp, function() { + console.log("Dialog " + dialog.callID + "Establishing early media"); + dialog.setState($.verto.enum.state.early); + + if (dialog.gotAnswer) { + console.log("Dialog " + dialog.callID + "Answering Channel"); + dialog.setState($.verto.enum.state.active); + } + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "EARLY SDP", params.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, + recovering: 1, + ringing: 1, + destroy: 1, + answering: 1, + hangup: 1 + }, + requesting: { + trying: 1, + hangup: 1, + active: 1 + }, + recovering: { + answering: 1, + hangup: 1 + }, + trying: { + active: 1, + early: 1, + hangup: 1 + }, + ringing: { + answering: 1, + hangup: 1 + }, + answering: { + active: 1, + hangup: 1 + }, + active: { + answering: 1, + requesting: 1, + 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 recovering ringing answering early active held hangup destroy purge"); + $.verto.enum.direction = $.verto.ENUM("inbound outbound"); + $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady"); + + $.verto.enum = Object.freeze($.verto.enum); + + $.verto.saved = []; + + $.verto.unloadJobs = []; + + var unloadEventName = 'beforeunload'; + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + if (iOS) { + unloadEventName = 'pagehide'; + } + + $(window).bind(unloadEventName, function() { + for (var f in $.verto.unloadJobs) { + $.verto.unloadJobs[f](); + } + + if ($.verto.haltClosure) + return $.verto.haltClosure(); + + for (var i in $.verto.saved) { + var verto = $.verto.saved[i]; + if (verto) { + verto.purge(); + verto.logout(); + } + } + + return $.verto.warnOnUnload; + }); + + $.verto.videoDevices = []; + $.verto.audioInDevices = []; + $.verto.audioOutDevices = []; + + var checkDevices = function(runtime) { + console.info("enumerating devices"); + var aud_in = [], aud_out = [], vid = []; + var has_video = 0, has_audio = 0; + var Xstream; + + function gotDevices(deviceInfos) { + // Handles being called several times to update labels. Preserve values. + for (var i = 0; i !== deviceInfos.length; ++i) { + var deviceInfo = deviceInfos[i]; + var text = ""; + + console.log(deviceInfo); + console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId); + + if (deviceInfo.kind === 'audioinput') { + text = deviceInfo.label || 'microphone ' + (aud_in.length + 1); + aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text}); + } else if (deviceInfo.kind === 'audiooutput') { + text = deviceInfo.label || 'speaker ' + (aud_out.length + 1); + aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text}); + } else if (deviceInfo.kind === 'videoinput') { + text = deviceInfo.label || 'camera ' + (vid.length + 1); + vid.push({id: deviceInfo.deviceId, kind: "video", label: text}); + } else { + console.log('Some other kind of source/device: ', deviceInfo); + } + } + + + $.verto.videoDevices = vid; + $.verto.audioInDevices = aud_in; + $.verto.audioOutDevices = aud_out; + + console.info("Audio IN Devices", $.verto.audioInDevices); + console.info("Audio Out Devices", $.verto.audioOutDevices); + console.info("Video Devices", $.verto.videoDevices); + + if (Xstream) { + Xstream.getTracks().forEach(function(track) {track.stop();}); + } + + if (runtime) { + runtime(true); + } + } + + + + + function handleError(error) { + console.log('device enumeration error: ', error); + if (runtime) runtime(false); + } + + + function checkTypes(devs) { + for (var i = 0; i !== devs.length; ++i) { + + if (devs[i].kind === 'audioinput') { + has_audio++; + } else if (devs[i].kind === 'videoinput') { + has_video++; + } + } + + navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)}, + function(stream) { + Xstream = stream; + navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); + }, + function(err) { + console.log("The following error occurred: " + err.name); + } + ); + } + + navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError); + + }; + + $.verto.refreshDevices = function(runtime) { + checkDevices(runtime); + } + + $.verto.init = function(obj, runtime) { + if (!obj) { + obj = {}; + } + + if (!obj.skipPermCheck && !obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + checkDevices(runtime); + }, true, true); + } else if (obj.skipPermCheck && !obj.skipDeviceCheck) { + checkDevices(runtime); + } else if (!obj.skipPermCheck && obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + runtime(status); + }, true, true); + } else { + runtime(null); + } + + } + + $.verto.genUUID = function () { + return generateGUID(); + } + + +})(jQuery); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 14393 && + url.indexOf('?transport=udp') === -1; + }); + + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} + +// Determines the intersection of local and remote capabilities. +function getCommonCapabilities(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + + var findCodecByPayloadType = function(pt, codecs) { + pt = parseInt(pt, 10); + for (var i = 0; i < codecs.length; i++) { + if (codecs[i].payloadType === pt || + codecs[i].preferredPayloadType === pt) { + return codecs[i]; + } + } + }; + + var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { + var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); + var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); + return lCodec && rCodec && + lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); + }; + + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate) { + if (lCodec.name.toLowerCase() === 'rtx' && + lCodec.parameters && rCodec.parameters.apt) { + // for RTX we need to find the local rtx that has a apt + // which points to the same local codec as the remote one. + if (!rtxCapabilityMatches(lCodec, rCodec, + localCapabilities.codecs, remoteCapabilities.codecs)) { + continue; + } + } + rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy + // number of channels is the highest common number of channels + rCodec.numChannels = Math.min(lCodec.numChannels, + rCodec.numChannels); + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; +} + +// is action=setLocalDescription with type allowed in signalingState +function isActionAllowedInSignalingState(action, type, signalingState) { + return { + offer: { + setLocalDescription: ['stable', 'have-local-offer'], + setRemoteDescription: ['stable', 'have-remote-offer'] + }, + answer: { + setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], + setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + } + }[type][action].indexOf(signalingState) !== -1; +} + +function maybeAddCandidate(iceTransport, candidate) { + // Edge's internal representation adds some fields therefore + // not all fieldѕ are taken into account. + var alreadyAdded = iceTransport.getRemoteCandidates() + .find(function(remoteCandidate) { + return candidate.foundation === remoteCandidate.foundation && + candidate.ip === remoteCandidate.ip && + candidate.port === remoteCandidate.port && + candidate.priority === remoteCandidate.priority && + candidate.protocol === remoteCandidate.protocol && + candidate.type === remoteCandidate.type; + }); + if (!alreadyAdded) { + iceTransport.addRemoteCandidate(candidate); + } + return !alreadyAdded; +} + + +// https://w3c.github.io/mediacapture-main/#mediastream +// Helper function to add the track to the stream and +// dispatch the event ourselves. +function addTrackToStreamAndFireEvent(track, stream) { + stream.addTrack(track); + var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function removeTrackFromStreamAndFireEvent(track, stream) { + stream.removeTrack(track); + var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function fireAddTrack(pc, track, receiver, streams) { + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = {receiver: receiver}; + trackEvent.streams = streams; + window.setTimeout(function() { + pc._dispatchEvent('track', trackEvent); + }); +} + +function makeError(name, description) { + var e = new Error(description); + e.name = name; + return e; +} + +module.exports = function(window, edgeVersion) { + var RTCPeerConnection = function(config) { + var pc = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + pc[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.canTrickleIceCandidates = null; + + this.needNegotiation = false; + + this.localStreams = []; + this.remoteStreams = []; + + this.localDescription = null; + this.remoteDescription = null; + + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.iceGatheringState = 'new'; + + config = JSON.parse(JSON.stringify(config || {})); + + this.usingBundle = config.bundlePolicy === 'max-bundle'; + if (config.rtcpMuxPolicy === 'negotiate') { + throw(makeError('NotSupportedError', + 'rtcpMuxPolicy \'negotiate\' is not supported')); + } else if (!config.rtcpMuxPolicy) { + config.rtcpMuxPolicy = 'require'; + } + + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + break; + default: + config.iceTransportPolicy = 'all'; + break; + } + + switch (config.bundlePolicy) { + case 'balanced': + case 'max-compat': + case 'max-bundle': + break; + default: + config.bundlePolicy = 'balanced'; + break; + } + + config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); + + this._iceGatherers = []; + if (config.iceCandidatePoolSize) { + for (var i = config.iceCandidatePoolSize; i > 0; i--) { + this._iceGatherers = new window.RTCIceGatherer({ + iceServers: config.iceServers, + gatherPolicy: config.iceTransportPolicy + }); + } + } else { + config.iceCandidatePoolSize = 0; + } + + this._config = config; + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + this._sdpSessionId = SDPUtils.generateSessionId(); + this._sdpSessionVersion = 0; + + this._dtlsRole = undefined; // role for a=setup to use in answers. + + this._isClosed = false; + }; + + // set up event handlers on prototype + RTCPeerConnection.prototype.onicecandidate = null; + RTCPeerConnection.prototype.onaddstream = null; + RTCPeerConnection.prototype.ontrack = null; + RTCPeerConnection.prototype.onremovestream = null; + RTCPeerConnection.prototype.onsignalingstatechange = null; + RTCPeerConnection.prototype.oniceconnectionstatechange = null; + RTCPeerConnection.prototype.onicegatheringstatechange = null; + RTCPeerConnection.prototype.onnegotiationneeded = null; + RTCPeerConnection.prototype.ondatachannel = null; + + RTCPeerConnection.prototype._dispatchEvent = function(name, event) { + if (this._isClosed) { + return; + } + this.dispatchEvent(event); + if (typeof this['on' + name] === 'function') { + this['on' + name](event); + } + }; + + RTCPeerConnection.prototype._emitGatheringStateChange = function() { + var event = new Event('icegatheringstatechange'); + this._dispatchEvent('icegatheringstatechange', event); + }; + + RTCPeerConnection.prototype.getConfiguration = function() { + return this._config; + }; + + RTCPeerConnection.prototype.getLocalStreams = function() { + return this.localStreams; + }; + + RTCPeerConnection.prototype.getRemoteStreams = function() { + return this.remoteStreams; + }; + + // internal helper to create a transceiver object. + // (whih is not yet the same as the WebRTC 1.0 transceiver) + RTCPeerConnection.prototype._createTransceiver = function(kind) { + var hasBundleTransport = this.transceivers.length > 0; + var transceiver = { + track: null, + iceGatherer: null, + iceTransport: null, + dtlsTransport: null, + localCapabilities: null, + remoteCapabilities: null, + rtpSender: null, + rtpReceiver: null, + kind: kind, + mid: null, + sendEncodingParameters: null, + recvEncodingParameters: null, + stream: null, + associatedRemoteMediaStreams: [], + wantReceive: true + }; + if (this.usingBundle && hasBundleTransport) { + transceiver.iceTransport = this.transceivers[0].iceTransport; + transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; + } else { + var transports = this._createIceAndDtlsTransports(); + transceiver.iceTransport = transports.iceTransport; + transceiver.dtlsTransport = transports.dtlsTransport; + } + this.transceivers.push(transceiver); + return transceiver; + }; + + RTCPeerConnection.prototype.addTrack = function(track, stream) { + var alreadyExists = this.transceivers.find(function(s) { + return s.track === track; + }); + + if (alreadyExists) { + throw makeError('InvalidAccessError', 'Track already exists.'); + } + + if (this.signalingState === 'closed') { + throw makeError('InvalidStateError', + 'Attempted to call addTrack on a closed peerconnection.'); + } + + var transceiver; + for (var i = 0; i < this.transceivers.length; i++) { + if (!this.transceivers[i].track && + this.transceivers[i].kind === track.kind) { + transceiver = this.transceivers[i]; + } + } + if (!transceiver) { + transceiver = this._createTransceiver(track.kind); + } + + this._maybeFireNegotiationNeeded(); + + if (this.localStreams.indexOf(stream) === -1) { + this.localStreams.push(stream); + } + + transceiver.track = track; + transceiver.stream = stream; + transceiver.rtpSender = new window.RTCRtpSender(track, + transceiver.dtlsTransport); + return transceiver.rtpSender; + }; + + RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + if (edgeVersion >= 15025) { + stream.getTracks().forEach(function(track) { + pc.addTrack(track, stream); + }); + } else { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + // Fixed in 15025 (or earlier) + var clonedStream = stream.clone(); + stream.getTracks().forEach(function(track, idx) { + var clonedTrack = clonedStream.getTracks()[idx]; + track.addEventListener('enabled', function(event) { + clonedTrack.enabled = event.enabled; + }); + }); + clonedStream.getTracks().forEach(function(track) { + pc.addTrack(track, clonedStream); + }); + } + }; + + RTCPeerConnection.prototype.removeTrack = function(sender) { + if (!(sender instanceof window.RTCRtpSender)) { + throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.'); + } + + var transceiver = this.transceivers.find(function(t) { + return t.rtpSender === sender; + }); + + if (!transceiver) { + throw makeError('InvalidAccessError', + 'Sender was not created by this connection.'); + } + var stream = transceiver.stream; + + transceiver.rtpSender.stop(); + transceiver.rtpSender = null; + transceiver.track = null; + transceiver.stream = null; + + // remove the stream from the set of local streams + var localStreams = this.transceivers.map(function(t) { + return t.stream; + }); + if (localStreams.indexOf(stream) === -1 && + this.localStreams.indexOf(stream) > -1) { + this.localStreams.splice(this.localStreams.indexOf(stream), 1); + } + + this._maybeFireNegotiationNeeded(); + }; + + RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + stream.getTracks().forEach(function(track) { + var sender = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (sender) { + pc.removeTrack(sender); + } + }); + }; + + RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + + RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, + usingBundle) { + var pc = this; + if (usingBundle && sdpMLineIndex > 0) { + return this.transceivers[0].iceGatherer; + } else if (this._iceGatherers.length) { + return this._iceGatherers.shift(); + } + var iceGatherer = new window.RTCIceGatherer({ + iceServers: this._config.iceServers, + gatherPolicy: this._config.iceTransportPolicy + }); + Object.defineProperty(iceGatherer, 'state', + {value: 'new', writable: true} + ); + + this.transceivers[sdpMLineIndex].candidates = []; + this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + iceGatherer.state = end ? 'completed' : 'gathering'; + if (pc.transceivers[sdpMLineIndex].candidates !== null) { + pc.transceivers[sdpMLineIndex].candidates.push(event.candidate); + } + }; + iceGatherer.addEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + return iceGatherer; + }; + + // start gathering from an RTCIceGatherer. + RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { + var pc = this; + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer.onlocalcandidate) { + return; + } + var candidates = this.transceivers[sdpMLineIndex].candidates; + this.transceivers[sdpMLineIndex].candidates = null; + iceGatherer.removeEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + iceGatherer.onlocalcandidate = function(evt) { + if (pc.usingBundle && sdpMLineIndex > 0) { + // if we know that we use bundle we can drop candidates with + // ѕdpMLineIndex > 0. If we don't do this then our state gets + // confused since we dispose the extra ice gatherer. + return; + } + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + var end = !cand || Object.keys(cand).length === 0; + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { + iceGatherer.state = 'completed'; + } + } else { + if (iceGatherer.state === 'new') { + iceGatherer.state = 'gathering'; + } + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + // update local description. + var sections = SDPUtils.splitSections(pc.localDescription.sdp); + if (!end) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=end-of-candidates\r\n'; + } + pc.localDescription.sdp = sections.join(''); + var complete = pc.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + if (pc.iceGatheringState !== 'gathering') { + pc.iceGatheringState = 'gathering'; + pc._emitGatheringStateChange(); + } + + // Emit candidate. Also emit null candidate when all gatherers are + // complete. + if (!end) { + pc._dispatchEvent('icecandidate', event); + } + if (complete) { + pc._dispatchEvent('icecandidate', new Event('icecandidate')); + pc.iceGatheringState = 'complete'; + pc._emitGatheringStateChange(); + } + }; + + // emit already gathered candidates. + window.setTimeout(function() { + candidates.forEach(function(candidate) { + var e = new Event('RTCIceGatherEvent'); + e.candidate = candidate; + iceGatherer.onlocalcandidate(e); + }); + }, 0); + }; + + // Create ICE transport and DTLS transport. + RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { + var pc = this; + var iceTransport = new window.RTCIceTransport(null); + iceTransport.onicestatechange = function() { + pc._updateConnectionState(); + }; + + var dtlsTransport = new window.RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + pc._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itpc. + Object.defineProperty(dtlsTransport, 'state', + {value: 'failed', writable: true}); + pc._updateConnectionState(); + }; + + return { + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Destroy ICE gatherer, ICE transport and DTLS transport. + // Without triggering the callbacks. + RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( + sdpMLineIndex) { + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer) { + delete iceGatherer.onlocalcandidate; + delete this.transceivers[sdpMLineIndex].iceGatherer; + } + var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; + if (iceTransport) { + delete iceTransport.onicestatechange; + delete this.transceivers[sdpMLineIndex].iceTransport; + } + var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; + if (dtlsTransport) { + delete dtlsTransport.ondtlsstatechange; + delete dtlsTransport.onerror; + delete this.transceivers[sdpMLineIndex].dtlsTransport; + } + }; + + // Start the RTP Sender and Receiver for a transceiver. + RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName, + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { + // remove RTX field in Edge 14942 + if (transceiver.kind === 'video' + && transceiver.recvEncodingParameters + && edgeVersion < 15019) { + transceiver.recvEncodingParameters.forEach(function(p) { + delete p.rtx; + }); + } + if (transceiver.recvEncodingParameters.length) { + params.encodings = transceiver.recvEncodingParameters; + } + params.rtcp = { + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.rtcpParameters.cname) { + params.rtcp.cname = transceiver.rtcpParameters.cname; + } + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + RTCPeerConnection.prototype.setLocalDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setLocalDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set local ' + description.type + + ' in state ' + pc.signalingState)); + } + + var sections; + var sessionpart; + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + pc.transceivers[sdpMLineIndex].localCapabilities = caps; + }); + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + pc._gather(transceiver.mid, sdpMLineIndex); + }); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc.remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = pc.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + + if (!rejected && !transceiver.isDatachannel) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!pc.usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver.mid, sdpMLineIndex); + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + // Calculate intersection of capabilities. + var params = getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + pc._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.setRemoteDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setRemoteDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set remote ' + description.type + + ' in state ' + pc.signalingState)); + } + + var streams = {}; + this.remoteStreams.forEach(function(stream) { + streams[stream.id] = stream; + }); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + var usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + this.usingBundle = usingBundle; + var iceOptions = SDPUtils.matchPrefix(sessionpart, + 'a=ice-options:')[0]; + if (iceOptions) { + this.canTrickleIceCandidates = iceOptions.substr(14).split(' ') + .indexOf('trickle') >= 0; + } else { + this.canTrickleIceCandidates = false; + } + + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var kind = SDPUtils.getKind(mediaSection); + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + var protocol = lines[0].substr(2).split(' ')[2]; + + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + var remoteMsid = SDPUtils.parseMsid(mediaSection); + + var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); + + // Reject datachannels which are not implemented yet. + if (kind === 'application' && protocol === 'DTLS/SCTP') { + pc.transceivers[sdpMLineIndex] = { + mid: mid, + isDatachannel: true + }; + return; + } + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === 1; + }); + + // Check if we can use BUNDLE and dispose transports. + if ((description.type === 'offer' || description.type === 'answer') && + !rejected && usingBundle && sdpMLineIndex > 0 && + pc.transceivers[sdpMLineIndex]) { + pc._disposeIceAndDtlsTransports(sdpMLineIndex); + pc.transceivers[sdpMLineIndex].iceGatherer = + pc.transceivers[0].iceGatherer; + pc.transceivers[sdpMLineIndex].iceTransport = + pc.transceivers[0].iceTransport; + pc.transceivers[sdpMLineIndex].dtlsTransport = + pc.transceivers[0].dtlsTransport; + if (pc.transceivers[sdpMLineIndex].rtpSender) { + pc.transceivers[sdpMLineIndex].rtpSender.setTransport( + pc.transceivers[0].dtlsTransport); + } + if (pc.transceivers[sdpMLineIndex].rtpReceiver) { + pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( + pc.transceivers[0].dtlsTransport); + } + } + if (description.type === 'offer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex] || + pc._createTransceiver(kind); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + usingBundle); + } + + if (cands.length && transceiver.iceTransport.state === 'new') { + if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { + transceiver.iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); + + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + + sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + var isNewTrack = false; + if (direction === 'sendrecv' || direction === 'sendonly') { + isNewTrack = !transceiver.rtpReceiver; + rtpReceiver = transceiver.rtpReceiver || + new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + + if (isNewTrack) { + var stream; + track = rtpReceiver.track; + // FIXME: does not work with Plan B. + if (remoteMsid && remoteMsid.stream === '-') { + // no-op. a stream id of '-' means: no associated stream. + } else if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + Object.defineProperty(streams[remoteMsid.stream], 'id', { + get: function() { + return remoteMsid.stream; + } + }); + } + Object.defineProperty(track, 'id', { + get: function() { + return remoteMsid.track; + } + }); + stream = streams[remoteMsid.stream]; + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + stream = streams.default; + } + if (stream) { + addTrackToStreamAndFireEvent(track, stream); + transceiver.associatedRemoteMediaStreams.push(stream); + } + receiverList.push([track, rtpReceiver, stream]); + } + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { + transceiver.associatedRemoteMediaStreams.forEach(function(s) { + var nativeTrack = s.getTracks().find(function(t) { + return t.id === transceiver.rtpReceiver.track.id; + }); + if (nativeTrack) { + removeTrackFromStreamAndFireEvent(nativeTrack, s); + } + }); + transceiver.associatedRemoteMediaStreams = []; + } + + transceiver.localCapabilities = localCapabilities; + transceiver.remoteCapabilities = remoteCapabilities; + transceiver.rtpReceiver = rtpReceiver; + transceiver.rtcpParameters = rtcpParameters; + transceiver.sendEncodingParameters = sendEncodingParameters; + transceiver.recvEncodingParameters = recvEncodingParameters; + + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + pc._transceive(pc.transceivers[sdpMLineIndex], + false, + isNewTrack); + } else if (description.type === 'answer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + pc.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + pc.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + + if (cands.length && iceTransport.state === 'new') { + if ((isIceLite || isComplete) && + (!usingBundle || sdpMLineIndex === 0)) { + iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + if (!usingBundle || sdpMLineIndex === 0) { + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + pc._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams.default); + receiverList.push([track, rtpReceiver, streams.default]); + } + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + if (this._dtlsRole === undefined) { + this._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + } + + this.remoteDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + Object.keys(streams).forEach(function(sid) { + var stream = streams[sid]; + if (stream.getTracks().length) { + if (pc.remoteStreams.indexOf(stream) === -1) { + pc.remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + window.setTimeout(function() { + pc._dispatchEvent('addstream', event); + }); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + if (stream.id !== item[2].id) { + return; + } + fireAddTrack(pc, track, receiver, [stream]); + }); + } + }); + receiverList.forEach(function(item) { + if (item[2]) { + return; + } + fireAddTrack(pc, item[0], item[1], []); + }); + + // check whether addIceCandidate({}) was called within four seconds after + // setRemoteDescription. + window.setTimeout(function() { + if (!(pc && pc.transceivers)) { + return; + } + pc.transceivers.forEach(function(transceiver) { + if (transceiver.iceTransport && + transceiver.iceTransport.state === 'new' && + transceiver.iceTransport.getRemoteCandidates().length > 0) { + console.warn('Timeout for addRemoteCandidate. Consider sending ' + + 'an end-of-candidates notification'); + transceiver.iceTransport.addRemoteCandidate({}); + } + }); + }, 4000); + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._isClosed = true; + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + RTCPeerConnection.prototype._updateSignalingState = function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this._dispatchEvent('signalingstatechange', event); + }; + + // Determine whether to fire the negotiationneeded event. + RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { + var pc = this; + if (this.signalingState !== 'stable' || this.needNegotiation === true) { + return; + } + this.needNegotiation = true; + window.setTimeout(function() { + if (pc.needNegotiation === false) { + return; + } + pc.needNegotiation = false; + var event = new Event('negotiationneeded'); + pc._dispatchEvent('negotiationneeded', event); + }, 0); + }; + + // Update the connection state. + RTCPeerConnection.prototype._updateConnectionState = function() { + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this._dispatchEvent('iceconnectionstatechange', event); + } + }; + + RTCPeerConnection.prototype.createOffer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createOffer after close')); + } + + var numAudioTracks = this.transceivers.filter(function(t) { + return t.kind === 'audio'; + }).length; + var numVideoTracks = this.transceivers.filter(function(t) { + return t.kind === 'video'; + }).length; + + // Determine number of audio and video tracks we need to send/recv. + var offerOptions = arguments[0]; + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + if (offerOptions.offerToReceiveAudio === true) { + numAudioTracks = 1; + } else if (offerOptions.offerToReceiveAudio === false) { + numAudioTracks = 0; + } else { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + } + if (offerOptions.offerToReceiveVideo !== undefined) { + if (offerOptions.offerToReceiveVideo === true) { + numVideoTracks = 1; + } else if (offerOptions.offerToReceiveVideo === false) { + numVideoTracks = 0; + } else { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + + this.transceivers.forEach(function(transceiver) { + if (transceiver.kind === 'audio') { + numAudioTracks--; + if (numAudioTracks < 0) { + transceiver.wantReceive = false; + } + } else if (transceiver.kind === 'video') { + numVideoTracks--; + if (numVideoTracks < 0) { + transceiver.wantReceive = false; + } + } + }); + + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + this._createTransceiver('audio'); + numAudioTracks--; + } + if (numVideoTracks > 0) { + this._createTransceiver('video'); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = transceiver.track; + var kind = transceiver.kind; + var mid = SDPUtils.generateIdentifier(); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + pc.usingBundle); + } + + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + localCapabilities.codecs.forEach(function(codec) { + // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 + // by adding level-asymmetry-allowed=1 + if (codec.name === 'H264' && + codec.parameters['level-asymmetry-allowed'] === undefined) { + codec.parameters['level-asymmetry-allowed'] = '1'; + } + }); + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + // add RTX + if (edgeVersion >= 15019 && kind === 'video' && + !sendEncodingParameters[0].rtx) { + sendEncodingParameters[0].rtx = { + ssrc: sendEncodingParameters[0].ssrc + 1 + }; + } + } + + if (transceiver.wantReceive) { + transceiver.rtpReceiver = new window.RTCRtpReceiver( + transceiver.dtlsTransport, kind); + } + + transceiver.localCapabilities = localCapabilities; + transceiver.sendEncodingParameters = sendEncodingParameters; + }); + + // always offer BUNDLE and dispose on return if not supported. + if (this._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + sdp += 'a=ice-options:trickle\r\n'; + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + sdp += writeMediaSection(transceiver, transceiver.localCapabilities, + 'offer', transceiver.stream, pc._dtlsRole); + sdp += 'a=rtcp-rsize\r\n'; + + if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !pc.usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { + cand.component = 1; + sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'completed') { + sdp += 'a=end-of-candidates\r\n'; + } + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.createAnswer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createAnswer after close')); + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + var mediaSectionsInOffer = SDPUtils.splitSections( + this.remoteDescription.sdp).length - 1; + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + if (sdpMLineIndex + 1 > mediaSectionsInOffer) { + return; + } + if (transceiver.isDatachannel) { + sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=mid:' + transceiver.mid + '\r\n'; + return; + } + + // FIXME: look at direction. + if (transceiver.stream) { + var localTrack; + if (transceiver.kind === 'audio') { + localTrack = transceiver.stream.getAudioTracks()[0]; + } else if (transceiver.kind === 'video') { + localTrack = transceiver.stream.getVideoTracks()[0]; + } + if (localTrack) { + // add RTX + if (edgeVersion >= 15019 && transceiver.kind === 'video' && + !transceiver.sendEncodingParameters[0].rtx) { + transceiver.sendEncodingParameters[0].rtx = { + ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 + }; + } + } + } + + // Calculate intersection of capabilities. + var commonCapabilities = getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + var hasRtx = commonCapabilities.codecs.filter(function(c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + sdp += writeMediaSection(transceiver, commonCapabilities, + 'answer', transceiver.stream, pc._dtlsRole); + if (transceiver.rtcpParameters && + transceiver.rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var sections; + if (!candidate || candidate.candidate === '') { + for (var j = 0; j < this.transceivers.length; j++) { + if (this.transceivers[j].isDatachannel) { + continue; + } + this.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[j + 1] += 'a=end-of-candidates\r\n'; + this.remoteDescription.sdp = sections.join(''); + if (this.usingBundle) { + break; + } + } + } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { + throw new TypeError('sdpMLineIndex or sdpMid required'); + } else if (!this.remoteDescription) { + return Promise.reject(makeError('InvalidStateError', + 'Can not add ICE candidate without a remote description')); + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[sdpMLineIndex]; + if (transceiver) { + if (transceiver.isDatachannel) { + return Promise.resolve(); + } + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return Promise.resolve(); + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component && cand.component !== 1) { + return Promise.resolve(); + } + // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && + transceiver.iceTransport !== this.transceivers[0].iceTransport)) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + + // update the remoteDescription. + var candidateString = candidate.candidate.trim(); + if (candidateString.indexOf('a=') === 0) { + candidateString = candidateString.substr(2); + } + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[sdpMLineIndex + 1] += 'a=' + + (cand.type ? candidateString : 'end-of-candidates') + + '\r\n'; + this.remoteDescription.sdp = sections.join(''); + } else { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var fixStatsType = function(stat) { + return { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[stat.type] || stat.type; + }; + return new Promise(function(resolve) { + // shim getStats with maplike support + var results = new Map(); + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + result[id].type = fixStatsType(result[id]); + results.set(id, result[id]); + }); + }); + resolve(results); + }); + }); + }; + + // legacy callback shims. Should be moved to adapter.js some days. + var methods = ['createOffer', 'createAnswer']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[0] === 'function' || + typeof args[1] === 'function') { // legacy + return nativeMethod.apply(this, [arguments[2]]) + .then(function(description) { + if (typeof args[0] === 'function') { + args[0].apply(null, [description]); + } + }, function(error) { + if (typeof args[1] === 'function') { + args[1].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function' || + typeof args[2] === 'function') { // legacy + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }, function(error) { + if (typeof args[2] === 'function') { + args[2].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // getStats is special. It doesn't have a spec legacy method yet we support + // getStats(something, cb) without error callbacks. + ['getStats'].forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function') { + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + return RTCPeerConnection; +}; + +},{"sdp":2}],2:[function(require,module,exports){ + /* eslint-env node */ +'use strict'; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random().toString(36).substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob.trim().split('\n').map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split('\nm='); + return parts.map(function(part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + if (candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.ufrag); + } + return 'candidate:' + sdp.join(' '); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(' '); +} + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== 'sendrecv' + ? '/' + headerExtension.direction + : '') + + ' ' + headerExtension.uri + '\r\n'; +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + params.push(param + '=' + codec.parameters[param]); + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + if (mid) { + return mid.substr(6); + } +} + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1] + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, + 'a=fingerprint:'); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; +}; +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.split(' '); + parts.shift(); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + ssrc: secondarySsrc + } + }; + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: secondarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 + - (50 * 40 * 8); + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return {stream: parts[0], track: parts[1]}; + } + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'msid'; + }); + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return {stream: parts[0], track: parts[1]}; + } +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.random().toString().substr(2, 21); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.direction) { + sdp += 'a=' + transceiver.direction + '\r\n'; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + // spec. + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + + // for Chrome. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + + transceiver.sendEncodingParameters[0].ssrc + ' ' + + transceiver.sendEncodingParameters[0].rtx.ssrc + + '\r\n'; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return { + kind: mline[0].substr(2), + port: parseInt(mline[1], 10), + protocol: mline[2], + fmt: mline.slice(3).join(' ') + }; +}; + +// Expose public methods. +if (typeof module === 'object') { + module.exports = SDPUtils; +} + +},{}],3:[function(require,module,exports){ +(function (global){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var adapterFactory = require('./adapter_factory.js'); +module.exports = adapterFactory({window: global.window}); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./adapter_factory.js":4}],4:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var utils = require('./utils'); +// Shimming starts here. +module.exports = function(dependencies, opts) { + var window = dependencies && dependencies.window; + + var options = { + shimChrome: true, + shimFirefox: true, + shimEdge: true, + shimSafari: true, + }; + + for (var key in opts) { + if (hasOwnProperty.call(opts, key)) { + options[key] = opts[key]; + } + } + + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + var commonShim = require('./common_shim') || null; + + // Export to the adapter global object visible in the browser. + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || + !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = chromeShim; + commonShim.shimCreateObjectURL(window); + + chromeShim.shimGetUserMedia(window); + chromeShim.shimMediaStream(window); + chromeShim.shimSourceObject(window); + chromeShim.shimPeerConnection(window); + chromeShim.shimOnTrack(window); + chromeShim.shimAddTrackRemoveTrack(window); + chromeShim.shimGetSendersWithDtmf(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || + !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = firefoxShim; + commonShim.shimCreateObjectURL(window); + + firefoxShim.shimGetUserMedia(window); + firefoxShim.shimSourceObject(window); + firefoxShim.shimPeerConnection(window); + firefoxShim.shimOnTrack(window); + firefoxShim.shimRemoveStream(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { + logging('MS edge shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = edgeShim; + commonShim.shimCreateObjectURL(window); + + edgeShim.shimGetUserMedia(window); + edgeShim.shimPeerConnection(window); + edgeShim.shimReplaceTrack(window); + + // the edge shim implements the full RTCIceCandidate object. + + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = safariShim; + commonShim.shimCreateObjectURL(window); + + safariShim.shimRTCIceServerUrls(window); + safariShim.shimCallbacksAPI(window); + safariShim.shimLocalStreamsAPI(window); + safariShim.shimRemoteStreamsAPI(window); + safariShim.shimTrackEventTransceiver(window); + safariShim.shimGetUserMedia(window); + safariShim.shimCreateOfferLegacy(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + default: + logging('Unsupported browser!'); + break; + } + + return adapter; +}; + +},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],5:[function(require,module,exports){ + +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimMediaStream: function(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + this.addEventListener('track', this._ontrack = f); + } + }); + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + if (!pc._ontrackpoly) { + pc._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = {track: te.track}; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = {track: track}; + } + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + }; + pc.addEventListener('addstream', pc._ontrackpoly); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } else if (!('RTCRtpTransceiver' in window)) { + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + if (!e.transceiver) { + e.transceiver = {receiver: e.receiver}; + } + return e; + }); + } + }, + + shimGetSendersWithDtmf: function(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if (typeof window === 'object' && window.RTCPeerConnection && + !('getSenders' in window.RTCPeerConnection.prototype) && + 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function(pc, track) { + return { + track: track, + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + }, + _pc: pc + }; + }; + + // augment addTrack when getSenders is not available. + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + var sender = origAddTrack.apply(pc, arguments); + if (!sender) { + sender = shimSenderWithDtmf(pc, track); + pc._senders.push(sender); + } + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + origRemoveTrack.apply(pc, arguments); + var idx = pc._senders.indexOf(sender); + if (idx !== -1) { + pc._senders.splice(idx, 1); + } + }; + } + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origAddStream.apply(pc, [stream]); + stream.getTracks().forEach(function(track) { + pc._senders.push(shimSenderWithDtmf(pc, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origRemoveStream.apply(pc, [stream]); + + stream.getTracks().forEach(function(track) { + var sender = pc._senders.find(function(s) { + return s.track === track; + }); + if (sender) { + pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender + } + }); + }; + } else if (typeof window === 'object' && window.RTCPeerConnection && + 'getSenders' in window.RTCPeerConnection.prototype && + 'createDTMFSender' in window.RTCPeerConnection.prototype && + window.RTCRtpSender && + !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + }, + + shimSourceObject: function(window) { + var URL = window && window.URL; + + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return undefined; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimAddTrackRemoveTrackWithNative: function(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function(streamId) { + return pc._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + var sender = origAddTrack.apply(this, arguments); + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + var existingSenders = pc.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = pc.getSenders().filter(function(newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function(streamId) { + var idx = pc._shimmedLocalStreams[streamId].indexOf(sender); + if (idx !== -1) { + pc._shimmedLocalStreams[streamId].splice(idx, 1); + } + if (pc._shimmedLocalStreams[streamId].length === 1) { + delete pc._shimmedLocalStreams[streamId]; + } + }); + } + return origRemoveTrack.apply(this, arguments); + }; + }, + + shimAddTrackRemoveTrack: function(window) { + var browserDetails = utils.detectBrowser(window); + // shim addTrack and removeTrack. + if (window.RTCPeerConnection.prototype.addTrack && + browserDetails.version >= 65) { + return this.shimAddTrackRemoveTrackWithNative(window); + } + + // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + var origGetLocalStreams = window.RTCPeerConnection.prototype + .getLocalStreams; + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + var nativeStreams = origGetLocalStreams.apply(this); + pc._reverseStreams = pc._reverseStreams || {}; + return nativeStreams.map(function(stream) { + return pc._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + if (!pc._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + stream = newStream; + } + origAddStream.apply(pc, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]); + delete pc._reverseStreams[(pc._streams[stream.id] ? + pc._streams[stream.id].id : stream.id)]; + delete pc._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + var streams = [].slice.call(arguments, 1); + if (streams.length !== 1 || + !streams[0].getTracks().find(function(t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException( + 'The adapter.js addTrack polyfill only supports a single ' + + ' stream which is associated with the specified track.', + 'NotSupportedError'); + } + + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + var oldStream = pc._streams[stream.id]; + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); + + // Trigger ONN async. + Promise.resolve().then(function() { + pc.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + pc.addStream(newStream); + } + return pc.getSenders().find(function(s) { + return s.track === track; + }); + }; + + // replace the internal stream id with the external one and + // vice versa. + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), + externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), + internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + var args = arguments; + var isLegacyCall = arguments.length && + typeof arguments[0] === 'function'; + if (isLegacyCall) { + return nativeMethod.apply(pc, [ + function(description) { + var desc = replaceInternalStreamId(pc, description); + args[0].apply(null, [desc]); + }, + function(err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2] + ]); + } + return nativeMethod.apply(pc, arguments) + .then(function(description) { + return replaceInternalStreamId(pc, description); + }); + }; + }); + + var origSetLocalDescription = + window.RTCPeerConnection.prototype.setLocalDescription; + window.RTCPeerConnection.prototype.setLocalDescription = function() { + var pc = this; + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(pc, arguments); + } + arguments[0] = replaceExternalStreamId(pc, arguments[0]); + return origSetLocalDescription.apply(pc, arguments); + }; + + // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + var origLocalDescription = Object.getOwnPropertyDescriptor( + window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, + 'localDescription', { + get: function() { + var pc = this; + var description = origLocalDescription.get.apply(this); + if (description.type === '') { + return description; + } + return replaceInternalStreamId(pc, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + var isLocal = sender._pc === pc; + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', + 'InvalidAccessError'); + } + + // Search for the native stream the senders track belongs to. + pc._streams = pc._streams || {}; + var stream; + Object.keys(pc._streams).forEach(function(streamid) { + var hasTrack = pc._streams[streamid].getTracks().find(function(track) { + return sender.track === track; + }); + if (hasTrack) { + stream = pc._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + pc.removeStream(pc._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + pc.dispatchEvent(new Event('negotiationneeded')); + } + }; + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + // The RTCPeerConnection object. + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + // this was fixed in M56 along with unprefixing RTCPeerConnection. + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + return new window.webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.webkitRTCPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if (window.webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.webkitRTCPeerConnection.generateCertificate; + } + }); + } + } else { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function(selector, + successCallback, errorCallback) { + var pc = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } + + // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + if (origGetStats.length === 0 && (arguments.length === 0 || + typeof arguments[0] !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + return new Map(Object.keys(stats).map(function(key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + origGetStats.apply(pc, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(successCallback, errorCallback); + }; + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var args = arguments; + var pc = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // promise support for createOffer and createAnswer. Available (without + // bugs) since M52: crbug/619289 + if (browserDetails.version < 52) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + } +}; + +},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && typeof constraints.audio === 'object') { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode && + !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches; + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function(d) { + return matches.some(function(match) { + return d.label.toLowerCase().indexOf(match) !== -1; + }); + }); + if (!dev && devices.length && matches.indexOf('back') !== -1) { + dev = devices[devices.length - 1]; // more likely the back cam + } + if (dev) { + constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : + {ideal: dev.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + InvalidStateError: 'NotReadableError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotReadableError', + MediaDeviceKillSwitchOn: 'NotReadableError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return window.MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }, + getSupportedConstraints: function() { + return { + deviceId: true, echoCancellation: true, facingMode: true, + frameRate: true, height: true, width: true + }; + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).then(function(stream) { + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } +}; + +},{"../utils.js":13}],7:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var SDPUtils = require('sdp'); +var utils = require('./utils'); + +module.exports = { + shimRTCIceCandidate: function(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (window.RTCIceCandidate && 'foundation' in + window.RTCIceCandidate.prototype) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + window.RTCIceCandidate = function(args) { + // Remove the a= which shouldn't be part of the candidate string. + if (typeof args === 'object' && args.candidate && + args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = SDPUtils.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, + parsedCandidate); + + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment, + }; + }; + return augmentedCandidate; + }; + + // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + return e; + }); + }, + + // shimCreateObjectURL must be called before shimSourceObject to avoid loop. + + shimCreateObjectURL: function(window) { + var URL = window && window.URL; + + if (!(typeof window === 'object' && window.HTMLMediaElement && + 'srcObject' in window.HTMLMediaElement.prototype && + URL.createObjectURL && URL.revokeObjectURL)) { + // Only shim CreateObjectURL using srcObject if srcObject exists. + return undefined; + } + + var nativeCreateObjectURL = URL.createObjectURL.bind(URL); + var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL); + var streams = new Map(), newId = 0; + + URL.createObjectURL = function(stream) { + if ('getTracks' in stream) { + var url = 'polyblob:' + (++newId); + streams.set(url, stream); + utils.deprecated('URL.createObjectURL(stream)', + 'elem.srcObject = stream'); + return url; + } + return nativeCreateObjectURL(stream); + }; + URL.revokeObjectURL = function(url) { + nativeRevokeObjectURL(url); + streams.delete(url); + }; + + var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype, + 'src'); + Object.defineProperty(window.HTMLMediaElement.prototype, 'src', { + get: function() { + return dsc.get.apply(this); + }, + set: function(url) { + this.srcObject = streams.get(url) || null; + return dsc.set.apply(this, [url]); + } + }); + + var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute; + window.HTMLMediaElement.prototype.setAttribute = function() { + if (arguments.length === 2 && + ('' + arguments[0]).toLowerCase() === 'src') { + this.srcObject = streams.get(arguments[1]) || null; + } + return nativeSetAttribute.apply(this, arguments); + }; + }, + + shimMaxMessageSize: function(window) { + if (window.RTCSctpTransport || !window.RTCPeerConnection) { + return; + } + var browserDetails = utils.detectBrowser(window); + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function(description) { + var sections = SDPUtils.splitSections(description.sdp); + sections.shift(); + return sections.some(function(mediaSection) { + var mLine = SDPUtils.parseMLine(mediaSection); + return mLine && mLine.kind === 'application' + && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + if (match === null || match.length < 2) { + return -1; + } + var version = parseInt(match[1], 10); + // Test for NaN (yes, this is ugly) + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = + browserDetails.version === 57 ? 65535 : 65536; + } + } + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; + + // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + if (browserDetails.browser === 'firefox' + && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && + remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + return maxMessageSize; + }; + + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + pc._sctp = null; + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); + + // Get the maximum message size the local peer is capable of sending + var canSendMMS = getCanSendMaxMessageSize(isFirefox); + + // Get the maximum message size of the remote peer. + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); + + // Determine final maximum message size + var maxMessageSize; + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } + + // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function() { + return maxMessageSize; + } + }); + pc._sctp = sctp; + } + + return origSetRemoteDescription.apply(pc, arguments); + }; + }, + + shimSendThrowTypeError: function(window) { + // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + var origCreateDataChannel = + window.RTCPeerConnection.prototype.createDataChannel; + window.RTCPeerConnection.prototype.createDataChannel = function() { + var pc = this; + var dataChannel = origCreateDataChannel.apply(pc, arguments); + var origDataChannelSend = dataChannel.send; + + // Patch 'send' method + dataChannel.send = function() { + var dc = this; + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + if (length > pc.sctp.maxMessageSize) { + throw new DOMException('Message too large (can send a maximum of ' + + pc.sctp.maxMessageSize + ' bytes)', 'TypeError'); + } + return origDataChannelSend.apply(dc, arguments); + }; + + return dataChannel; + }; + } +}; + +},{"./utils":13,"sdp":2}],8:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var shimRTCPeerConnection = require('rtcpeerconnection-shim'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + // this adds an additional event listener to MediaStrackTrack that signals + // when a tracks enabled property was changed. Workaround for a bug in + // addStream, see below. No longer required in 15025+ + if (browserDetails.version < 15025) { + var origMSTEnabled = Object.getOwnPropertyDescriptor( + window.MediaStreamTrack.prototype, 'enabled'); + Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { + set: function(value) { + origMSTEnabled.set.call(this, value); + var ev = new Event('enabled'); + ev.enabled = value; + this.dispatchEvent(ev); + } + }); + } + } + + // ORTC defines the DTMF sender a bit different. + // https://github.com/w3c/ortc/issues/714 + if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = new window.RTCDtmfSender(this); + } else if (this.track.kind === 'video') { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + + window.RTCPeerConnection = + shimRTCPeerConnection(window, browserDetails.version); + }, + shimReplaceTrack: function(window) { + // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 + if (window.RTCRtpSender && + !('replaceTrack' in window.RTCRtpSender.prototype)) { + window.RTCRtpSender.prototype.replaceTrack = + window.RTCRtpSender.prototype.setTrack; + } + } +}; + +},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +// Expose public methods. +module.exports = function(window) { + var navigator = window && window.navigator; + + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; +}; + +},{}],10:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.transceiver = {receiver: event.receiver}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + if (typeof window === 'object' && window.RTCTrackEvent && + ('receiver' in window.RTCTrackEvent.prototype) && + !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimSourceObject: function(window) { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new window.mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (window.mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = window.mozRTCSessionDescription; + window.RTCIceCandidate = window.mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function( + selector, + onSucc, + onErr + ) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + if (browserDetails.version < 48) { + stats = makeMapStats(stats); + } + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function(stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + stats.forEach(function(stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + return stats; + }) + .then(onSucc, onErr); + }; + }, + + shimRemoveStream: function(window) { + if (!window.RTCPeerConnection || + 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function(sender) { + if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } +}; + +},{"../utils":13,"./getusermedia":11}],11:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + var shimError_ = function(e) { + return { + name: { + InternalError: 'NotReadableError', + NotSupportedError: 'TypeError', + PermissionDeniedError: 'NotAllowedError', + SecurityError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).then(function(stream) { + // Work around https://bugzil.la/802326 + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('The object can not be found here.', + 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + if (!(browserDetails.version > 55 && + 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + if (typeof c === 'object' && typeof c.audio === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + MediaStreamTrack.prototype.getSettings = function() { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + MediaStreamTrack.prototype.applyConstraints = function(c) { + if (this.kind === 'audio' && typeof c === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeApplyConstraints.apply(this, [c]); + }; + } + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; +}; + +},{"../utils":13}],12:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +'use strict'; +var utils = require('../utils'); + +module.exports = { + shimLocalStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function() { + if (!this._localStreams) { + this._localStreams = []; + } + return this._localStreams; + }; + } + if (!('getStreamById' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getStreamById = function(id) { + var result = null; + if (this._localStreams) { + this._localStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + if (this._remoteStreams) { + this._remoteStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + return result; + }; + } + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + var pc = this; + stream.getTracks().forEach(function(track) { + _addTrack.call(pc, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (stream) { + if (!this._localStreams) { + this._localStreams = [stream]; + } else if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + } + return _addTrack.call(this, track, stream); + }; + } + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + var index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } + this._localStreams.splice(index, 1); + var pc = this; + var tracks = stream.getTracks(); + this.getSenders().forEach(function(sender) { + if (tracks.indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } + }, + shimRemoteStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function() { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function() { + return this._onaddstream; + }, + set: function(f) { + var pc = this; + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); + } + this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function(e) { + e.streams.forEach(function(stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + pc._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + }); + } + }, + shimCallbacksAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + var prototype = window.RTCPeerConnection.prototype; + var createOffer = prototype.createOffer; + var createAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createOffer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createAnswer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setLocalDescription = withCallback; + + withCallback = function(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setRemoteDescription = withCallback; + + withCallback = function(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.addIceCandidate = withCallback; + }, + shimGetUserMedia: function(window) { + var navigator = window && window.navigator; + + if (!navigator.getUserMedia) { + if (navigator.webkitGetUserMedia) { + navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + } else if (navigator.mediaDevices && + navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function(constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints) + .then(cb, errcb); + }.bind(navigator); + } + } + }, + shimRTCIceServerUrls: function(window) { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if ('generateCertificate' in window.RTCPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + }, + shimTrackEventTransceiver: function(window) { + // Add event.transceiver member over deprecated event.receiver + if (typeof window === 'object' && window.RTCPeerConnection && + ('receiver' in window.RTCTrackEvent.prototype) && + // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is + // defined for some reason even when window.RTCTransceiver is not. + !window.RTCTransceiver) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimCreateOfferLegacy: function(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function(offerOptions) { + var pc = this; + if (offerOptions) { + var audioTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'audio'; + }); + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && + !audioTransceiver) { + pc.addTransceiver('audio'); + } + + var videoTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'video'; + }); + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + videoTransceiver.setDirection('sendonly'); + } else if (videoTransceiver.direction === 'recvonly') { + videoTransceiver.setDirection('inactive'); + } + } else if (offerOptions.offerToReceiveVideo === true && + !videoTransceiver) { + pc.addTransceiver('video'); + } + } + return origCreateOffer.apply(pc, arguments); + }; + } +}; + +},{"../utils":13}],13:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var logDisabled_ = true; +var deprecationWarnings_ = true; + +/** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ +function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); +} + +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object. +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function(e) { + cb(wrapper(e)); + }; + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, + wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap + || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, + unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function() { + return this['_on' + eventNameToWrap]; + }, + set: function(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, + this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, + this['_on' + eventNameToWrap] = cb); + } + } + }); +} + +// Utility methods. +module.exports = { + extractVersion: extractVersion, + wrapPeerConnectionEvent: wrapPeerConnectionEvent, + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + /** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + disableWarnings: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + deprecated: function(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + + ' instead.'); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function(window) { + var navigator = window && window.navigator; + + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + if (navigator.mozGetUserMedia) { // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, + /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, + /Chrom(e|ium)\/(\d+)\./, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. + result.browser = 'edge'; + result.version = extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, + /AppleWebKit\/(\d+)\./, 1); + } else { // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } +}; + +},{}]},{},[3])(3) +}); \ No newline at end of file diff --git a/html5/verto/video_demo-live_canvas/js/verto-min.js b/html5/verto/video_demo-live_canvas/js/verto-min.js index 78f8bc450e..2ac8e89e7e 100644 --- a/html5/verto/video_demo-live_canvas/js/verto-min.js +++ b/html5/verto/video_demo-live_canvas/js/verto-min.js @@ -1,630 +1,9508 @@ +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.FSRTC.js - WebRTC Glue code + * + */ -(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=0;if(iOS){self.options.useVideo.setAttribute("playsinline",true);self.options.useVideo.setAttribute("controls",true);}} -var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);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.stopPeer=function(){if(self.peer){console.log("stopping peer");self.peer.stop();}} -$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';self.options.useVideo['src']='';} -if(self.localStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}} -self.localStream=null;} -if(self.options.localVideo){self.options.localVideo.style.display='none';self.options.localVideo['src']='';} -if(self.options.localVideoStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}} -if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.getMute=function(){var self=this;return self.audioEnabled;} -$.FSRTC.prototype.setMute=function(what){var self=this;var audioTracks=self.localStream.getAudioTracks();for(var i=0,len=audioTracks.length;i=w&&$.FSRTC.validRes[i][1]>=h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}} -return[w,h];} -var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} -var video={} -if(cam){video.deviceId={exact:cam};} -w=resList[resI][0];h=resList[resI][1];resI++;video={width:{exact:w},height:{exact:h}};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.warn(w+"x"+h+" not supported.");checkRes(cam,func);}});} -$.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");} -return func?func(cache):null;} -$.FSRTC.validRes=[];resI=0;checkRes(cam,func);} -$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);} -console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i1){return false;} -return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);} -if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;} -self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;} -if(self.options.onWSClose){self.options.onWSClose(self);} -console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);} -self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);} -var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}} -return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to) -clearTimeout(self.to);} -$.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;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);this.speedCB(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});this.speedCB=null;} -return;} -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,loginParams:self.options.loginParams,userVariables:self.options.userVariables},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 i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i0){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);dialog.setState($.verto.enum.state.recovering);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.punt':verto.purge();verto.logout();break;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&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else 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.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);console.debug("CLIENT READY",data.params);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=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i":"\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;} -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.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",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);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;} -return data;} -function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);} -return data;} -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;}} -if(config.onChange){config.onChange(obj,args);} -try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;} -dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;} -if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));} -dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;} -dt.fnUpdate(genRow(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(genArray(obj));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;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);} -if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);} -if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';} -if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};} -$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="

"+""+""+""+""+ -(confMan.params.hasVid?"":"")+"

";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j
"+"Video Layout Canvas "+(j+1)+" "+"

";jq.append(vlhtml);} -$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});} -$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});} -function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="
";html+="General Controls
";html+=""+""+""+""+""+""+"";if(confMan.params.hasVid){html+="

Video Controls
";html+=""+""+""+"";if(confMan.canvasCount>1){html+="

Canvas Controls
"+""+""+""+"
"+""+""+"";} -html+="
"+""+""+""+"
";} -jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();} -jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});} -$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;} -var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready

");}else{$(confMan.params.mainModID).html("");} -verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);} -if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;jb)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}} -if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"

");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;} -confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready

");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}} -var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};} -confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();} -if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);} -if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);} -if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;} -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;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useMic=dialog.params.useMic;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();} -if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();} -if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}} -if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);} -dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_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){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} -if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{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.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);} -console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();} -console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});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;} -if((obj.noDialogParams&&i!="callID")){continue;} -obj.dialogParams[i]=dialog.params[i];} -delete obj.noDialogParams;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;} -function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}} -return id;} -$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;} -if(callback){callback(false,null,arg);} -console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}} -$.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.log("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.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);} -break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;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:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();} -delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();} -break;} -return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;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(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;} -if(params.cause){dialog.cause=params.cause;}} -if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";} -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,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.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.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;} -pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;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){params={};} -params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);} -dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;} -if(params.useMic){dialog.useMic=params.useMic;} -if(params.useSpeak){dialog.useSpeak=params.useSpeak;}} -dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;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{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" <":" <")+dialog.params.remote_caller_id_number+(enc?">":">");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);};$.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(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;} -dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.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,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,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 recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();} -if($.verto.haltClosure) -return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}} -return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}} -$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});} -if(runtime){runtime(true);}} -function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);} -function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}} -navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});} -navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.verto.refreshDevices=function(runtime){checkDevices(runtime);} -$.verto.init=function(obj,runtime){if(!obj){obj={};} -if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}} -$.verto.genUUID=function(){return generateGUID();}})(jQuery);(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=14393&&url.indexOf('?transport=udp')===-1;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});} -function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i0;i--){this._iceGatherers=new window.RTCIceGatherer({iceServers:config.iceServers,gatherPolicy:config.iceTransportPolicy});}}else{config.iceCandidatePoolSize=0;} -this._config=config;this.transceivers=[];this._sdpSessionId=SDPUtils.generateSessionId();this._sdpSessionVersion=0;this._dtlsRole=undefined;this._isClosed=false;};RTCPeerConnection.prototype.onicecandidate=null;RTCPeerConnection.prototype.onaddstream=null;RTCPeerConnection.prototype.ontrack=null;RTCPeerConnection.prototype.onremovestream=null;RTCPeerConnection.prototype.onsignalingstatechange=null;RTCPeerConnection.prototype.oniceconnectionstatechange=null;RTCPeerConnection.prototype.onicegatheringstatechange=null;RTCPeerConnection.prototype.onnegotiationneeded=null;RTCPeerConnection.prototype.ondatachannel=null;RTCPeerConnection.prototype._dispatchEvent=function(name,event){if(this._isClosed){return;} -this.dispatchEvent(event);if(typeof this['on'+name]==='function'){this['on'+name](event);}};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this._dispatchEvent('icegatheringstatechange',event);};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype.getLocalStreams=function(){return this.localStreams;};RTCPeerConnection.prototype.getRemoteStreams=function(){return this.remoteStreams;};RTCPeerConnection.prototype._createTransceiver=function(kind){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,associatedRemoteMediaStreams:[],wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;} -this.transceivers.push(transceiver);return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){var alreadyExists=this.transceivers.find(function(s){return s.track===track;});if(alreadyExists){throw makeError('InvalidAccessError','Track already exists.');} -if(this.signalingState==='closed'){throw makeError('InvalidStateError','Attempted to call addTrack on a closed peerconnection.');} -var transceiver;for(var i=0;i=15025){stream.getTracks().forEach(function(track){pc.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){pc.addTrack(track,clonedStream);});}};RTCPeerConnection.prototype.removeTrack=function(sender){if(!(sender instanceof window.RTCRtpSender)){throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.');} -var transceiver=this.transceivers.find(function(t){return t.rtpSender===sender;});if(!transceiver){throw makeError('InvalidAccessError','Sender was not created by this connection.');} -var stream=transceiver.stream;transceiver.rtpSender.stop();transceiver.rtpSender=null;transceiver.track=null;transceiver.stream=null;var localStreams=this.transceivers.map(function(t){return t.stream;});if(localStreams.indexOf(stream)===-1&&this.localStreams.indexOf(stream)>-1){this.localStreams.splice(this.localStreams.indexOf(stream),1);} -this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;stream.getTracks().forEach(function(track){var sender=pc.getSenders().find(function(s){return s.track===track;});if(sender){pc.removeTrack(sender);}});};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(sdpMLineIndex,usingBundle){var pc=this;if(usingBundle&&sdpMLineIndex>0){return this.transceivers[0].iceGatherer;}else if(this._iceGatherers.length){return this._iceGatherers.shift();} -var iceGatherer=new window.RTCIceGatherer({iceServers:this._config.iceServers,gatherPolicy:this._config.iceTransportPolicy});Object.defineProperty(iceGatherer,'state',{value:'new',writable:true});this.transceivers[sdpMLineIndex].candidates=[];this.transceivers[sdpMLineIndex].bufferCandidates=function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;iceGatherer.state=end?'completed':'gathering';if(pc.transceivers[sdpMLineIndex].candidates!==null){pc.transceivers[sdpMLineIndex].candidates.push(event.candidate);}};iceGatherer.addEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);return iceGatherer;};RTCPeerConnection.prototype._gather=function(mid,sdpMLineIndex){var pc=this;var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer.onlocalcandidate){return;} -var candidates=this.transceivers[sdpMLineIndex].candidates;this.transceivers[sdpMLineIndex].candidates=null;iceGatherer.removeEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);iceGatherer.onlocalcandidate=function(evt){if(pc.usingBundle&&sdpMLineIndex>0){return;} -var event=new Event('icecandidate');event.candidate={sdpMid:mid,sdpMLineIndex:sdpMLineIndex};var cand=evt.candidate;var end=!cand||Object.keys(cand).length===0;if(end){if(iceGatherer.state==='new'||iceGatherer.state==='gathering'){iceGatherer.state='completed';}}else{if(iceGatherer.state==='new'){iceGatherer.state='gathering';} -cand.component=1;event.candidate.candidate=SDPUtils.writeCandidate(cand);} -var sections=SDPUtils.splitSections(pc.localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex+1]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex+1]+='a=end-of-candidates\r\n';} -pc.localDescription.sdp=sections.join('');var complete=pc.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(pc.iceGatheringState!=='gathering'){pc.iceGatheringState='gathering';pc._emitGatheringStateChange();} -if(!end){pc._dispatchEvent('icecandidate',event);} -if(complete){pc._dispatchEvent('icecandidate',new Event('icecandidate'));pc.iceGatheringState='complete';pc._emitGatheringStateChange();}};window.setTimeout(function(){candidates.forEach(function(candidate){var e=new Event('RTCIceGatherEvent');e.candidate=candidate;iceGatherer.onlocalcandidate(e);});},0);};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var pc=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){pc._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){pc._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});pc._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;} -var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;} -var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlsstatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;} -transceiver.rtpSender.send(params);} -if(recv&&transceiver.rtpReceiver&¶ms.codecs.length>0){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});} -if(transceiver.recvEncodingParameters.length){params.encodings=transceiver.recvEncodingParameters;} -params.rtcp={compound:transceiver.rtcpParameters.compound};if(transceiver.rtcpParameters.cname){params.rtcp.cname=transceiver.rtcpParameters.cname;} -if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;} -transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var pc=this;if(!isActionAllowedInSignalingState('setLocalDescription',description.type,this.signalingState)||this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set local '+description.type+' in state '+pc.signalingState));} -var sections;var sessionpart;if(description.type==='offer'){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);pc.transceivers[sdpMLineIndex].localCapabilities=caps;});this.transceivers.forEach(function(transceiver,sdpMLineIndex){pc._gather(transceiver.mid,sdpMLineIndex);});}else if(description.type==='answer'){sections=SDPUtils.splitSections(pc.remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=pc.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;if(!rejected&&!transceiver.isDatachannel){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';} -if(!pc.usingBundle||sdpMLineIndex===0){pc._gather(transceiver.mid,sdpMLineIndex);if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');} -if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}} -var params=getCommonCapabilities(localCapabilities,remoteCapabilities);pc._transceive(transceiver,params.codecs.length>0,false);}});} -this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -return Promise.resolve();};RTCPeerConnection.prototype.setRemoteDescription=function(description){var pc=this;if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,this.signalingState)||this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set remote '+description.type+' in state '+pc.signalingState));} -var streams={};this.remoteStreams.forEach(function(stream){streams[stream.id]=stream;});var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;this.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){this.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{this.canTrickleIceCandidates=false;} -sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(kind==='application'&&protocol==='DTLS/SCTP'){pc.transceivers[sdpMLineIndex]={mid:mid,isDatachannel:true};return;} -var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';} -recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component===1;});if((description.type==='offer'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&pc.transceivers[sdpMLineIndex]){pc._disposeIceAndDtlsTransports(sdpMLineIndex);pc.transceivers[sdpMLineIndex].iceGatherer=pc.transceivers[0].iceGatherer;pc.transceivers[sdpMLineIndex].iceTransport=pc.transceivers[0].iceTransport;pc.transceivers[sdpMLineIndex].dtlsTransport=pc.transceivers[0].dtlsTransport;if(pc.transceivers[sdpMLineIndex].rtpSender){pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport);} -if(pc.transceivers[sdpMLineIndex].rtpReceiver){pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport);}} -if(description.type==='offer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex]||pc._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,usingBundle);} -if(cands.length&&transceiver.iceTransport.state==='new'){if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}} -localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+2)*1001}];var isNewTrack=false;if(direction==='sendrecv'||direction==='sendonly'){isNewTrack=!transceiver.rtpReceiver;rtpReceiver=transceiver.rtpReceiver||new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);if(isNewTrack){var stream;track=rtpReceiver.track;if(remoteMsid&&remoteMsid.stream==='-'){}else if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});} -Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});stream=streams[remoteMsid.stream];}else{if(!streams.default){streams.default=new window.MediaStream();} -stream=streams.default;} -if(stream){addTrackToStreamAndFireEvent(track,stream);transceiver.associatedRemoteMediaStreams.push(stream);} -receiverList.push([track,rtpReceiver,stream]);}}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track){transceiver.associatedRemoteMediaStreams.forEach(function(s){var nativeTrack=s.getTracks().find(function(t){return t.id===transceiver.rtpReceiver.track.id;});if(nativeTrack){removeTrackFromStreamAndFireEvent(nativeTrack,s);}});transceiver.associatedRemoteMediaStreams=[];} -transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;pc._transceive(pc.transceivers[sdpMLineIndex],false,isNewTrack);}else if(description.type==='answer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;pc.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;pc.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;pc.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if(cands.length&&iceTransport.state==='new'){if((isIceLite||isComplete)&&(!usingBundle||sdpMLineIndex===0)){iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}} -if(!usingBundle||sdpMLineIndex===0){if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');} -if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}} -pc._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();} -addTrackToStreamAndFireEvent(track,streams[remoteMsid.stream]);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();} -addTrackToStreamAndFireEvent(track,streams.default);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});if(this._dtlsRole===undefined){this._dtlsRole=description.type==='offer'?'active':'passive';} -this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){if(pc.remoteStreams.indexOf(stream)===-1){pc.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;window.setTimeout(function(){pc._dispatchEvent('addstream',event);});} -receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;} -fireAddTrack(pc,track,receiver,[stream]);});}});receiverList.forEach(function(item){if(item[2]){return;} -fireAddTrack(pc,item[0],item[1],[]);});window.setTimeout(function(){if(!(pc&&pc.transceivers)){return;} -pc.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);return Promise.resolve();};RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();} -if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();} -if(transceiver.rtpSender){transceiver.rtpSender.stop();} -if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._isClosed=true;this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this._dispatchEvent('signalingstatechange',event);};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var pc=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;} -this.needNegotiation=true;window.setTimeout(function(){if(pc.needNegotiation===false){return;} -pc.needNegotiation=false;var event=new Event('negotiationneeded');pc._dispatchEvent('negotiationneeded',event);},0);};RTCPeerConnection.prototype._updateConnectionState=function(){var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';} -if(newState!==this.iceConnectionState){this.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this._dispatchEvent('iceconnectionstatechange',event);}};RTCPeerConnection.prototype.createOffer=function(){var pc=this;if(this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createOffer after close'));} -var numAudioTracks=this.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=this.transceivers.filter(function(t){return t.kind==='video';}).length;var offerOptions=arguments[0];if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');} -if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}} -if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}} -this.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){this._createTransceiver('audio');numAudioTracks--;} -if(numVideoTracks>0){this._createTransceiver('video');numVideoTracks--;}} -var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId,this._sdpSessionVersion++);this.transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,pc.usingBundle);} -var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}});var sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'&&!sendEncodingParameters[0].rtx){sendEncodingParameters[0].rtx={ssrc:sendEncodingParameters[0].ssrc+1};}} -if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);} -transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(this._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -sdp+='a=ice-options:trickle\r\n';this.transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream,pc._dtlsRole);sdp+='a=rtcp-rsize\r\n';if(transceiver.iceGatherer&&pc.iceGatheringState!=='new'&&(sdpMLineIndex===0||!pc.usingBundle)){transceiver.iceGatherer.getLocalCandidates().forEach(function(cand){cand.component=1;sdp+='a='+SDPUtils.writeCandidate(cand)+'\r\n';});if(transceiver.iceGatherer.state==='completed'){sdp+='a=end-of-candidates\r\n';}}});var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var pc=this;if(this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer after close'));} -var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId,this._sdpSessionVersion++);if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -var mediaSectionsInOffer=SDPUtils.splitSections(this.remoteDescription.sdp).length-1;this.transceivers.forEach(function(transceiver,sdpMLineIndex){if(sdpMLineIndex+1>mediaSectionsInOffer){return;} -if(transceiver.isDatachannel){sdp+='m=application 0 DTLS/SCTP 5000\r\n'+'c=IN IP4 0.0.0.0\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;} -if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];} -if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'&&!transceiver.sendEncodingParameters[0].rtx){transceiver.sendEncodingParameters[0].rtx={ssrc:transceiver.sendEncodingParameters[0].ssrc+1};}}} -var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;} -sdp+=writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream,pc._dtlsRole);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){var sections;if(!candidate||candidate.candidate===''){for(var j=0;j0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return Promise.resolve();} -if(cand.component&&cand.component!==1){return Promise.resolve();} -if(sdpMLineIndex===0||(sdpMLineIndex>0&&transceiver.iceTransport!==this.transceivers[0].iceTransport)){if(!maybeAddCandidate(transceiver.iceTransport,cand)){return Promise.reject(makeError('OperationError','Can not add ICE candidate'));}} -var candidateString=candidate.candidate.trim();if(candidateString.indexOf('a=')===0){candidateString=candidateString.substr(2);} -sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[sdpMLineIndex+1]+='a='+ -(cand.type?candidateString:'end-of-candidates') -+'\r\n';this.remoteDescription.sdp=sections.join('');}else{return Promise.reject(makeError('OperationError','Can not add ICE candidate'));}} -return Promise.resolve();};RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var fixStatsType=function(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;};return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){result[id].type=fixStatsType(result[id]);results.set(id,result[id]);});});resolve(results);});});};var methods=['createOffer','createAnswer'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[0]==='function'||typeof args[1]==='function'){return nativeMethod.apply(this,[arguments[2]]).then(function(description){if(typeof args[0]==='function'){args[0].apply(null,[description]);}},function(error){if(typeof args[1]==='function'){args[1].apply(null,[error]);}});} -return nativeMethod.apply(this,arguments);};});methods=['setLocalDescription','setRemoteDescription','addIceCandidate'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'||typeof args[2]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}},function(error){if(typeof args[2]==='function'){args[2].apply(null,[error]);}});} -return nativeMethod.apply(this,arguments);};});['getStats'].forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}});} -return nativeMethod.apply(this,arguments);};});return RTCPeerConnection;};},{"sdp":2}],2:[function(require,module,exports){'use strict';var SDPUtils={};SDPUtils.generateIdentifier=function(){return Math.random().toString(36).substr(2,10);};SDPUtils.localCName=SDPUtils.generateIdentifier();SDPUtils.splitLines=function(blob){return blob.trim().split('\n').map(function(line){return line.trim();});};SDPUtils.splitSections=function(blob){var parts=blob.split('\nm=');return parts.map(function(part,index){return(index>0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');} -var candidate={foundation:parts[0],component:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+ -(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+headerExtension.uri+'\r\n';};SDPUtils.parseFmtp=function(line){var parsed={};var kv;var parts=line.substr(line.indexOf(' ')+1).split(';');for(var j=0;j-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);} -return parts;};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}} -SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;} -return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';} -sdp+='a=rtcp-mux\r\n';caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];} -description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});} -var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10)*1000*0.95 --(50*40*8);}else{bandwidth=undefined;} -encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});} -return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;} -var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};} -var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId,sessVer){var sessionId;var version=sessVer!==undefined?sessVer:2;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();} -return'v=0\r\n'+'o=thisisadapterortc '+sessionId+' '+version+' IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.direction){sdp+='a='+transceiver.direction+'\r\n';}else if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';} -if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+ -transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+ -transceiver.sendEncodingParameters[0].ssrc+' '+ -transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}} -sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';} -return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i=65){return this.shimAddTrackRemoveTrackWithNative(window);} -var origGetLocalStreams=window.RTCPeerConnection.prototype.getLocalStreams;window.RTCPeerConnection.prototype.getLocalStreams=function(){var pc=this;var nativeStreams=origGetLocalStreams.apply(this);pc._reverseStreams=pc._reverseStreams||{};return nativeStreams.map(function(stream){return pc._reverseStreams[stream.id];});};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var pc=this;pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=pc.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});if(!pc._reverseStreams[stream.id]){var newStream=new window.MediaStream(stream.getTracks());pc._streams[stream.id]=newStream;pc._reverseStreams[newStream.id]=stream;stream=newStream;} -origAddStream.apply(pc,[stream]);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};origRemoveStream.apply(pc,[(pc._streams[stream.id]||stream)]);delete pc._reverseStreams[(pc._streams[stream.id]?pc._streams[stream.id].id:stream.id)];delete pc._streams[stream.id];};window.RTCPeerConnection.prototype.addTrack=function(track,stream){var pc=this;if(pc.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');} -var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');} -var alreadyExists=pc.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');} -pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};var oldStream=pc._streams[stream.id];if(oldStream){oldStream.addTrack(track);Promise.resolve().then(function(){pc.dispatchEvent(new Event('negotiationneeded'));});}else{var newStream=new window.MediaStream([track]);pc._streams[stream.id]=newStream;pc._reverseStreams[newStream.id]=stream;pc.addStream(newStream);} -return pc.getSenders().find(function(s){return s.track===track;});};function replaceInternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(internalStream.id,'g'),externalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});} -function replaceExternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(externalStream.id,'g'),internalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});} -['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var pc=this;var args=arguments;var isLegacyCall=arguments.length&&typeof arguments[0]==='function';if(isLegacyCall){return nativeMethod.apply(pc,[function(description){var desc=replaceInternalStreamId(pc,description);args[0].apply(null,[desc]);},function(err){if(args[1]){args[1].apply(null,err);}},arguments[2]]);} -return nativeMethod.apply(pc,arguments).then(function(description){return replaceInternalStreamId(pc,description);});};});var origSetLocalDescription=window.RTCPeerConnection.prototype.setLocalDescription;window.RTCPeerConnection.prototype.setLocalDescription=function(){var pc=this;if(!arguments.length||!arguments[0].type){return origSetLocalDescription.apply(pc,arguments);} -arguments[0]=replaceExternalStreamId(pc,arguments[0]);return origSetLocalDescription.apply(pc,arguments);};var origLocalDescription=Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype,'localDescription');Object.defineProperty(window.RTCPeerConnection.prototype,'localDescription',{get:function(){var pc=this;var description=origLocalDescription.get.apply(this);if(description.type===''){return description;} -return replaceInternalStreamId(pc,description);}});window.RTCPeerConnection.prototype.removeTrack=function(sender){var pc=this;if(pc.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');} -if(!sender._pc){throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.','TypeError');} -var isLocal=sender._pc===pc;if(!isLocal){throw new DOMException('Sender was not created by this connection.','InvalidAccessError');} -pc._streams=pc._streams||{};var stream;Object.keys(pc._streams).forEach(function(streamid){var hasTrack=pc._streams[streamid].getTracks().find(function(track){return sender.track===track;});if(hasTrack){stream=pc._streams[streamid];}});if(stream){if(stream.getTracks().length===1){pc.removeStream(pc._reverseStreams[stream.id]);}else{stream.removeTrack(sender.track);} -pc.dispatchEvent(new Event('negotiationneeded'));}};},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(!window.RTCPeerConnection&&window.webkitRTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){logging('PeerConnection');if(pcConfig&&pcConfig.iceTransportPolicy){pcConfig.iceTransports=pcConfig.iceTransportPolicy;} -return new window.webkitRTCPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=window.webkitRTCPeerConnection.prototype;if(window.webkitRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return window.webkitRTCPeerConnection.generateCertificate;}});}}else{var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i0&&typeof selector==='function'){return origGetStats.apply(this,arguments);} -if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);} -var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);} -return new Promise(function(resolve,reject){origGetStats.apply(pc,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var args=arguments;var pc=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(pc,[args[0],resolve,reject]);});if(args.length<2){return promise;} -return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});} -if(browserDetails.version<52){['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var pc=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(pc,[resolve,reject,opts]);});} -return nativeMethod.apply(this,arguments);};});} -['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);} -return Promise.resolve();} -return nativeAddIceCandidate.apply(this,arguments);};}};},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){'use strict';var utils=require('../utils.js');var logging=utils.log;module.exports=function(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;} -var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;} -var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;} -var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);} -return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}} -if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);} -return cc;};var shimConstraints_=function(constraints,func){if(browserDetails.version>=61){return func(constraints);} -constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&typeof constraints.audio==='object'){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);} -if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<66;if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];} -if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().indexOf(match)!==-1;});});if(!dev&&devices.length&&matches.indexOf('back')!==-1){dev=devices[devices.length-1];} -if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.deviceId};} -constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}} -constraints.video=constraintsToChrome_(constraints.video);} -logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',InvalidStateError:'NotReadableError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotReadableError',MediaDeviceKillSwitchOn:'NotReadableError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){if(onError){onError(shimError_(e));}});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return window.MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});},getSupportedConstraints:function(){return{deviceId:true,echoCancellation:true,facingMode:true,frameRate:true,height:true,width:true};}};} -if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');} -return stream;},function(e){return Promise.reject(shimError_(e));});});};} -if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};} -if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":13}],7:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');var utils=require('./utils');module.exports={shimRTCIceCandidate:function(window){if(window.RTCIceCandidate&&'foundation'in -window.RTCIceCandidate.prototype){return;} -var NativeRTCIceCandidate=window.RTCIceCandidate;window.RTCIceCandidate=function(args){if(typeof args==='object'&&args.candidate&&args.candidate.indexOf('a=')===0){args=JSON.parse(JSON.stringify(args));args.candidate=args.candidate.substr(2);} -var nativeCandidate=new NativeRTCIceCandidate(args);var parsedCandidate=SDPUtils.parseCandidate(args.candidate);var augmentedCandidate=Object.assign(nativeCandidate,parsedCandidate);augmentedCandidate.toJSON=function(){return{candidate:augmentedCandidate.candidate,sdpMid:augmentedCandidate.sdpMid,sdpMLineIndex:augmentedCandidate.sdpMLineIndex,usernameFragment:augmentedCandidate.usernameFragment,};};return augmentedCandidate;};utils.wrapPeerConnectionEvent(window,'icecandidate',function(e){if(e.candidate){Object.defineProperty(e,'candidate',{value:new window.RTCIceCandidate(e.candidate),writable:'false'});} -return e;});},shimCreateObjectURL:function(window){var URL=window&&window.URL;if(!(typeof window==='object'&&window.HTMLMediaElement&&'srcObject'in window.HTMLMediaElement.prototype&&URL.createObjectURL&&URL.revokeObjectURL)){return undefined;} -var nativeCreateObjectURL=URL.createObjectURL.bind(URL);var nativeRevokeObjectURL=URL.revokeObjectURL.bind(URL);var streams=new Map(),newId=0;URL.createObjectURL=function(stream){if('getTracks'in stream){var url='polyblob:'+(++newId);streams.set(url,stream);utils.deprecated('URL.createObjectURL(stream)','elem.srcObject = stream');return url;} -return nativeCreateObjectURL(stream);};URL.revokeObjectURL=function(url){nativeRevokeObjectURL(url);streams.delete(url);};var dsc=Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,'src');Object.defineProperty(window.HTMLMediaElement.prototype,'src',{get:function(){return dsc.get.apply(this);},set:function(url){this.srcObject=streams.get(url)||null;return dsc.set.apply(this,[url]);}});var nativeSetAttribute=window.HTMLMediaElement.prototype.setAttribute;window.HTMLMediaElement.prototype.setAttribute=function(){if(arguments.length===2&&(''+arguments[0]).toLowerCase()==='src'){this.srcObject=streams.get(arguments[1])||null;} -return nativeSetAttribute.apply(this,arguments);};},shimMaxMessageSize:function(window){if(window.RTCSctpTransport||!window.RTCPeerConnection){return;} -var browserDetails=utils.detectBrowser(window);if(!('sctp'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'sctp',{get:function(){return typeof this._sctp==='undefined'?null:this._sctp;}});} -var sctpInDescription=function(description){var sections=SDPUtils.splitSections(description.sdp);sections.shift();return sections.some(function(mediaSection){var mLine=SDPUtils.parseMLine(mediaSection);return mLine&&mLine.kind==='application'&&mLine.protocol.indexOf('SCTP')!==-1;});};var getRemoteFirefoxVersion=function(description){var match=description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(match===null||match.length<2){return-1;} -var version=parseInt(match[1],10);return version!==version?-1:version;};var getCanSendMaxMessageSize=function(remoteIsFirefox){var canSendMaxMessageSize=65536;if(browserDetails.browser==='firefox'){if(browserDetails.version<57){if(remoteIsFirefox===-1){canSendMaxMessageSize=16384;}else{canSendMaxMessageSize=2147483637;}}else{canSendMaxMessageSize=browserDetails.version===57?65535:65536;}} -return canSendMaxMessageSize;};var getMaxMessageSize=function(description,remoteIsFirefox){var maxMessageSize=65536;if(browserDetails.browser==='firefox'&&browserDetails.version===57){maxMessageSize=65535;} -var match=SDPUtils.matchPrefix(description.sdp,'a=max-message-size:');if(match.length>0){maxMessageSize=parseInt(match[0].substr(19),10);}else if(browserDetails.browser==='firefox'&&remoteIsFirefox!==-1){maxMessageSize=2147483637;} -return maxMessageSize;};var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var pc=this;pc._sctp=null;if(sctpInDescription(arguments[0])){var isFirefox=getRemoteFirefoxVersion(arguments[0]);var canSendMMS=getCanSendMaxMessageSize(isFirefox);var remoteMMS=getMaxMessageSize(arguments[0],isFirefox);var maxMessageSize;if(canSendMMS===0&&remoteMMS===0){maxMessageSize=Number.POSITIVE_INFINITY;}else if(canSendMMS===0||remoteMMS===0){maxMessageSize=Math.max(canSendMMS,remoteMMS);}else{maxMessageSize=Math.min(canSendMMS,remoteMMS);} -var sctp={};Object.defineProperty(sctp,'maxMessageSize',{get:function(){return maxMessageSize;}});pc._sctp=sctp;} -return origSetRemoteDescription.apply(pc,arguments);};},shimSendThrowTypeError:function(window){var origCreateDataChannel=window.RTCPeerConnection.prototype.createDataChannel;window.RTCPeerConnection.prototype.createDataChannel=function(){var pc=this;var dataChannel=origCreateDataChannel.apply(pc,arguments);var origDataChannelSend=dataChannel.send;dataChannel.send=function(){var dc=this;var data=arguments[0];var length=data.length||data.size||data.byteLength;if(length>pc.sctp.maxMessageSize){throw new DOMException('Message too large (can send a maximum of '+ -pc.sctp.maxMessageSize+' bytes)','TypeError');} -return origDataChannelSend.apply(dc,arguments);};return dataChannel;};}};},{"./utils":13,"sdp":2}],8:[function(require,module,exports){'use strict';var utils=require('../utils');var shimRTCPeerConnection=require('rtcpeerconnection-shim');module.exports={shimGetUserMedia:require('./getusermedia'),shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};} -if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};} -if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}} -if(window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=new window.RTCDtmfSender(this);}else if(this.track.kind==='video'){this._dtmf=null;}} -return this._dtmf;}});} -window.RTCPeerConnection=shimRTCPeerConnection(window,browserDetails.version);},shimReplaceTrack:function(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}};},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){'use strict';module.exports=function(window){var navigator=window&&window.navigator;var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],10:[function(require,module,exports){'use strict';var utils=require('../utils');module.exports={shimGetUserMedia:require('./getusermedia'),shimOnTrack:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in -window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);} -this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.transceiver={receiver:event.receiver};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});} -if(typeof window==='object'&&window.RTCTrackEvent&&('receiver'in window.RTCTrackEvent.prototype)&&!('transceiver'in window.RTCTrackEvent.prototype)){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function(){return{receiver:this.receiver};}});}},shimSourceObject:function(window){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;} -if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if(typeof c==='object'&&typeof c.audio==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');} -return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};} -if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&typeof c==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');} -return nativeApplyConstraints.apply(this,[c]);};}} -navigator.getUserMedia=function(constraints,onSuccess,onError){if(browserDetails.version<44){return getUserMedia_(constraints,onSuccess,onError);} -utils.deprecated('navigator.getUserMedia','navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};};},{"../utils":13}],12:[function(require,module,exports){'use strict';var utils=require('../utils');module.exports={shimLocalStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];} -return this._localStreams;};} -if(!('getStreamById'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getStreamById=function(id){var result=null;if(this._localStreams){this._localStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -if(this._remoteStreams){this._remoteStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -return result;};} -if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){if(!this._localStreams){this._localStreams=[];} -if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);} -var pc=this;stream.getTracks().forEach(function(track){_addTrack.call(pc,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);}} -return _addTrack.call(this,track,stream);};} -if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){if(!this._localStreams){this._localStreams=[];} -var index=this._localStreams.indexOf(stream);if(index===-1){return;} -this._localStreams.splice(index,1);var pc=this;var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.indexOf(sender.track)!==-1){pc.removeTrack(sender);}});};}},shimRemoteStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};} -if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function(){return this._onaddstream;},set:function(f){var pc=this;if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);} -this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!pc._remoteStreams){pc._remoteStreams=[];} -if(pc._remoteStreams.indexOf(stream)>=0){return;} -pc._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;pc.dispatchEvent(event);});});}});}},shimCallbacksAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;},shimGetUserMedia:function(window){var navigator=window&&window.navigator;if(!navigator.getUserMedia){if(navigator.webkitGetUserMedia){navigator.getUserMedia=navigator.webkitGetUserMedia.bind(navigator);}else if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}},shimRTCIceServerUrls:function(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i=pos&&parseInt(match[pos],10);} -function wrapPeerConnectionEvent(window,eventNameToWrap,wrapper){if(!window.RTCPeerConnection){return;} -var proto=window.RTCPeerConnection.prototype;var nativeAddEventListener=proto.addEventListener;proto.addEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap){return nativeAddEventListener.apply(this,arguments);} -var wrappedCallback=function(e){cb(wrapper(e));};this._eventMap=this._eventMap||{};this._eventMap[cb]=wrappedCallback;return nativeAddEventListener.apply(this,[nativeEventName,wrappedCallback]);};var nativeRemoveEventListener=proto.removeEventListener;proto.removeEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap||!this._eventMap||!this._eventMap[cb]){return nativeRemoveEventListener.apply(this,arguments);} -var unwrappedCb=this._eventMap[cb];delete this._eventMap[cb];return nativeRemoveEventListener.apply(this,[nativeEventName,unwrappedCb]);};Object.defineProperty(proto,'on'+eventNameToWrap,{get:function(){return this['_on'+eventNameToWrap];},set:function(cb){if(this['_on'+eventNameToWrap]){this.removeEventListener(eventNameToWrap,this['_on'+eventNameToWrap]);delete this['_on'+eventNameToWrap];} -if(cb){this.addEventListener(eventNameToWrap,this['_on'+eventNameToWrap]=cb);}}});} -module.exports={extractVersion:extractVersion,wrapPeerConnectionEvent:wrapPeerConnectionEvent,disableLog:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');} -logDisabled_=bool;return(bool)?'adapter.js logging disabled':'adapter.js logging enabled';},disableWarnings:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');} -deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');},log:function(){if(typeof window==='object'){if(logDisabled_){return;} -if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}},deprecated:function(oldMethod,newMethod){if(!deprecationWarnings_){return;} -console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');},detectBrowser:function(window){var navigator=window&&window.navigator;var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;} -if(navigator.mozGetUserMedia){result.browser='firefox';result.version=extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){result.browser='chrome';result.version=extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;} -return result;}};},{}]},{},[3])(3)}); \ No newline at end of file +(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, + localVideo: null, + screenShare: false, + useCamera: "any", + iceServers: false, + videoParams: {}, + audioParams: {}, + callbacks: { + onICEComplete: function() {}, + onICE: function() {}, + onOfferSDP: function() {} + }, + useStream: null, + }, options); + + this.audioEnabled = true; + this.videoEnabled = true; + + + this.mediaData = { + SDP: null, + profile: {}, + candidateList: [] + }; + + this.constraints = { + offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, + offerToReceiveVideo: this.options.useVideo ? true : false, + }; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + } + + setCompat(); + checkCompat(); + }; + + $.FSRTC.validRes = []; + + $.FSRTC.prototype.useVideo = function(obj, local) { + var self = this; + + if (obj) { + self.options.useVideo = obj; + self.options.localVideo = local; + self.constraints.offerToReceiveVideo = true; + } else { + self.options.useVideo = null; + self.options.localVideo = null; + self.constraints.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) { + return sdp; + } else { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + // Find the payload in fmtp line. + var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + + if (fmtpLineIndex === null) { + // create an fmtp line + sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1" + } else { + // Append stereo=1 to fmtp line. + sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); + } + + sdp = sdpLines.join('\r\n'); + return sdp; + }; + + function setCompat() { + } + + function checkCompat() { + return true; + } + + function onStreamError(self, e) { + console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution', e); + doCallback(self, "onError", e); + } + + function onStreamSuccess(self, stream) { + console.log("Stream Success"); + doCallback(self, "onStream", stream); + } + + function onRemoteStreamSuccess(self, stream) { + console.log("Remote Stream Success"); + doCallback(self, "onRemoteStream", stream); + } + + 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); + } + + FSRTCattachMediaStream = function(element, stream) { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else { + console.error('Error attaching stream to element.'); + } + } + + function onRemoteStream(self, stream) { + if (self.options.useVideo) { + self.options.useVideo.style.display = 'block'; + + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + + if (iOS) { + self.options.useVideo.setAttribute("playsinline", true); + } + } + + var element = self.options.useAudio; + console.log("REMOTE STREAM", stream, element); + + FSRTCattachMediaStream(element, stream); + + + + //self.options.useAudio.play(); + self.remoteStream = stream; + onRemoteStreamSuccess(self, 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.stopPeer = function() { + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + } + + $.FSRTC.prototype.stop = function() { + var self = this; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + self.options.useVideo['src'] = ''; + } + + if (self.localStream && !self.options.useStream) { + if(typeof self.localStream.stop == 'function') { + self.localStream.stop(); + } else { + if (self.localStream.active){ + var tracks = self.localStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + self.localStream = null; + } + + if (self.options.localVideo) { + deactivateLocalVideo(self.options.localVideo); + } + + if (self.options.localVideoStream && !self.options.useStream) { + if(typeof self.options.localVideoStream.stop == 'function') { + self.options.localVideoStream.stop(); + } else { + if (self.options.localVideoStream.active){ + var tracks = self.options.localVideoStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + } + + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + }; + + $.FSRTC.prototype.getMute = function() { + var self = this; + return self.audioEnabled; + } + + $.FSRTC.prototype.setMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var audioTracks = self.localStream.getAudioTracks(); + + for (var i = 0, len = audioTracks.length; i < len; i++ ) { + switch(what) { + case "on": + audioTracks[i].enabled = true; + break; + case "off": + audioTracks[i].enabled = false; + break; + case "toggle": + audioTracks[i].enabled = !audioTracks[i].enabled; + default: + break; + } + + self.audioEnabled = audioTracks[i].enabled; + } + + return !self.audioEnabled; + } + + $.FSRTC.prototype.getVideoMute = function() { + var self = this; + return self.videoEnabled; + } + + $.FSRTC.prototype.setVideoMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var videoTracks = self.localStream.getVideoTracks(); + + for (var i = 0, len = videoTracks.length; i < len; i++ ) { + switch(what) { + case "on": + videoTracks[i].enabled = true; + break; + case "off": + videoTracks[i].enabled = false; + break; + case "toggle": + videoTracks[i].enabled = !videoTracks[i].enabled; + default: + break; + } + + self.videoEnabled = videoTracks[i].enabled; + } + + return !self.videoEnabled; + } + + $.FSRTC.prototype.createAnswer = function(params) { + var self = this; + self.type = "answer"; + self.remoteSDP = params.sdp; + console.debug("inbound sdp: ", params.sdp); + + function onSuccess(stream) { + self.localStream = stream; + + self.peer = FSRTCPeerConnection({ + 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, + iceServers: self.options.iceServers, + offerSDP: { + type: "offer", + sdp: self.remoteSDP + }, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useVideo && self.options.localVideo && !self.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: params.useCamera }, + }, + localVideo: self.options.localVideo, + onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else { + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + } + + }; + + function getMediaParams(obj) { + + var audio; + + if (obj.options.useMic && obj.options.useMic === "none") { + console.log("Microphone Disabled"); + audio = false; + } else if (obj.options.videoParams && obj.options.screenShare) {//obj.options.videoParams.chromeMediaSource == 'desktop') { + console.error("SCREEN SHARE", obj.options.videoParams); + audio = false; + } else { + audio = { + }; + + if (obj.options.audioParams) { + audio = obj.options.audioParams; + } + + if (obj.options.useMic !== "any") { + //audio.optional = [{sourceId: obj.options.useMic}]; + audio.deviceId = {exact: obj.options.useMic}; + } + } + + if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: obj.options.useCamera }, + }, + localVideo: obj.options.localVideo, + onsuccess: function(e) {obj.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + var video = {}; + var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; + var minFrameRate = obj.options.videoParams.minFrameRate || 15; + delete obj.options.videoParams.vertoBestFrameRate; + + if (obj.options.screenShare) { + if (!obj.options.useCamera && !!navigator.mozGetUserMedia) { + //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way + var dowin = window.confirm("Do you want to share an application window? If not you can share an entire screen."); + + video = { + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}, + mediaSource: dowin ? "window" : "screen" + } + } else { + var opt = []; + if (obj.options.useCamera) { + opt.push({sourceId: obj.options.useCamera}); + } + + if (bestFrameRate) { + opt.push({minFrameRate: bestFrameRate}); + opt.push({maxFrameRate: bestFrameRate}); + } + + video = { + mandatory: obj.options.videoParams, + optional: opt + }; + // NOTE: This is a workaround for + // https://bugs.chromium.org/p/chromium/issues/detail?id=862325 + if (!!navigator.userAgent.match(/Android/i)) { + delete video.frameRate.min; + } + } + } else { + + video = { + //mandatory: obj.options.videoParams, + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight} + }; + + + + var useVideo = obj.options.useVideo; + + if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { + //if (!video.optional) { + //video.optional = []; + //} + + + if (obj.options.useCamera !== "any") { + //video.optional.push({sourceId: obj.options.useCamera}); + video.deviceId = { + exact: obj.options.useCamera, + }; + } + + if (bestFrameRate) { + //video.optional.push({minFrameRate: bestFrameRate}); + //video.optional.push({maxFrameRate: bestFrameRate}); + video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30}; + } + + } else { + console.log("Camera Disabled"); + video = false; + useVideo = false; + } + } + + return {audio: audio, video: video, useVideo: useVideo}; + } + + $.FSRTC.prototype.call = function(profile) { + checkCompat(); + + var self = this; + var screen = false; + + self.type = "offer"; + + if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') { + screen = true; + } + + function onSuccess(stream) { + self.localStream = stream; + + if (screen) { + self.constraints.offerToReceiveVideo = false; + self.constraints.offerToReceiveAudio = false; + self.constraints.offerToSendAudio = false; + } + + self.peer = FSRTCPeerConnection({ + type: self.type, + attachStream: self.localStream, + onICE: function(candidate) { + return onICE(self, candidate); + }, + onICEComplete: function() { + return onICEComplete(self); + }, + onRemoteStream: screen ? function(stream) {} : 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, + iceServers: self.options.iceServers, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else if (mediaParams.audio || mediaParams.video) { + + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + + } else { + onSuccess(null); + } + + + + /* + navigator.getUserMedia({ + video: self.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 + + function FSRTCPeerConnection(options) { + var gathering = false, done = false; + var config = {}; + var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }]; + + if (self.options.turnServer) { + default_ice.push(self.options.turnServer) + } + + if (options.iceServers) { + if (typeof(options.iceServers) === "boolean") { + config.iceServers = default_ice; + } else { + config.iceServers = options.iceServers; + } + } + + config.bundlePolicy = "max-compat"; + + var peer = new window.RTCPeerConnection(config); + + openOffererChannel(); + var x = 0; + + function ice_handler() { + + done = true; + gathering = null; + + if (options.onICEComplete) { + options.onICEComplete(); + } + + if (options.type == "offer") { + options.onICESDP(peer.localDescription); + } else { + if (!x && options.onICESDP) { + options.onICESDP(peer.localDescription); + } + } + } + + peer.onicecandidate = function(event) { + + if (done) { + return; + } + + if (!gathering) { + gathering = setTimeout(ice_handler, 1000); + } + + if (event) { + if (event.candidate) { + options.onICE(event.candidate); + } + } else { + done = true; + + if (gathering) { + clearTimeout(gathering); + gathering = null; + } + + ice_handler(); + } + }; + + // 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.oninactive = 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 || { + // 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); + }, + onSdpError, options.constraints); + } + + // onAnswerSDP(RTCSessionDescription) + function createAnswer() { + if (options.type != "answer") return; + + //options.offerSDP.sdp = addStereo(options.offerSDP.sdp); + peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP), onSdpSuccess, onSdpError); + peer.createAnswer(function(sessionDescription) { + sessionDescription.sdp = serializeSdp(sessionDescription.sdp); + peer.setLocalDescription(sessionDescription); + if (options.onAnswerSDP) { + options.onAnswerSDP(sessionDescription); + } + }, + onSdpError); + } + + + if ((options.onChannelMessage) || !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) { + return sdp; + } + + // DataChannel management + var channel; + + function openOffererChannel() { + if (!options.onChannelMessage) return; + + _openOffererChannel(); + + return; + } + + function _openOffererChannel() { + channel = peer.createDataChannel(options.channel || 'RTCDataChannel', { + reliable: false + }); + + 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); + }; + } + + function openAnswererChannel() { + peer.ondatachannel = function(event) { + channel = event.channel; + channel.binaryType = 'blob'; + setChannelEvents(); + }; + + return; + } + + // 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 window.RTCSessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); + }, + addICE: function(candidate) { + peer.addIceCandidate(new window.RTCIceCandidate({ + 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) { + if(typeof options.attachStream.stop == 'function') { + options.attachStream.stop(); + } else { + options.attachStream.active = false; + } + } + } + + }; + } + + // getUserMedia + var video_constraints = { + //mandatory: {}, + //optional: [] + }; + + function activateLocalVideo(el, stream) { + el.srcObject = stream; + el.style.display = 'block'; + } + + function deactivateLocalVideo(el) { + el.srcObject = null; + el.style.display = 'none'; + } + + function getUserMedia(options) { + var n = navigator, + media; + n.getMedia = n.getUserMedia; + n.getMedia(options.constraints || { + audio: true, + video: video_constraints + }, + streaming, options.onerror || + function(e) { + console.error(e); + }); + + function streaming(stream) { + if (options.localVideo) { + activateLocalVideo(options.localVideo, stream); + } + + if (options.onsuccess) { + options.onsuccess(stream); + } + + media = stream; + } + + return media; + } + + $.FSRTC.resSupported = function(w, h) { + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) { + return true; + } + } + + return false; + } + + $.FSRTC.bestResSupported = function() { + var w = 0, h = 0; + + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] >= w && $.FSRTC.validRes[i][1] >= h) { + w = $.FSRTC.validRes[i][0]; + h = $.FSRTC.validRes[i][1]; + } + } + + return [w, h]; + } + + var resList = [[160, 120], [320, 180], [320, 240], [640, 360], [640, 480], [1280, 720], [1920, 1080]]; + var resI = 0; + var ttl = 0; + + var checkRes = function (cam, func) { + + if (resI >= resList.length) { + var res = { + 'validRes': $.FSRTC.validRes, + 'bestResSupported': $.FSRTC.bestResSupported() + }; + + localStorage.setItem("res_" + cam, $.toJSON(res)); + + if (func) return func(res); + return; + } + + w = resList[resI][0]; + h = resList[resI][1]; + resI++; + + var video = { + width: {exact: w}, + height: {exact: h} + }; + + if (cam !== "any") { + video.deviceId = { + exact: cam, + }; + } + + getUserMedia({ + constraints: { + audio: ttl++ == 0, + video: video + }, + onsuccess: function(e) { + e.getTracks().forEach(function(track) {track.stop();}); + console.info(w + "x" + h + " supported."); $.FSRTC.validRes.push([w, h]); checkRes(cam, func);}, + onerror: function(e) {console.warn( w + "x" + h + " not supported."); checkRes(cam, func);} + }); + } + + + $.FSRTC.getValidRes = function (cam, func) { + var used = []; + var cached = localStorage.getItem("res_" + cam); + + if (cached) { + var cache = $.parseJSON(cached); + + if (cache) { + $.FSRTC.validRes = cache.validRes; + console.log("CACHED RES FOR CAM " + cam, cache); + } else { + console.error("INVALID CACHE"); + } + return func ? func(cache) : null; + } + + + $.FSRTC.validRes = []; + resI = 0; + + checkRes(cam, func); + } + + $.FSRTC.checkPerms = function (runtime, check_audio, check_video) { + getUserMedia({ + constraints: { + audio: check_audio, + video: check_video, + }, + onsuccess: function(e) { + + e.getTracks().forEach(function(track) {track.stop();}); + + console.info("media perm init complete"); + if (runtime) { + setTimeout(runtime, 100, true); + } + }, + onerror: function(e) { + if (check_video && check_audio) { + console.error("error, retesting with audio params only"); + return $.FSRTC.checkPerms(runtime, check_audio, false); + } + + console.error("media perm init error"); + + if (runtime) { + runtime(false) + } + } + }); + } + +})(jQuery); +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * + * 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, + loginParams : null, + userVariables : null, + getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); } + }, options); + + self.ws_cnt = 0; + + // 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 : { success_cb: cb, error_cb: cb } + $.JsonRpcClient.prototype._ws_callbacks = {}; + + /// The next JSON-RPC request id. + $.JsonRpcClient.prototype._current_id = 1; + + + $.JsonRpcClient.prototype.speedTest = function (bytes, cb) { + var socket = this.options.getSocket(this.wsOnMessage); + if (socket !== null) { + this.speedCB = cb; + this.speedBytes = bytes; + socket.send("#SPU " + bytes); + + var loops = bytes / 1024; + var rem = bytes % 1024; + var i; + var data = new Array(1024).join("."); + for (i = 0; i < loops; i++) { + socket.send("#SPB " + data); + } + + if (rem) { + socket.send("#SPB " + data); + } + + socket.send("#SPE"); + } + }; + + + + /** + * @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() { + var self = this; + if (self.socketReady()) { + self._ws_socket.onclose = function (w) {console.log("Closing Socket");}; + self._ws_socket.close(); + } + }; + + $.JsonRpcClient.prototype.loginData = function(params) { + var self = this; + self.options.login = params.login; + self.options.passwd = params.passwd; + self.options.loginParams = params.loginParams; + self.options.userVariables = params.userVariables; + }; + + $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) { + var self = this; + + if (self.to) { + clearTimeout(self.to); + } + + if (!self.socketReady()) { + self.authing = false; + + if (self._ws_socket) { + delete self._ws_socket; + } + + // No socket, or dying socket, let's get a new one. + self._ws_socket = new WebSocket(self.options.socketUrl); + + if (self._ws_socket) { + // Set up onmessage handler. + self._ws_socket.onmessage = onmessage_cb; + self._ws_socket.onclose = function (w) { + if (!self.ws_sleep) { + self.ws_sleep = 1000; + } + + if (self.options.onWSClose) { + self.options.onWSClose(self); + } + + if (self.ws_cnt > 10 && self.options.wsFallbackURL) { + self.options.socketUrl = self.options.wsFallbackURL; + } + + console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec"); + + self.to = setTimeout(function() { + console.log("Attempting Reconnection...."); + self.connectSocket(onmessage_cb); + }, self.ws_sleep); + + self.ws_cnt++; + + if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) { + self.ws_sleep += 1000; + } + }; + + // Set up sending of message for when the socket is open. + self._ws_socket.onopen = function() { + if (self.to) { + clearTimeout(self.to); + } + self.ws_sleep = 1000; + self.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 self._ws_socket ? true : false; + }; + + $.JsonRpcClient.prototype.stopRetrying = function() { + if (self.to) + clearTimeout(self.to); + } + + $.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; + + // Special sub proto + if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") { + if (event.data[3] == "U") { + this.up_dur = parseInt(event.data.substring(4)); + } else if (this.speedCB && event.data[3] == "D") { + this.down_dur = parseInt(event.data.substring(4)); + + var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0); + var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0); + + console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps); + var cb = this.speedCB; + this.speedCB = null; + cb(event, { + upDur: this.up_dur, + downDur: this.down_dur, + upKPS: up_kps, + downKPS: down_kps + }); + } + + return; + } + + + 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; + } else if ('error' in response && this._ws_callbacks[response.id]) { + // If this is an object with error, it is an error response. + + // 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, loginParams: self.options.loginParams, + userVariables: self.options.userVariables}, + 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 = {}; + var i = 0; + var call; + var success_cb; + var error_cb; + + // 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 (i = 0; i < this._requests.length; i++) { + call = this._requests[i]; + success_cb = ('success_cb' in call) ? call.success_cb : undefined; + 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 (i = 0; i < this._requests.length; i++) { + 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 + }; + } + } + + 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); + +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.verto.js - Main interface + * + */ + +(function($) { + var sources = []; + + var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ? + function() { + // If we have a cryptographically secure PRNG, use that + // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript + 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() { + // Otherwise, just use Math.random + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + 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); + }); + }; + + /// MASTER OBJ + $.verto = function(options, callbacks) { + var verto = this; + + $.verto.saved.push(verto); + + verto.options = $.extend({ + login: null, + passwd: null, + socketUrl: null, + tag: null, + localTag: null, + videoParams: {}, + audioParams: {}, + loginParams: {}, + deviceParams: {onResCheck: null}, + userVariables: {}, + iceServers: false, + ringSleep: 6000, + sessid: null, + useStream: null + }, options); + + if (verto.options.deviceParams.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck); + } + + if (!verto.options.deviceParams.useMic) { + verto.options.deviceParams.useMic = "any"; + } + + if (!verto.options.deviceParams.useSpeak) { + verto.options.deviceParams.useSpeak = "any"; + } + + if (verto.options.sessid) { + verto.sessid = verto.options.sessid; + } else { + verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID(); + localStorage.setItem("verto_session_uuid", verto.sessid); + } + + verto.dialogs = {}; + verto.callbacks = callbacks || {}; + verto.eventSUBS = {}; + + verto.rpcClient = new $.JsonRpcClient({ + login: verto.options.login, + passwd: verto.options.passwd, + socketUrl: verto.options.socketUrl, + wsFallbackURL: verto.options.wsFallbackURL, + turnServer: verto.options.turnServer, + loginParams: verto.options.loginParams, + userVariables: verto.options.userVariables, + 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(); + } + }); + + var tag = verto.options.tag; + if (typeof(tag) === "function") { + tag = tag(); + } + + if (verto.options.ringFile && verto.options.tag) { + verto.ringer = $("#" + tag); + } + + verto.rpcClient.call('login', {}); + + }; + + + $.verto.prototype.deviceParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.deviceParams[i] = obj[i]; + } + + if (obj.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined); + } + }; + + $.verto.prototype.videoParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.videoParams[i] = obj[i]; + } + }; + + $.verto.prototype.iceServers = function(obj) { + var verto = this; + verto.options.iceServers = obj; + }; + + $.verto.prototype.loginData = function(params) { + var verto = this; + 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(); + if (verto.callbacks.onWSClose) { + verto.callbacks.onWSClose(verto, false); + } + 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": + //console.error(e); + break; + } + }; + + $.verto.prototype.sendMethod = function(method, params) { + var verto = this; + + verto.rpcClient.call(method, params, + + function(e) { + /* Success */ + verto.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + if (args["useCamera"]) { + verto.options.deviceParams["useCamera"] = args["useCamera"]; + } + + var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args); + + if (callbacks) { + dialog.callbacks = callbacks; + } + + dialog.invite(); + + return dialog; + }; + + $.verto.prototype.handleMessage = function(data) { + var verto = this; + + if (!(data && data.method)) { + console.error("Invalid Data", data); + return; + } + + if (data.params.callID) { + var dialog = verto.dialogs[data.params.callID]; + + if (data.method === "verto.attach" && dialog) { + delete dialog.verto.dialogs[dialog.callID]; + dialog.rtc.stop(); + dialog = null; + } + + if (dialog) { + + switch (data.method) { + case 'verto.bye': + dialog.hangup(data.params); + break; + case 'verto.answer': + dialog.handleAnswer(data.params); + break; + case 'verto.media': + dialog.handleMedia(data.params); + 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", dialog, 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); + dialog.setState($.verto.enum.state.recovering); + + 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.punt': + verto.purge(); + verto.logout(); + break; + 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 && key === verto.sessid) { + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params); + } + } else 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; + + case 'verto.clientReady': + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params); + } + console.debug("CLIENT READY", data.params); + 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 ? "
" : "\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; + + // Inherit methods of hashArray + hashArray.call(la); + + // Save the hashArray add, del, reorder, clear methods so we can make our own. + 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)) { + + //la.clear(); + 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 + }); + } + } + }; + + // @param serno La is the serial number for la particular request. + // @param key If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object. + // @param index If looking at it as an array, la represents the position in the array where you want to store the val object. + // @param val La is the object you want to store at the key or index location in the hash table / array. + 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; + + //console.error("READ:", packet); + + if (packet.name != la.name) { + return; + } + + 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.sendCommand = function(cmd, obj) { + var self = la; + self.broadcast(self.context, { + liveArray: { + command: cmd, + context: self.context, + name: self.name, + obj: obj + } + }); + }; + + la.bootstrap = function(obj) { + var self = la; + la.sendCommand("bootstrap", obj); + //self.heartbeat(); + }; + + 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); + }; + + /* back compat so jsonstatus can always be enabled */ + function genRow(data) { + if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) { + var tmp = $.parseJSON(data[4]); + data[4] = tmp.oldStatus; + data[5] = null; + } + return data; + } + + function genArray(obj) { + var data = obj.asArray(); + + for (var i in data) { + data[i] = genRow(data[i]); + } + + return data; + } + + + 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; + } + } + + if (config.onChange) { + config.onChange(obj, args); + } + + try { + switch (args.action) { + case "bootObj": + if (!args.data) { + console.error("missing data"); + return; + } + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + dt.fnAdjustColumnSizing(); + break; + case "add": + if (!args.data) { + console.error("missing data"); + return; + } + if (args.redraw > -1) { + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + } else { + dt.fnAddData(genRow(args.data)); + } + dt.fnAdjustColumnSizing(); + break; + case "modify": + if (!args.data) { + return; + } + //console.debug(args, index); + dt.fnUpdate(genRow(args.data), index); + dt.fnAdjustColumnSizing(); + break; + case "del": + dt.fnDeleteRow(index); + dt.fnAdjustColumnSizing(); + break; + case "clear": + dt.fnClearTable(); + break; + case "reorder": + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + 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; + } + + }; + + la.onChange(la, { + action: "init" + }); + + }; + + var CONFMAN_SERNO = 1; + + /* + Conference Manager without jQuery table. + */ + + $.verto.conf = function(verto, params) { + var conf = this; + + conf.params = $.extend({ + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + conf.verto = verto; + conf.serno = CONFMAN_SERNO++; + + createMainModeratorMethods(); + + verto.subscribe(conf.params.laData.modChannel, { + handler: function(v, e) { + if (conf.params.onBroadcast) { + conf.params.onBroadcast(verto, conf, e.data); + } + } + }); + + verto.subscribe(conf.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(conf.params.infoCallback) === "function") { + conf.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(conf.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(conf.params.chatCallback) === "function") { + conf.params.chatCallback(v,e); + } + } + }); + }; + + $.verto.conf.prototype.modCommand = function(cmd, id, value) { + var conf = this; + + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.conf.prototype.destroy = function() { + var conf = this; + + conf.destroyed = true; + conf.params.onBroadcast(conf.verto, conf, 'destroy'); + + if (conf.params.laData.modChannel) { + conf.verto.unsubscribe(conf.params.laData.modChannel); + } + + if (conf.params.laData.chatChannel) { + conf.verto.unsubscribe(conf.params.laData.chatChannel); + } + + if (conf.params.laData.infoChannel) { + conf.verto.unsubscribe(conf.params.laData.infoChannel); + } + }; + + function createMainModeratorMethods() { + $.verto.conf.prototype.listVideoLayouts = function() { + this.modCommand("list-videoLayouts", null, null); + }; + + $.verto.conf.prototype.play = function(file) { + this.modCommand("play", null, file); + }; + + $.verto.conf.prototype.stop = function() { + this.modCommand("stop", null, "all"); + }; + + $.verto.conf.prototype.deaf = function(memberID) { + this.modCommand("deaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.undeaf = function(memberID) { + this.modCommand("undeaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.record = function(file) { + this.modCommand("recording", null, ["start", file]); + }; + + $.verto.conf.prototype.stopRecord = function() { + this.modCommand("recording", null, ["stop", "all"]); + }; + + $.verto.conf.prototype.snapshot = function(file) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-write-png", null, file); + }; + + $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + if (canvasID) { + this.modCommand("vid-layout", null, [layout, canvasID]); + } else { + this.modCommand("vid-layout", null, layout); + } + }; + + $.verto.conf.prototype.kick = function(memberID) { + this.modCommand("kick", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteMic = function(memberID) { + this.modCommand("tmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteVideo = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("tvmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.presenter = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-res-id", parseInt(memberID), "presenter"); + }; + + $.verto.conf.prototype.videoFloor = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-floor", parseInt(memberID), "force"); + }; + + $.verto.conf.prototype.banner = function(memberID, text) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-banner", parseInt(memberID), escape(text)); + }; + + $.verto.conf.prototype.volumeDown = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.volumeUp = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.gainDown = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.gainUp = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.transfer = function(memberID, exten) { + this.modCommand("transfer", parseInt(memberID), exten); + }; + + $.verto.conf.prototype.sendChat = function(message, type) { + var conf = this; + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + } + + $.verto.modfuncs = {}; + + $.verto.confMan = function(verto, params) { + var confMan = this; + + confMan.params = $.extend({ + tableID: null, + statusID: null, + mainModID: null, + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + confMan.verto = verto; + confMan.serno = CONFMAN_SERNO++; + confMan.canvasCount = confMan.params.laData.canvasCount; + + function genMainMod(jq) { + var play_id = "play_" + confMan.serno; + var stop_id = "stop_" + confMan.serno; + var recording_id = "recording_" + confMan.serno; + var snapshot_id = "snapshot_" + confMan.serno; + var rec_stop_id = "recording_stop" + confMan.serno; + var div_id = "confman_" + confMan.serno; + + var html = "

" + + "" + + "" + + "" + + "" + + (confMan.params.hasVid ? "" : "") + + "

"; + + jq.html(html); + + $.verto.modfuncs.change_video_layout = function(id, canvas_id) { + var val = $("#" + id + " option:selected").text(); + if (val !== "none") { + confMan.modCommand("vid-layout", null, [val, canvas_id]); + } + }; + + if (confMan.params.hasVid) { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno; + var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno; + + + var vlhtml = "

" + + "Video Layout Canvas " + (j+1) + + " " + + "

"; + jq.append(vlhtml); + } + + $("#" + snapshot_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("vid-write-png", null, file); + } + }); + } + + $("#" + play_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("play", null, file); + } + }); + + $("#" + stop_id).click(function() { + confMan.modCommand("stop", null, "all"); + }); + + $("#" + recording_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("recording", null, ["start", file]); + } + }); + + $("#" + rec_stop_id).click(function() { + confMan.modCommand("recording", null, ["stop", "all"]); + }); + + } + + function genControls(jq, rowid) { + var x = parseInt(rowid); + var kick_id = "kick_" + x; + var canvas_in_next_id = "canvas_in_next_" + x; + var canvas_in_prev_id = "canvas_in_prev_" + x; + var canvas_out_next_id = "canvas_out_next_" + x; + var canvas_out_prev_id = "canvas_out_prev_" + x; + + var canvas_in_set_id = "canvas_in_set_" + x; + var canvas_out_set_id = "canvas_out_set_" + x; + + var layer_set_id = "layer_set_" + x; + var layer_next_id = "layer_next_" + x; + var layer_prev_id = "layer_prev_" + x; + + var tmute_id = "tmute_" + x; + var tvmute_id = "tvmute_" + x; + var vbanner_id = "vbanner_" + x; + var tvpresenter_id = "tvpresenter_" + x; + var tvfloor_id = "tvfloor_" + x; + var box_id = "box_" + x; + var gainup_id = "gain_in_up" + x; + var gaindn_id = "gain_in_dn" + x; + var volup_id = "vol_in_up" + x; + var voldn_id = "vol_in_dn" + x; + var transfer_id = "transfer" + x; + + + var html = "
"; + + html += "General Controls
"; + + html += "" + + "" + + "" + + "" + + "" + + "" + + ""; + + if (confMan.params.hasVid) { + html += "

Video Controls
"; + + + html += "" + + "" + + "" + + ""; + + if (confMan.canvasCount > 1) { + html += "

Canvas Controls
" + + "" + + "" + + "" + + + "
" + + + "" + + "" + + ""; + } + + html += "
" + + + "" + + "" + + "" + + + + + "
"; + } + + jq.html(html); + + + if (!jq.data("mouse")) { + $("#" + box_id).hide(); + } + + jq.mouseover(function(e) { + jq.data({"mouse": true}); + $("#" + box_id).show(); + }); + + jq.mouseout(function(e) { + jq.data({"mouse": false}); + $("#" + box_id).hide(); + }); + + $("#" + transfer_id).click(function() { + var xten = prompt("Enter Extension"); + if (xten) { + confMan.modCommand("transfer", x, xten); + } + }); + + $("#" + kick_id).click(function() { + confMan.modCommand("kick", x); + }); + + + $("#" + layer_set_id).click(function() { + var cid = prompt("Please enter layer ID", ""); + if (cid) { + confMan.modCommand("vid-layer", x, cid); + } + }); + + $("#" + layer_next_id).click(function() { + confMan.modCommand("vid-layer", x, "next"); + }); + $("#" + layer_prev_id).click(function() { + confMan.modCommand("vid-layer", x, "prev"); + }); + + $("#" + canvas_in_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-canvas", x, cid); + } + }); + + $("#" + canvas_out_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-watching-canvas", x, cid); + } + }); + + $("#" + canvas_in_next_id).click(function() { + confMan.modCommand("vid-canvas", x, "next"); + }); + $("#" + canvas_in_prev_id).click(function() { + confMan.modCommand("vid-canvas", x, "prev"); + }); + + + $("#" + canvas_out_next_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "next"); + }); + $("#" + canvas_out_prev_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "prev"); + }); + + $("#" + tmute_id).click(function() { + confMan.modCommand("tmute", x); + }); + + if (confMan.params.hasVid) { + $("#" + tvmute_id).click(function() { + confMan.modCommand("tvmute", x); + }); + $("#" + tvpresenter_id).click(function() { + confMan.modCommand("vid-res-id", x, "presenter"); + }); + $("#" + tvfloor_id).click(function() { + confMan.modCommand("vid-floor", x, "force"); + }); + $("#" + vbanner_id).click(function() { + var text = prompt("Please enter text", ""); + if (text) { + confMan.modCommand("vid-banner", x, escape(text)); + } + }); + } + + $("#" + gainup_id).click(function() { + confMan.modCommand("volume_in", x, "up"); + }); + + $("#" + gaindn_id).click(function() { + confMan.modCommand("volume_in", x, "down"); + }); + + $("#" + volup_id).click(function() { + confMan.modCommand("volume_out", x, "up"); + }); + + $("#" + voldn_id).click(function() { + confMan.modCommand("volume_out", x, "down"); + }); + + return html; + } + + var atitle = ""; + var awidth = 0; + + //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px"); + + verto.subscribe(confMan.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(confMan.params.infoCallback) === "function") { + confMan.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(confMan.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(confMan.params.chatCallback) === "function") { + confMan.params.chatCallback(v,e); + } + } + }); + + if (confMan.params.laData.role === "moderator") { + atitle = "Action"; + awidth = 600; + + if (confMan.params.mainModID) { + genMainMod($(confMan.params.mainModID)); + $(confMan.params.displayID).html("Moderator Controls Ready

"); + } else { + $(confMan.params.mainModID).html(""); + } + + verto.subscribe(confMan.params.laData.modChannel, { + handler: function(v, e) { + //console.error("MODDATA:", e.data); + if (confMan.params.onBroadcast) { + confMan.params.onBroadcast(verto, confMan, e.data); + } + + if (e.data["conf-command"] === "list-videoLayouts") { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno; + var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno; + + var x = 0; + var options; + + $(vlselect_id).selectmenu({}); + $(vlselect_id).selectmenu("enable"); + $(vlselect_id).empty(); + + $(vlselect_id).append(new Option("Choose a Layout", "none")); + + if (e.data.responseData) { + var rdata = []; + + for (var i in e.data.responseData) { + rdata.push(e.data.responseData[i].name); + } + + options = rdata.sort(function(a, b) { + var ga = a.substring(0, 6) == "group:" ? true : false; + var gb = b.substring(0, 6) == "group:" ? true : false; + + if ((ga || gb) && ga != gb) { + return ga ? -1 : 1; + } + + return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) ); + }); + + for (var i in options) { + $(vlselect_id).append(new Option(options[i], options[i])); + x++; + } + } + + if (x) { + $(vlselect_id).selectmenu('refresh', true); + } else { + $(vlayout_id).hide(); + } + } + } else { + + if (!confMan.destroyed && confMan.params.displayID) { + $(confMan.params.displayID).html(e.data.response + "

"); + if (confMan.lastTimeout) { + clearTimeout(confMan.lastTimeout); + confMan.lastTimeout = 0; + } + confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready

");}, 4000); + } + } + } + }); + + + if (confMan.params.hasVid) { + confMan.modCommand("list-videoLayouts", null, null); + } + } + + var row_callback = null; + + if (confMan.params.laData.role === "moderator") { + row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) { + if (!aData[5]) { + var $row = $('td:eq(5)', nRow); + genControls($row, aData); + + if (confMan.params.onLaRow) { + confMan.params.onLaRow(verto, confMan, $row, aData); + } + } + }; + } + + confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), { + subParams: { + callID: confMan.params.dialog ? confMan.params.dialog.callID : null + }, + + "onChange": function(obj, args) { + $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)"); + if (confMan.params.onLaChange) { + confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args); + } + }, + + "aaData": [], + "aoColumns": [ + { + "sTitle": "ID", + "sWidth": "50" + }, + { + "sTitle": "Number", + "sWidth": "250" + }, + { + "sTitle": "Name", + "sWidth": "250" + }, + { + "sTitle": "Codec", + "sWidth": "100" + }, + { + "sTitle": "Status", + "sWidth": confMan.params.hasVid ? "200px" : "150px" + }, + { + "sTitle": atitle, + "sWidth": awidth, + } + ], + "bAutoWidth": true, + "bDestroy": true, + "bSort": false, + "bInfo": false, + "bFilter": false, + "bLengthChange": false, + "bPaginate": false, + "iDisplayLength": 1400, + + "oLanguage": { + "sEmptyTable": "The Conference is Empty....." + }, + + "fnRowCallback": row_callback + + }); + }; + + $.verto.confMan.prototype.modCommand = function(cmd, id, value) { + var confMan = this; + + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.confMan.prototype.sendChat = function(message, type) { + var confMan = this; + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + + $.verto.confMan.prototype.destroy = function() { + var confMan = this; + + confMan.destroyed = true; + + if (confMan.lt) { + confMan.lt.destroy(); + } + + if (confMan.params.laData.chatChannel) { + confMan.verto.unsubscribe(confMan.params.laData.chatChannel); + } + + if (confMan.params.laData.modChannel) { + confMan.verto.unsubscribe(confMan.params.laData.modChannel); + } + + if (confMan.params.mainModID) { + $(confMan.params.mainModID).html(""); + } + }; + + $.verto.dialog = function(direction, verto, params) { + var dialog = this; + + dialog.params = $.extend({ + useVideo: verto.options.useVideo, + useStereo: verto.options.useStereo, + screenShare: false, + useCamera: false, + useMic: verto.options.deviceParams.useMic, + useSpeak: verto.options.deviceParams.useSpeak, + tag: verto.options.tag, + localTag: verto.options.localTag, + login: verto.options.login, + videoParams: verto.options.videoParams, + useStream: verto.options.useStream, + }, params); + + + if (!dialog.params.screenShare) { + dialog.params.useCamera = verto.options.deviceParams.useCamera; + } + + 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; + dialog.screenShare = params.screenShare || false; + dialog.useCamera = dialog.params.useCamera; + dialog.useMic = dialog.params.useMic; + dialog.useSpeak = dialog.params.useSpeak; + + if (dialog.params.callID) { + dialog.callID = dialog.params.callID; + } else { + dialog.callID = dialog.params.callID = generateGUID(); + } + + if (typeof(dialog.params.tag) === "function") { + dialog.params.tag = dialog.params.tag(); + } + + if (dialog.params.tag) { + dialog.audioStream = document.getElementById(dialog.params.tag); + + if (dialog.params.useVideo) { + dialog.videoStream = dialog.audioStream; + } + } //else conjure one TBD + + if (dialog.params.localTag) { + dialog.localVideo = document.getElementById(dialog.params.localTag); + } + + dialog.verto.dialogs[dialog.callID] = dialog; + + var RTCcallbacks = {}; + + if (dialog.direction == $.verto.enum.direction.inbound) { + if (dialog.params.display_direction === "outbound") { + dialog.params.remote_caller_id_name = dialog.params.caller_id_name; + dialog.params.remote_caller_id_number = dialog.params.caller_id_number; + } else { + dialog.params.remote_caller_id_name = dialog.params.callee_id_name; + dialog.params.remote_caller_id_number = dialog.params.callee_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) { + console.log("RECV " + rtc.type + " SDP", rtc.mediaData.SDP); + + if (dialog.state == $.verto.enum.state.requesting || dialog.state == $.verto.enum.state.answering || dialog.state == $.verto.enum.state.active) { + location.reload(); + return; + } + + if (rtc.type == "offer") { + if (dialog.state == $.verto.enum.state.active) { + dialog.setState($.verto.enum.state.requesting); + dialog.sendMethod("verto.attach", { + sdp: rtc.mediaData.SDP + }); + } else { + dialog.setState($.verto.enum.state.requesting); + + dialog.sendMethod("verto.invite", { + sdp: rtc.mediaData.SDP + }); + } + } else { //answer + dialog.setState($.verto.enum.state.answering); + + dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", { + sdp: dialog.rtc.mediaData.SDP + }); + } + }; + + RTCcallbacks.onICE = function(rtc) { + //console.log("cand", rtc.mediaData.candidate); + if (rtc.type == "offer") { + console.log("offer", rtc.mediaData.candidate); + return; + } + }; + + RTCcallbacks.onStream = function(rtc, stream) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onGranted === 'function') { + dialog.callbacks.permissionCallback.onGranted(stream); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onGranted === 'function'){ + dialog.verto.options.permissionCallback.onGranted(stream); + } + console.log("stream started"); + }; + + RTCcallbacks.onRemoteStream = function(rtc, stream) { + if (typeof dialog.callbacks.onRemoteStream === 'function') { + dialog.callbacks.onRemoteStream(stream, dialog); + } + console.log("remote stream started"); + }; + + RTCcallbacks.onError = function(e) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onDenied === 'function') { + dialog.callbacks.permissionCallback.onDenied(); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onDenied === 'function'){ + dialog.verto.options.permissionCallback.onDenied(); + } + console.error("ERROR:", e); + dialog.hangup({cause: "Device or Permission Error"}); + }; + + dialog.rtc = new $.FSRTC({ + callbacks: RTCcallbacks, + localVideo: dialog.screenShare ? null : dialog.localVideo, + useVideo: dialog.params.useVideo ? dialog.videoStream : null, + useAudio: dialog.audioStream, + useStereo: dialog.params.useStereo, + videoParams: dialog.params.videoParams, + audioParams: verto.options.audioParams, + iceServers: verto.options.iceServers, + screenShare: dialog.screenShare, + useCamera: dialog.useCamera, + useMic: dialog.useMic, + useSpeak: dialog.useSpeak, + turnServer: verto.options.turnServer, + useStream: dialog.params.useStream + }); + + 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; + } + + if ((obj.noDialogParams && i != "callID")) { + continue; + } + + obj.dialogParams[i] = dialog.params[i]; + } + + delete obj.noDialogParams; + + dialog.verto.rpcClient.call(method, obj, + + function(e) { + /* Success */ + dialog.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + + // Attach audio output device to video element using device/sink ID. + function find_name(id) { + for (var i in $.verto.audioOutDevices) { + var source = $.verto.audioOutDevices[i]; + if (source.id === id) { + return(source.label); + } + } + + return id; + } + + $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) { + var dialog = this; + var element = dialog.audioStream; + + if (typeof element.sinkId !== 'undefined') { + var devname = find_name(sinkId); + console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname); + + element.setSinkId(sinkId) + .then(function() { + console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId); + if (callback) { + callback(true, devname, arg); + } + }) + .catch(function(error) { + var errorMessage = error; + if (error.name === 'SecurityError') { + errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' + + 'device: ' + error; + } + if (callback) { + callback(false, null, arg); + } + console.error(errorMessage); + }); + } else { + console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.'); + if (callback) { + callback(false, null, arg); + } + } + } + + $.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.log("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.early: + case $.verto.enum.state.active: + + var speaker = dialog.useSpeak; + console.info("Using Speaker: ", speaker); + + if (speaker && speaker !== "any" && speaker !== "none") { + setTimeout(function() { + dialog.setAudioPlaybackDevice(speaker); + }, 500); + } + + break; + + case $.verto.enum.state.trying: + setTimeout(function() { + if (dialog.state == $.verto.enum.state.trying) { + dialog.setState($.verto.enum.state.hangup); + } + }, 30000); + break; + 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: + + if (typeof(dialog.verto.options.tag) === "function") { + $('#' + dialog.params.tag).remove(); + } + + delete dialog.verto.dialogs[dialog.callID]; + if (dialog.params.screenShare) { + dialog.rtc.stopPeer(); + } else { + 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(params) { + var dialog = this; + + if (params) { + if (params.causeCode) { + dialog.causeCode = params.causeCode; + } + + if (params.cause) { + dialog.cause = params.cause; + } + } + + if (!dialog.cause && !dialog.causeCode) { + dialog.cause = "NORMAL_CLEARING"; + } + + 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, dialog.localVideo); + + }; + + $.verto.dialog.prototype.setMute = function(what) { + var dialog = this; + return dialog.rtc.setMute(what); + }; + + $.verto.dialog.prototype.getMute = function() { + var dialog = this; + return dialog.rtc.getMute(); + }; + + $.verto.dialog.prototype.setVideoMute = function(what) { + var dialog = this; + return dialog.rtc.setVideoMute(what); + }; + + $.verto.dialog.prototype.getVideoMute = function() { + var dialog = this; + return dialog.rtc.getVideoMute(); + }; + + $.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.rtt = function(obj) { + var dialog = this; + var pobj = {}; + + if (!obj) { + return false; + } + + pobj.code = obj.code; + pobj.chars = obj.chars; + + + if (pobj.chars || pobj.code) { + dialog.sendMethod("verto.info", { + txt: obj, + noDialogParams: true + }); + } + }; + + $.verto.dialog.prototype.transfer = function(dest, params) { + var dialog = this; + if (dest) { + dialog.sendMethod("verto.modify", { + action: "transfer", + destination: dest, + params: params + }); + } + }; + + $.verto.dialog.prototype.replace = function(replaceCallID, params) { + var dialog = this; + if (replaceCallID) { + dialog.sendMethod("verto.modify", { + action: "replace", + replaceCallID: replaceCallID, + params: params + }); + } + }; + + $.verto.dialog.prototype.hold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "hold", + params: params + }); + }; + + $.verto.dialog.prototype.unhold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "unhold", + params: params + }); + }; + + $.verto.dialog.prototype.toggleHold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "toggleHold", + params: params + }); + }; + + $.verto.dialog.prototype.message = function(msg) { + var dialog = this; + var err = 0; + + msg.from = dialog.params.login; + + 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) { + params = {}; + } + + params.sdp = dialog.params.sdp; + + if (params) { + if (params.useVideo) { + dialog.useVideo(true); + } + dialog.params.callee_id_name = params.callee_id_name; + dialog.params.callee_id_number = params.callee_id_number; + + if (params.useCamera) { + dialog.useCamera = params.useCamera; + } + + if (params.useMic) { + dialog.useMic = params.useMic; + } + + if (params.useSpeak) { + dialog.useSpeak = params.useSpeak; + } + } + + dialog.rtc.createAnswer(params); + dialog.answered = true; + } + }; + + $.verto.dialog.prototype.handleAnswer = function(params) { + var dialog = this; + + dialog.gotAnswer = true; + + 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 { + if (dialog.gotEarly) { + console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying..."); + } else { + console.log("Dialog " + dialog.callID + " Answering Channel"); + dialog.rtc.answer(params.sdp, function() { + dialog.setState($.verto.enum.state.active); + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp); + } + } + + + }; + + $.verto.dialog.prototype.cidString = function(enc) { + var dialog = this; + var party = dialog.params.remote_caller_id_name + (enc ? " <" : " <") + dialog.params.remote_caller_id_number + (enc ? ">" : ">"); + 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); + + }; + + $.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(params) { + var dialog = this; + + if (dialog.state.val >= $.verto.enum.state.early.val) { + return; + } + + dialog.gotEarly = true; + + dialog.rtc.answer(params.sdp, function() { + console.log("Dialog " + dialog.callID + "Establishing early media"); + dialog.setState($.verto.enum.state.early); + + if (dialog.gotAnswer) { + console.log("Dialog " + dialog.callID + "Answering Channel"); + dialog.setState($.verto.enum.state.active); + } + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "EARLY SDP", params.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, + recovering: 1, + ringing: 1, + destroy: 1, + answering: 1, + hangup: 1 + }, + requesting: { + trying: 1, + hangup: 1, + active: 1 + }, + recovering: { + answering: 1, + hangup: 1 + }, + trying: { + active: 1, + early: 1, + hangup: 1 + }, + ringing: { + answering: 1, + hangup: 1 + }, + answering: { + active: 1, + hangup: 1 + }, + active: { + answering: 1, + requesting: 1, + 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 recovering ringing answering early active held hangup destroy purge"); + $.verto.enum.direction = $.verto.ENUM("inbound outbound"); + $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady"); + + $.verto.enum = Object.freeze($.verto.enum); + + $.verto.saved = []; + + $.verto.unloadJobs = []; + + var unloadEventName = 'beforeunload'; + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + if (iOS) { + unloadEventName = 'pagehide'; + } + + $(window).bind(unloadEventName, function() { + for (var f in $.verto.unloadJobs) { + $.verto.unloadJobs[f](); + } + + if ($.verto.haltClosure) + return $.verto.haltClosure(); + + for (var i in $.verto.saved) { + var verto = $.verto.saved[i]; + if (verto) { + verto.purge(); + verto.logout(); + } + } + + return $.verto.warnOnUnload; + }); + + $.verto.videoDevices = []; + $.verto.audioInDevices = []; + $.verto.audioOutDevices = []; + + var checkDevices = function(runtime) { + console.info("enumerating devices"); + var aud_in = [], aud_out = [], vid = []; + var has_video = 0, has_audio = 0; + var Xstream; + + function gotDevices(deviceInfos) { + // Handles being called several times to update labels. Preserve values. + for (var i = 0; i !== deviceInfos.length; ++i) { + var deviceInfo = deviceInfos[i]; + var text = ""; + + console.log(deviceInfo); + console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId); + + if (deviceInfo.kind === 'audioinput') { + text = deviceInfo.label || 'microphone ' + (aud_in.length + 1); + aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text}); + } else if (deviceInfo.kind === 'audiooutput') { + text = deviceInfo.label || 'speaker ' + (aud_out.length + 1); + aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text}); + } else if (deviceInfo.kind === 'videoinput') { + text = deviceInfo.label || 'camera ' + (vid.length + 1); + vid.push({id: deviceInfo.deviceId, kind: "video", label: text}); + } else { + console.log('Some other kind of source/device: ', deviceInfo); + } + } + + + $.verto.videoDevices = vid; + $.verto.audioInDevices = aud_in; + $.verto.audioOutDevices = aud_out; + + console.info("Audio IN Devices", $.verto.audioInDevices); + console.info("Audio Out Devices", $.verto.audioOutDevices); + console.info("Video Devices", $.verto.videoDevices); + + if (Xstream) { + Xstream.getTracks().forEach(function(track) {track.stop();}); + } + + if (runtime) { + runtime(true); + } + } + + + + + function handleError(error) { + console.log('device enumeration error: ', error); + if (runtime) runtime(false); + } + + + function checkTypes(devs) { + for (var i = 0; i !== devs.length; ++i) { + + if (devs[i].kind === 'audioinput') { + has_audio++; + } else if (devs[i].kind === 'videoinput') { + has_video++; + } + } + + navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)}, + function(stream) { + Xstream = stream; + navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); + }, + function(err) { + console.log("The following error occurred: " + err.name); + } + ); + } + + navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError); + + }; + + $.verto.refreshDevices = function(runtime) { + checkDevices(runtime); + } + + $.verto.init = function(obj, runtime) { + if (!obj) { + obj = {}; + } + + if (!obj.skipPermCheck && !obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + checkDevices(runtime); + }, true, true); + } else if (obj.skipPermCheck && !obj.skipDeviceCheck) { + checkDevices(runtime); + } else if (!obj.skipPermCheck && obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + runtime(status); + }, true, true); + } else { + runtime(null); + } + + } + + $.verto.genUUID = function () { + return generateGUID(); + } + + +})(jQuery); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 14393 && + url.indexOf('?transport=udp') === -1; + }); + + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} + +// Determines the intersection of local and remote capabilities. +function getCommonCapabilities(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + + var findCodecByPayloadType = function(pt, codecs) { + pt = parseInt(pt, 10); + for (var i = 0; i < codecs.length; i++) { + if (codecs[i].payloadType === pt || + codecs[i].preferredPayloadType === pt) { + return codecs[i]; + } + } + }; + + var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { + var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); + var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); + return lCodec && rCodec && + lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); + }; + + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate) { + if (lCodec.name.toLowerCase() === 'rtx' && + lCodec.parameters && rCodec.parameters.apt) { + // for RTX we need to find the local rtx that has a apt + // which points to the same local codec as the remote one. + if (!rtxCapabilityMatches(lCodec, rCodec, + localCapabilities.codecs, remoteCapabilities.codecs)) { + continue; + } + } + rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy + // number of channels is the highest common number of channels + rCodec.numChannels = Math.min(lCodec.numChannels, + rCodec.numChannels); + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; +} + +// is action=setLocalDescription with type allowed in signalingState +function isActionAllowedInSignalingState(action, type, signalingState) { + return { + offer: { + setLocalDescription: ['stable', 'have-local-offer'], + setRemoteDescription: ['stable', 'have-remote-offer'] + }, + answer: { + setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], + setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + } + }[type][action].indexOf(signalingState) !== -1; +} + +function maybeAddCandidate(iceTransport, candidate) { + // Edge's internal representation adds some fields therefore + // not all fieldѕ are taken into account. + var alreadyAdded = iceTransport.getRemoteCandidates() + .find(function(remoteCandidate) { + return candidate.foundation === remoteCandidate.foundation && + candidate.ip === remoteCandidate.ip && + candidate.port === remoteCandidate.port && + candidate.priority === remoteCandidate.priority && + candidate.protocol === remoteCandidate.protocol && + candidate.type === remoteCandidate.type; + }); + if (!alreadyAdded) { + iceTransport.addRemoteCandidate(candidate); + } + return !alreadyAdded; +} + + +// https://w3c.github.io/mediacapture-main/#mediastream +// Helper function to add the track to the stream and +// dispatch the event ourselves. +function addTrackToStreamAndFireEvent(track, stream) { + stream.addTrack(track); + var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function removeTrackFromStreamAndFireEvent(track, stream) { + stream.removeTrack(track); + var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function fireAddTrack(pc, track, receiver, streams) { + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = {receiver: receiver}; + trackEvent.streams = streams; + window.setTimeout(function() { + pc._dispatchEvent('track', trackEvent); + }); +} + +function makeError(name, description) { + var e = new Error(description); + e.name = name; + return e; +} + +module.exports = function(window, edgeVersion) { + var RTCPeerConnection = function(config) { + var pc = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + pc[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.canTrickleIceCandidates = null; + + this.needNegotiation = false; + + this.localStreams = []; + this.remoteStreams = []; + + this.localDescription = null; + this.remoteDescription = null; + + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.iceGatheringState = 'new'; + + config = JSON.parse(JSON.stringify(config || {})); + + this.usingBundle = config.bundlePolicy === 'max-bundle'; + if (config.rtcpMuxPolicy === 'negotiate') { + throw(makeError('NotSupportedError', + 'rtcpMuxPolicy \'negotiate\' is not supported')); + } else if (!config.rtcpMuxPolicy) { + config.rtcpMuxPolicy = 'require'; + } + + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + break; + default: + config.iceTransportPolicy = 'all'; + break; + } + + switch (config.bundlePolicy) { + case 'balanced': + case 'max-compat': + case 'max-bundle': + break; + default: + config.bundlePolicy = 'balanced'; + break; + } + + config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); + + this._iceGatherers = []; + if (config.iceCandidatePoolSize) { + for (var i = config.iceCandidatePoolSize; i > 0; i--) { + this._iceGatherers = new window.RTCIceGatherer({ + iceServers: config.iceServers, + gatherPolicy: config.iceTransportPolicy + }); + } + } else { + config.iceCandidatePoolSize = 0; + } + + this._config = config; + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + this._sdpSessionId = SDPUtils.generateSessionId(); + this._sdpSessionVersion = 0; + + this._dtlsRole = undefined; // role for a=setup to use in answers. + + this._isClosed = false; + }; + + // set up event handlers on prototype + RTCPeerConnection.prototype.onicecandidate = null; + RTCPeerConnection.prototype.onaddstream = null; + RTCPeerConnection.prototype.ontrack = null; + RTCPeerConnection.prototype.onremovestream = null; + RTCPeerConnection.prototype.onsignalingstatechange = null; + RTCPeerConnection.prototype.oniceconnectionstatechange = null; + RTCPeerConnection.prototype.onicegatheringstatechange = null; + RTCPeerConnection.prototype.onnegotiationneeded = null; + RTCPeerConnection.prototype.ondatachannel = null; + + RTCPeerConnection.prototype._dispatchEvent = function(name, event) { + if (this._isClosed) { + return; + } + this.dispatchEvent(event); + if (typeof this['on' + name] === 'function') { + this['on' + name](event); + } + }; + + RTCPeerConnection.prototype._emitGatheringStateChange = function() { + var event = new Event('icegatheringstatechange'); + this._dispatchEvent('icegatheringstatechange', event); + }; + + RTCPeerConnection.prototype.getConfiguration = function() { + return this._config; + }; + + RTCPeerConnection.prototype.getLocalStreams = function() { + return this.localStreams; + }; + + RTCPeerConnection.prototype.getRemoteStreams = function() { + return this.remoteStreams; + }; + + // internal helper to create a transceiver object. + // (whih is not yet the same as the WebRTC 1.0 transceiver) + RTCPeerConnection.prototype._createTransceiver = function(kind) { + var hasBundleTransport = this.transceivers.length > 0; + var transceiver = { + track: null, + iceGatherer: null, + iceTransport: null, + dtlsTransport: null, + localCapabilities: null, + remoteCapabilities: null, + rtpSender: null, + rtpReceiver: null, + kind: kind, + mid: null, + sendEncodingParameters: null, + recvEncodingParameters: null, + stream: null, + associatedRemoteMediaStreams: [], + wantReceive: true + }; + if (this.usingBundle && hasBundleTransport) { + transceiver.iceTransport = this.transceivers[0].iceTransport; + transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; + } else { + var transports = this._createIceAndDtlsTransports(); + transceiver.iceTransport = transports.iceTransport; + transceiver.dtlsTransport = transports.dtlsTransport; + } + this.transceivers.push(transceiver); + return transceiver; + }; + + RTCPeerConnection.prototype.addTrack = function(track, stream) { + var alreadyExists = this.transceivers.find(function(s) { + return s.track === track; + }); + + if (alreadyExists) { + throw makeError('InvalidAccessError', 'Track already exists.'); + } + + if (this.signalingState === 'closed') { + throw makeError('InvalidStateError', + 'Attempted to call addTrack on a closed peerconnection.'); + } + + var transceiver; + for (var i = 0; i < this.transceivers.length; i++) { + if (!this.transceivers[i].track && + this.transceivers[i].kind === track.kind) { + transceiver = this.transceivers[i]; + } + } + if (!transceiver) { + transceiver = this._createTransceiver(track.kind); + } + + this._maybeFireNegotiationNeeded(); + + if (this.localStreams.indexOf(stream) === -1) { + this.localStreams.push(stream); + } + + transceiver.track = track; + transceiver.stream = stream; + transceiver.rtpSender = new window.RTCRtpSender(track, + transceiver.dtlsTransport); + return transceiver.rtpSender; + }; + + RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + if (edgeVersion >= 15025) { + stream.getTracks().forEach(function(track) { + pc.addTrack(track, stream); + }); + } else { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + // Fixed in 15025 (or earlier) + var clonedStream = stream.clone(); + stream.getTracks().forEach(function(track, idx) { + var clonedTrack = clonedStream.getTracks()[idx]; + track.addEventListener('enabled', function(event) { + clonedTrack.enabled = event.enabled; + }); + }); + clonedStream.getTracks().forEach(function(track) { + pc.addTrack(track, clonedStream); + }); + } + }; + + RTCPeerConnection.prototype.removeTrack = function(sender) { + if (!(sender instanceof window.RTCRtpSender)) { + throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.'); + } + + var transceiver = this.transceivers.find(function(t) { + return t.rtpSender === sender; + }); + + if (!transceiver) { + throw makeError('InvalidAccessError', + 'Sender was not created by this connection.'); + } + var stream = transceiver.stream; + + transceiver.rtpSender.stop(); + transceiver.rtpSender = null; + transceiver.track = null; + transceiver.stream = null; + + // remove the stream from the set of local streams + var localStreams = this.transceivers.map(function(t) { + return t.stream; + }); + if (localStreams.indexOf(stream) === -1 && + this.localStreams.indexOf(stream) > -1) { + this.localStreams.splice(this.localStreams.indexOf(stream), 1); + } + + this._maybeFireNegotiationNeeded(); + }; + + RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + stream.getTracks().forEach(function(track) { + var sender = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (sender) { + pc.removeTrack(sender); + } + }); + }; + + RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + + RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, + usingBundle) { + var pc = this; + if (usingBundle && sdpMLineIndex > 0) { + return this.transceivers[0].iceGatherer; + } else if (this._iceGatherers.length) { + return this._iceGatherers.shift(); + } + var iceGatherer = new window.RTCIceGatherer({ + iceServers: this._config.iceServers, + gatherPolicy: this._config.iceTransportPolicy + }); + Object.defineProperty(iceGatherer, 'state', + {value: 'new', writable: true} + ); + + this.transceivers[sdpMLineIndex].candidates = []; + this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + iceGatherer.state = end ? 'completed' : 'gathering'; + if (pc.transceivers[sdpMLineIndex].candidates !== null) { + pc.transceivers[sdpMLineIndex].candidates.push(event.candidate); + } + }; + iceGatherer.addEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + return iceGatherer; + }; + + // start gathering from an RTCIceGatherer. + RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { + var pc = this; + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer.onlocalcandidate) { + return; + } + var candidates = this.transceivers[sdpMLineIndex].candidates; + this.transceivers[sdpMLineIndex].candidates = null; + iceGatherer.removeEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + iceGatherer.onlocalcandidate = function(evt) { + if (pc.usingBundle && sdpMLineIndex > 0) { + // if we know that we use bundle we can drop candidates with + // ѕdpMLineIndex > 0. If we don't do this then our state gets + // confused since we dispose the extra ice gatherer. + return; + } + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + var end = !cand || Object.keys(cand).length === 0; + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { + iceGatherer.state = 'completed'; + } + } else { + if (iceGatherer.state === 'new') { + iceGatherer.state = 'gathering'; + } + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + // update local description. + var sections = SDPUtils.splitSections(pc.localDescription.sdp); + if (!end) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=end-of-candidates\r\n'; + } + pc.localDescription.sdp = sections.join(''); + var complete = pc.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + if (pc.iceGatheringState !== 'gathering') { + pc.iceGatheringState = 'gathering'; + pc._emitGatheringStateChange(); + } + + // Emit candidate. Also emit null candidate when all gatherers are + // complete. + if (!end) { + pc._dispatchEvent('icecandidate', event); + } + if (complete) { + pc._dispatchEvent('icecandidate', new Event('icecandidate')); + pc.iceGatheringState = 'complete'; + pc._emitGatheringStateChange(); + } + }; + + // emit already gathered candidates. + window.setTimeout(function() { + candidates.forEach(function(candidate) { + var e = new Event('RTCIceGatherEvent'); + e.candidate = candidate; + iceGatherer.onlocalcandidate(e); + }); + }, 0); + }; + + // Create ICE transport and DTLS transport. + RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { + var pc = this; + var iceTransport = new window.RTCIceTransport(null); + iceTransport.onicestatechange = function() { + pc._updateConnectionState(); + }; + + var dtlsTransport = new window.RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + pc._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itpc. + Object.defineProperty(dtlsTransport, 'state', + {value: 'failed', writable: true}); + pc._updateConnectionState(); + }; + + return { + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Destroy ICE gatherer, ICE transport and DTLS transport. + // Without triggering the callbacks. + RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( + sdpMLineIndex) { + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer) { + delete iceGatherer.onlocalcandidate; + delete this.transceivers[sdpMLineIndex].iceGatherer; + } + var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; + if (iceTransport) { + delete iceTransport.onicestatechange; + delete this.transceivers[sdpMLineIndex].iceTransport; + } + var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; + if (dtlsTransport) { + delete dtlsTransport.ondtlsstatechange; + delete dtlsTransport.onerror; + delete this.transceivers[sdpMLineIndex].dtlsTransport; + } + }; + + // Start the RTP Sender and Receiver for a transceiver. + RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName, + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { + // remove RTX field in Edge 14942 + if (transceiver.kind === 'video' + && transceiver.recvEncodingParameters + && edgeVersion < 15019) { + transceiver.recvEncodingParameters.forEach(function(p) { + delete p.rtx; + }); + } + if (transceiver.recvEncodingParameters.length) { + params.encodings = transceiver.recvEncodingParameters; + } + params.rtcp = { + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.rtcpParameters.cname) { + params.rtcp.cname = transceiver.rtcpParameters.cname; + } + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + RTCPeerConnection.prototype.setLocalDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setLocalDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set local ' + description.type + + ' in state ' + pc.signalingState)); + } + + var sections; + var sessionpart; + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + pc.transceivers[sdpMLineIndex].localCapabilities = caps; + }); + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + pc._gather(transceiver.mid, sdpMLineIndex); + }); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc.remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = pc.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + + if (!rejected && !transceiver.isDatachannel) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!pc.usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver.mid, sdpMLineIndex); + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + // Calculate intersection of capabilities. + var params = getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + pc._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.setRemoteDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setRemoteDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set remote ' + description.type + + ' in state ' + pc.signalingState)); + } + + var streams = {}; + this.remoteStreams.forEach(function(stream) { + streams[stream.id] = stream; + }); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + var usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + this.usingBundle = usingBundle; + var iceOptions = SDPUtils.matchPrefix(sessionpart, + 'a=ice-options:')[0]; + if (iceOptions) { + this.canTrickleIceCandidates = iceOptions.substr(14).split(' ') + .indexOf('trickle') >= 0; + } else { + this.canTrickleIceCandidates = false; + } + + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var kind = SDPUtils.getKind(mediaSection); + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + var protocol = lines[0].substr(2).split(' ')[2]; + + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + var remoteMsid = SDPUtils.parseMsid(mediaSection); + + var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); + + // Reject datachannels which are not implemented yet. + if (kind === 'application' && protocol === 'DTLS/SCTP') { + pc.transceivers[sdpMLineIndex] = { + mid: mid, + isDatachannel: true + }; + return; + } + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === 1; + }); + + // Check if we can use BUNDLE and dispose transports. + if ((description.type === 'offer' || description.type === 'answer') && + !rejected && usingBundle && sdpMLineIndex > 0 && + pc.transceivers[sdpMLineIndex]) { + pc._disposeIceAndDtlsTransports(sdpMLineIndex); + pc.transceivers[sdpMLineIndex].iceGatherer = + pc.transceivers[0].iceGatherer; + pc.transceivers[sdpMLineIndex].iceTransport = + pc.transceivers[0].iceTransport; + pc.transceivers[sdpMLineIndex].dtlsTransport = + pc.transceivers[0].dtlsTransport; + if (pc.transceivers[sdpMLineIndex].rtpSender) { + pc.transceivers[sdpMLineIndex].rtpSender.setTransport( + pc.transceivers[0].dtlsTransport); + } + if (pc.transceivers[sdpMLineIndex].rtpReceiver) { + pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( + pc.transceivers[0].dtlsTransport); + } + } + if (description.type === 'offer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex] || + pc._createTransceiver(kind); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + usingBundle); + } + + if (cands.length && transceiver.iceTransport.state === 'new') { + if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { + transceiver.iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); + + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + + sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + var isNewTrack = false; + if (direction === 'sendrecv' || direction === 'sendonly') { + isNewTrack = !transceiver.rtpReceiver; + rtpReceiver = transceiver.rtpReceiver || + new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + + if (isNewTrack) { + var stream; + track = rtpReceiver.track; + // FIXME: does not work with Plan B. + if (remoteMsid && remoteMsid.stream === '-') { + // no-op. a stream id of '-' means: no associated stream. + } else if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + Object.defineProperty(streams[remoteMsid.stream], 'id', { + get: function() { + return remoteMsid.stream; + } + }); + } + Object.defineProperty(track, 'id', { + get: function() { + return remoteMsid.track; + } + }); + stream = streams[remoteMsid.stream]; + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + stream = streams.default; + } + if (stream) { + addTrackToStreamAndFireEvent(track, stream); + transceiver.associatedRemoteMediaStreams.push(stream); + } + receiverList.push([track, rtpReceiver, stream]); + } + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { + transceiver.associatedRemoteMediaStreams.forEach(function(s) { + var nativeTrack = s.getTracks().find(function(t) { + return t.id === transceiver.rtpReceiver.track.id; + }); + if (nativeTrack) { + removeTrackFromStreamAndFireEvent(nativeTrack, s); + } + }); + transceiver.associatedRemoteMediaStreams = []; + } + + transceiver.localCapabilities = localCapabilities; + transceiver.remoteCapabilities = remoteCapabilities; + transceiver.rtpReceiver = rtpReceiver; + transceiver.rtcpParameters = rtcpParameters; + transceiver.sendEncodingParameters = sendEncodingParameters; + transceiver.recvEncodingParameters = recvEncodingParameters; + + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + pc._transceive(pc.transceivers[sdpMLineIndex], + false, + isNewTrack); + } else if (description.type === 'answer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + pc.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + pc.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + + if (cands.length && iceTransport.state === 'new') { + if ((isIceLite || isComplete) && + (!usingBundle || sdpMLineIndex === 0)) { + iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + if (!usingBundle || sdpMLineIndex === 0) { + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + pc._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams.default); + receiverList.push([track, rtpReceiver, streams.default]); + } + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + if (this._dtlsRole === undefined) { + this._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + } + + this.remoteDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + Object.keys(streams).forEach(function(sid) { + var stream = streams[sid]; + if (stream.getTracks().length) { + if (pc.remoteStreams.indexOf(stream) === -1) { + pc.remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + window.setTimeout(function() { + pc._dispatchEvent('addstream', event); + }); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + if (stream.id !== item[2].id) { + return; + } + fireAddTrack(pc, track, receiver, [stream]); + }); + } + }); + receiverList.forEach(function(item) { + if (item[2]) { + return; + } + fireAddTrack(pc, item[0], item[1], []); + }); + + // check whether addIceCandidate({}) was called within four seconds after + // setRemoteDescription. + window.setTimeout(function() { + if (!(pc && pc.transceivers)) { + return; + } + pc.transceivers.forEach(function(transceiver) { + if (transceiver.iceTransport && + transceiver.iceTransport.state === 'new' && + transceiver.iceTransport.getRemoteCandidates().length > 0) { + console.warn('Timeout for addRemoteCandidate. Consider sending ' + + 'an end-of-candidates notification'); + transceiver.iceTransport.addRemoteCandidate({}); + } + }); + }, 4000); + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._isClosed = true; + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + RTCPeerConnection.prototype._updateSignalingState = function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this._dispatchEvent('signalingstatechange', event); + }; + + // Determine whether to fire the negotiationneeded event. + RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { + var pc = this; + if (this.signalingState !== 'stable' || this.needNegotiation === true) { + return; + } + this.needNegotiation = true; + window.setTimeout(function() { + if (pc.needNegotiation === false) { + return; + } + pc.needNegotiation = false; + var event = new Event('negotiationneeded'); + pc._dispatchEvent('negotiationneeded', event); + }, 0); + }; + + // Update the connection state. + RTCPeerConnection.prototype._updateConnectionState = function() { + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this._dispatchEvent('iceconnectionstatechange', event); + } + }; + + RTCPeerConnection.prototype.createOffer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createOffer after close')); + } + + var numAudioTracks = this.transceivers.filter(function(t) { + return t.kind === 'audio'; + }).length; + var numVideoTracks = this.transceivers.filter(function(t) { + return t.kind === 'video'; + }).length; + + // Determine number of audio and video tracks we need to send/recv. + var offerOptions = arguments[0]; + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + if (offerOptions.offerToReceiveAudio === true) { + numAudioTracks = 1; + } else if (offerOptions.offerToReceiveAudio === false) { + numAudioTracks = 0; + } else { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + } + if (offerOptions.offerToReceiveVideo !== undefined) { + if (offerOptions.offerToReceiveVideo === true) { + numVideoTracks = 1; + } else if (offerOptions.offerToReceiveVideo === false) { + numVideoTracks = 0; + } else { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + + this.transceivers.forEach(function(transceiver) { + if (transceiver.kind === 'audio') { + numAudioTracks--; + if (numAudioTracks < 0) { + transceiver.wantReceive = false; + } + } else if (transceiver.kind === 'video') { + numVideoTracks--; + if (numVideoTracks < 0) { + transceiver.wantReceive = false; + } + } + }); + + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + this._createTransceiver('audio'); + numAudioTracks--; + } + if (numVideoTracks > 0) { + this._createTransceiver('video'); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = transceiver.track; + var kind = transceiver.kind; + var mid = SDPUtils.generateIdentifier(); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + pc.usingBundle); + } + + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + localCapabilities.codecs.forEach(function(codec) { + // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 + // by adding level-asymmetry-allowed=1 + if (codec.name === 'H264' && + codec.parameters['level-asymmetry-allowed'] === undefined) { + codec.parameters['level-asymmetry-allowed'] = '1'; + } + }); + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + // add RTX + if (edgeVersion >= 15019 && kind === 'video' && + !sendEncodingParameters[0].rtx) { + sendEncodingParameters[0].rtx = { + ssrc: sendEncodingParameters[0].ssrc + 1 + }; + } + } + + if (transceiver.wantReceive) { + transceiver.rtpReceiver = new window.RTCRtpReceiver( + transceiver.dtlsTransport, kind); + } + + transceiver.localCapabilities = localCapabilities; + transceiver.sendEncodingParameters = sendEncodingParameters; + }); + + // always offer BUNDLE and dispose on return if not supported. + if (this._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + sdp += 'a=ice-options:trickle\r\n'; + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + sdp += writeMediaSection(transceiver, transceiver.localCapabilities, + 'offer', transceiver.stream, pc._dtlsRole); + sdp += 'a=rtcp-rsize\r\n'; + + if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !pc.usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { + cand.component = 1; + sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'completed') { + sdp += 'a=end-of-candidates\r\n'; + } + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.createAnswer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createAnswer after close')); + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + var mediaSectionsInOffer = SDPUtils.splitSections( + this.remoteDescription.sdp).length - 1; + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + if (sdpMLineIndex + 1 > mediaSectionsInOffer) { + return; + } + if (transceiver.isDatachannel) { + sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=mid:' + transceiver.mid + '\r\n'; + return; + } + + // FIXME: look at direction. + if (transceiver.stream) { + var localTrack; + if (transceiver.kind === 'audio') { + localTrack = transceiver.stream.getAudioTracks()[0]; + } else if (transceiver.kind === 'video') { + localTrack = transceiver.stream.getVideoTracks()[0]; + } + if (localTrack) { + // add RTX + if (edgeVersion >= 15019 && transceiver.kind === 'video' && + !transceiver.sendEncodingParameters[0].rtx) { + transceiver.sendEncodingParameters[0].rtx = { + ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 + }; + } + } + } + + // Calculate intersection of capabilities. + var commonCapabilities = getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + var hasRtx = commonCapabilities.codecs.filter(function(c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + sdp += writeMediaSection(transceiver, commonCapabilities, + 'answer', transceiver.stream, pc._dtlsRole); + if (transceiver.rtcpParameters && + transceiver.rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var sections; + if (!candidate || candidate.candidate === '') { + for (var j = 0; j < this.transceivers.length; j++) { + if (this.transceivers[j].isDatachannel) { + continue; + } + this.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[j + 1] += 'a=end-of-candidates\r\n'; + this.remoteDescription.sdp = sections.join(''); + if (this.usingBundle) { + break; + } + } + } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { + throw new TypeError('sdpMLineIndex or sdpMid required'); + } else if (!this.remoteDescription) { + return Promise.reject(makeError('InvalidStateError', + 'Can not add ICE candidate without a remote description')); + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[sdpMLineIndex]; + if (transceiver) { + if (transceiver.isDatachannel) { + return Promise.resolve(); + } + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return Promise.resolve(); + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component && cand.component !== 1) { + return Promise.resolve(); + } + // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && + transceiver.iceTransport !== this.transceivers[0].iceTransport)) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + + // update the remoteDescription. + var candidateString = candidate.candidate.trim(); + if (candidateString.indexOf('a=') === 0) { + candidateString = candidateString.substr(2); + } + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[sdpMLineIndex + 1] += 'a=' + + (cand.type ? candidateString : 'end-of-candidates') + + '\r\n'; + this.remoteDescription.sdp = sections.join(''); + } else { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var fixStatsType = function(stat) { + return { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[stat.type] || stat.type; + }; + return new Promise(function(resolve) { + // shim getStats with maplike support + var results = new Map(); + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + result[id].type = fixStatsType(result[id]); + results.set(id, result[id]); + }); + }); + resolve(results); + }); + }); + }; + + // legacy callback shims. Should be moved to adapter.js some days. + var methods = ['createOffer', 'createAnswer']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[0] === 'function' || + typeof args[1] === 'function') { // legacy + return nativeMethod.apply(this, [arguments[2]]) + .then(function(description) { + if (typeof args[0] === 'function') { + args[0].apply(null, [description]); + } + }, function(error) { + if (typeof args[1] === 'function') { + args[1].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function' || + typeof args[2] === 'function') { // legacy + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }, function(error) { + if (typeof args[2] === 'function') { + args[2].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // getStats is special. It doesn't have a spec legacy method yet we support + // getStats(something, cb) without error callbacks. + ['getStats'].forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function') { + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + return RTCPeerConnection; +}; + +},{"sdp":2}],2:[function(require,module,exports){ + /* eslint-env node */ +'use strict'; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random().toString(36).substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob.trim().split('\n').map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split('\nm='); + return parts.map(function(part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + if (candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.ufrag); + } + return 'candidate:' + sdp.join(' '); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(' '); +} + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== 'sendrecv' + ? '/' + headerExtension.direction + : '') + + ' ' + headerExtension.uri + '\r\n'; +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + params.push(param + '=' + codec.parameters[param]); + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + if (mid) { + return mid.substr(6); + } +} + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1] + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, + 'a=fingerprint:'); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; +}; +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.split(' '); + parts.shift(); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + ssrc: secondarySsrc + } + }; + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: secondarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 + - (50 * 40 * 8); + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return {stream: parts[0], track: parts[1]}; + } + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'msid'; + }); + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return {stream: parts[0], track: parts[1]}; + } +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.random().toString().substr(2, 21); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.direction) { + sdp += 'a=' + transceiver.direction + '\r\n'; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + // spec. + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + + // for Chrome. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + + transceiver.sendEncodingParameters[0].ssrc + ' ' + + transceiver.sendEncodingParameters[0].rtx.ssrc + + '\r\n'; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return { + kind: mline[0].substr(2), + port: parseInt(mline[1], 10), + protocol: mline[2], + fmt: mline.slice(3).join(' ') + }; +}; + +// Expose public methods. +if (typeof module === 'object') { + module.exports = SDPUtils; +} + +},{}],3:[function(require,module,exports){ +(function (global){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var adapterFactory = require('./adapter_factory.js'); +module.exports = adapterFactory({window: global.window}); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./adapter_factory.js":4}],4:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var utils = require('./utils'); +// Shimming starts here. +module.exports = function(dependencies, opts) { + var window = dependencies && dependencies.window; + + var options = { + shimChrome: true, + shimFirefox: true, + shimEdge: true, + shimSafari: true, + }; + + for (var key in opts) { + if (hasOwnProperty.call(opts, key)) { + options[key] = opts[key]; + } + } + + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + var commonShim = require('./common_shim') || null; + + // Export to the adapter global object visible in the browser. + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || + !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = chromeShim; + commonShim.shimCreateObjectURL(window); + + chromeShim.shimGetUserMedia(window); + chromeShim.shimMediaStream(window); + chromeShim.shimSourceObject(window); + chromeShim.shimPeerConnection(window); + chromeShim.shimOnTrack(window); + chromeShim.shimAddTrackRemoveTrack(window); + chromeShim.shimGetSendersWithDtmf(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || + !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = firefoxShim; + commonShim.shimCreateObjectURL(window); + + firefoxShim.shimGetUserMedia(window); + firefoxShim.shimSourceObject(window); + firefoxShim.shimPeerConnection(window); + firefoxShim.shimOnTrack(window); + firefoxShim.shimRemoveStream(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { + logging('MS edge shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = edgeShim; + commonShim.shimCreateObjectURL(window); + + edgeShim.shimGetUserMedia(window); + edgeShim.shimPeerConnection(window); + edgeShim.shimReplaceTrack(window); + + // the edge shim implements the full RTCIceCandidate object. + + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = safariShim; + commonShim.shimCreateObjectURL(window); + + safariShim.shimRTCIceServerUrls(window); + safariShim.shimCallbacksAPI(window); + safariShim.shimLocalStreamsAPI(window); + safariShim.shimRemoteStreamsAPI(window); + safariShim.shimTrackEventTransceiver(window); + safariShim.shimGetUserMedia(window); + safariShim.shimCreateOfferLegacy(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + default: + logging('Unsupported browser!'); + break; + } + + return adapter; +}; + +},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],5:[function(require,module,exports){ + +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimMediaStream: function(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + this.addEventListener('track', this._ontrack = f); + } + }); + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + if (!pc._ontrackpoly) { + pc._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = {track: te.track}; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = {track: track}; + } + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + }; + pc.addEventListener('addstream', pc._ontrackpoly); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } else if (!('RTCRtpTransceiver' in window)) { + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + if (!e.transceiver) { + e.transceiver = {receiver: e.receiver}; + } + return e; + }); + } + }, + + shimGetSendersWithDtmf: function(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if (typeof window === 'object' && window.RTCPeerConnection && + !('getSenders' in window.RTCPeerConnection.prototype) && + 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function(pc, track) { + return { + track: track, + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + }, + _pc: pc + }; + }; + + // augment addTrack when getSenders is not available. + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + var sender = origAddTrack.apply(pc, arguments); + if (!sender) { + sender = shimSenderWithDtmf(pc, track); + pc._senders.push(sender); + } + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + origRemoveTrack.apply(pc, arguments); + var idx = pc._senders.indexOf(sender); + if (idx !== -1) { + pc._senders.splice(idx, 1); + } + }; + } + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origAddStream.apply(pc, [stream]); + stream.getTracks().forEach(function(track) { + pc._senders.push(shimSenderWithDtmf(pc, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origRemoveStream.apply(pc, [stream]); + + stream.getTracks().forEach(function(track) { + var sender = pc._senders.find(function(s) { + return s.track === track; + }); + if (sender) { + pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender + } + }); + }; + } else if (typeof window === 'object' && window.RTCPeerConnection && + 'getSenders' in window.RTCPeerConnection.prototype && + 'createDTMFSender' in window.RTCPeerConnection.prototype && + window.RTCRtpSender && + !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + }, + + shimSourceObject: function(window) { + var URL = window && window.URL; + + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return undefined; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimAddTrackRemoveTrackWithNative: function(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function(streamId) { + return pc._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + var sender = origAddTrack.apply(this, arguments); + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + var existingSenders = pc.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = pc.getSenders().filter(function(newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function(streamId) { + var idx = pc._shimmedLocalStreams[streamId].indexOf(sender); + if (idx !== -1) { + pc._shimmedLocalStreams[streamId].splice(idx, 1); + } + if (pc._shimmedLocalStreams[streamId].length === 1) { + delete pc._shimmedLocalStreams[streamId]; + } + }); + } + return origRemoveTrack.apply(this, arguments); + }; + }, + + shimAddTrackRemoveTrack: function(window) { + var browserDetails = utils.detectBrowser(window); + // shim addTrack and removeTrack. + if (window.RTCPeerConnection.prototype.addTrack && + browserDetails.version >= 65) { + return this.shimAddTrackRemoveTrackWithNative(window); + } + + // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + var origGetLocalStreams = window.RTCPeerConnection.prototype + .getLocalStreams; + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + var nativeStreams = origGetLocalStreams.apply(this); + pc._reverseStreams = pc._reverseStreams || {}; + return nativeStreams.map(function(stream) { + return pc._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + if (!pc._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + stream = newStream; + } + origAddStream.apply(pc, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]); + delete pc._reverseStreams[(pc._streams[stream.id] ? + pc._streams[stream.id].id : stream.id)]; + delete pc._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + var streams = [].slice.call(arguments, 1); + if (streams.length !== 1 || + !streams[0].getTracks().find(function(t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException( + 'The adapter.js addTrack polyfill only supports a single ' + + ' stream which is associated with the specified track.', + 'NotSupportedError'); + } + + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + var oldStream = pc._streams[stream.id]; + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); + + // Trigger ONN async. + Promise.resolve().then(function() { + pc.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + pc.addStream(newStream); + } + return pc.getSenders().find(function(s) { + return s.track === track; + }); + }; + + // replace the internal stream id with the external one and + // vice versa. + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), + externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), + internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + var args = arguments; + var isLegacyCall = arguments.length && + typeof arguments[0] === 'function'; + if (isLegacyCall) { + return nativeMethod.apply(pc, [ + function(description) { + var desc = replaceInternalStreamId(pc, description); + args[0].apply(null, [desc]); + }, + function(err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2] + ]); + } + return nativeMethod.apply(pc, arguments) + .then(function(description) { + return replaceInternalStreamId(pc, description); + }); + }; + }); + + var origSetLocalDescription = + window.RTCPeerConnection.prototype.setLocalDescription; + window.RTCPeerConnection.prototype.setLocalDescription = function() { + var pc = this; + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(pc, arguments); + } + arguments[0] = replaceExternalStreamId(pc, arguments[0]); + return origSetLocalDescription.apply(pc, arguments); + }; + + // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + var origLocalDescription = Object.getOwnPropertyDescriptor( + window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, + 'localDescription', { + get: function() { + var pc = this; + var description = origLocalDescription.get.apply(this); + if (description.type === '') { + return description; + } + return replaceInternalStreamId(pc, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + var isLocal = sender._pc === pc; + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', + 'InvalidAccessError'); + } + + // Search for the native stream the senders track belongs to. + pc._streams = pc._streams || {}; + var stream; + Object.keys(pc._streams).forEach(function(streamid) { + var hasTrack = pc._streams[streamid].getTracks().find(function(track) { + return sender.track === track; + }); + if (hasTrack) { + stream = pc._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + pc.removeStream(pc._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + pc.dispatchEvent(new Event('negotiationneeded')); + } + }; + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + // The RTCPeerConnection object. + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + // this was fixed in M56 along with unprefixing RTCPeerConnection. + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + return new window.webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.webkitRTCPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if (window.webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.webkitRTCPeerConnection.generateCertificate; + } + }); + } + } else { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function(selector, + successCallback, errorCallback) { + var pc = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } + + // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + if (origGetStats.length === 0 && (arguments.length === 0 || + typeof arguments[0] !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + return new Map(Object.keys(stats).map(function(key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + origGetStats.apply(pc, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(successCallback, errorCallback); + }; + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var args = arguments; + var pc = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // promise support for createOffer and createAnswer. Available (without + // bugs) since M52: crbug/619289 + if (browserDetails.version < 52) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + } +}; + +},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && typeof constraints.audio === 'object') { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode && + !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches; + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function(d) { + return matches.some(function(match) { + return d.label.toLowerCase().indexOf(match) !== -1; + }); + }); + if (!dev && devices.length && matches.indexOf('back') !== -1) { + dev = devices[devices.length - 1]; // more likely the back cam + } + if (dev) { + constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : + {ideal: dev.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + InvalidStateError: 'NotReadableError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotReadableError', + MediaDeviceKillSwitchOn: 'NotReadableError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return window.MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }, + getSupportedConstraints: function() { + return { + deviceId: true, echoCancellation: true, facingMode: true, + frameRate: true, height: true, width: true + }; + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).then(function(stream) { + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } +}; + +},{"../utils.js":13}],7:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var SDPUtils = require('sdp'); +var utils = require('./utils'); + +module.exports = { + shimRTCIceCandidate: function(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (window.RTCIceCandidate && 'foundation' in + window.RTCIceCandidate.prototype) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + window.RTCIceCandidate = function(args) { + // Remove the a= which shouldn't be part of the candidate string. + if (typeof args === 'object' && args.candidate && + args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = SDPUtils.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, + parsedCandidate); + + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment, + }; + }; + return augmentedCandidate; + }; + + // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + return e; + }); + }, + + // shimCreateObjectURL must be called before shimSourceObject to avoid loop. + + shimCreateObjectURL: function(window) { + var URL = window && window.URL; + + if (!(typeof window === 'object' && window.HTMLMediaElement && + 'srcObject' in window.HTMLMediaElement.prototype && + URL.createObjectURL && URL.revokeObjectURL)) { + // Only shim CreateObjectURL using srcObject if srcObject exists. + return undefined; + } + + var nativeCreateObjectURL = URL.createObjectURL.bind(URL); + var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL); + var streams = new Map(), newId = 0; + + URL.createObjectURL = function(stream) { + if ('getTracks' in stream) { + var url = 'polyblob:' + (++newId); + streams.set(url, stream); + utils.deprecated('URL.createObjectURL(stream)', + 'elem.srcObject = stream'); + return url; + } + return nativeCreateObjectURL(stream); + }; + URL.revokeObjectURL = function(url) { + nativeRevokeObjectURL(url); + streams.delete(url); + }; + + var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype, + 'src'); + Object.defineProperty(window.HTMLMediaElement.prototype, 'src', { + get: function() { + return dsc.get.apply(this); + }, + set: function(url) { + this.srcObject = streams.get(url) || null; + return dsc.set.apply(this, [url]); + } + }); + + var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute; + window.HTMLMediaElement.prototype.setAttribute = function() { + if (arguments.length === 2 && + ('' + arguments[0]).toLowerCase() === 'src') { + this.srcObject = streams.get(arguments[1]) || null; + } + return nativeSetAttribute.apply(this, arguments); + }; + }, + + shimMaxMessageSize: function(window) { + if (window.RTCSctpTransport || !window.RTCPeerConnection) { + return; + } + var browserDetails = utils.detectBrowser(window); + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function(description) { + var sections = SDPUtils.splitSections(description.sdp); + sections.shift(); + return sections.some(function(mediaSection) { + var mLine = SDPUtils.parseMLine(mediaSection); + return mLine && mLine.kind === 'application' + && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + if (match === null || match.length < 2) { + return -1; + } + var version = parseInt(match[1], 10); + // Test for NaN (yes, this is ugly) + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = + browserDetails.version === 57 ? 65535 : 65536; + } + } + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; + + // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + if (browserDetails.browser === 'firefox' + && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && + remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + return maxMessageSize; + }; + + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + pc._sctp = null; + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); + + // Get the maximum message size the local peer is capable of sending + var canSendMMS = getCanSendMaxMessageSize(isFirefox); + + // Get the maximum message size of the remote peer. + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); + + // Determine final maximum message size + var maxMessageSize; + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } + + // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function() { + return maxMessageSize; + } + }); + pc._sctp = sctp; + } + + return origSetRemoteDescription.apply(pc, arguments); + }; + }, + + shimSendThrowTypeError: function(window) { + // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + var origCreateDataChannel = + window.RTCPeerConnection.prototype.createDataChannel; + window.RTCPeerConnection.prototype.createDataChannel = function() { + var pc = this; + var dataChannel = origCreateDataChannel.apply(pc, arguments); + var origDataChannelSend = dataChannel.send; + + // Patch 'send' method + dataChannel.send = function() { + var dc = this; + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + if (length > pc.sctp.maxMessageSize) { + throw new DOMException('Message too large (can send a maximum of ' + + pc.sctp.maxMessageSize + ' bytes)', 'TypeError'); + } + return origDataChannelSend.apply(dc, arguments); + }; + + return dataChannel; + }; + } +}; + +},{"./utils":13,"sdp":2}],8:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var shimRTCPeerConnection = require('rtcpeerconnection-shim'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + // this adds an additional event listener to MediaStrackTrack that signals + // when a tracks enabled property was changed. Workaround for a bug in + // addStream, see below. No longer required in 15025+ + if (browserDetails.version < 15025) { + var origMSTEnabled = Object.getOwnPropertyDescriptor( + window.MediaStreamTrack.prototype, 'enabled'); + Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { + set: function(value) { + origMSTEnabled.set.call(this, value); + var ev = new Event('enabled'); + ev.enabled = value; + this.dispatchEvent(ev); + } + }); + } + } + + // ORTC defines the DTMF sender a bit different. + // https://github.com/w3c/ortc/issues/714 + if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = new window.RTCDtmfSender(this); + } else if (this.track.kind === 'video') { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + + window.RTCPeerConnection = + shimRTCPeerConnection(window, browserDetails.version); + }, + shimReplaceTrack: function(window) { + // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 + if (window.RTCRtpSender && + !('replaceTrack' in window.RTCRtpSender.prototype)) { + window.RTCRtpSender.prototype.replaceTrack = + window.RTCRtpSender.prototype.setTrack; + } + } +}; + +},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +// Expose public methods. +module.exports = function(window) { + var navigator = window && window.navigator; + + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; +}; + +},{}],10:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.transceiver = {receiver: event.receiver}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + if (typeof window === 'object' && window.RTCTrackEvent && + ('receiver' in window.RTCTrackEvent.prototype) && + !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimSourceObject: function(window) { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new window.mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (window.mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = window.mozRTCSessionDescription; + window.RTCIceCandidate = window.mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function( + selector, + onSucc, + onErr + ) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + if (browserDetails.version < 48) { + stats = makeMapStats(stats); + } + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function(stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + stats.forEach(function(stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + return stats; + }) + .then(onSucc, onErr); + }; + }, + + shimRemoveStream: function(window) { + if (!window.RTCPeerConnection || + 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function(sender) { + if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } +}; + +},{"../utils":13,"./getusermedia":11}],11:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + var shimError_ = function(e) { + return { + name: { + InternalError: 'NotReadableError', + NotSupportedError: 'TypeError', + PermissionDeniedError: 'NotAllowedError', + SecurityError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).then(function(stream) { + // Work around https://bugzil.la/802326 + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('The object can not be found here.', + 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + if (!(browserDetails.version > 55 && + 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + if (typeof c === 'object' && typeof c.audio === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + MediaStreamTrack.prototype.getSettings = function() { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + MediaStreamTrack.prototype.applyConstraints = function(c) { + if (this.kind === 'audio' && typeof c === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeApplyConstraints.apply(this, [c]); + }; + } + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; +}; + +},{"../utils":13}],12:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +'use strict'; +var utils = require('../utils'); + +module.exports = { + shimLocalStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function() { + if (!this._localStreams) { + this._localStreams = []; + } + return this._localStreams; + }; + } + if (!('getStreamById' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getStreamById = function(id) { + var result = null; + if (this._localStreams) { + this._localStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + if (this._remoteStreams) { + this._remoteStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + return result; + }; + } + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + var pc = this; + stream.getTracks().forEach(function(track) { + _addTrack.call(pc, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (stream) { + if (!this._localStreams) { + this._localStreams = [stream]; + } else if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + } + return _addTrack.call(this, track, stream); + }; + } + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + var index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } + this._localStreams.splice(index, 1); + var pc = this; + var tracks = stream.getTracks(); + this.getSenders().forEach(function(sender) { + if (tracks.indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } + }, + shimRemoteStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function() { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function() { + return this._onaddstream; + }, + set: function(f) { + var pc = this; + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); + } + this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function(e) { + e.streams.forEach(function(stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + pc._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + }); + } + }, + shimCallbacksAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + var prototype = window.RTCPeerConnection.prototype; + var createOffer = prototype.createOffer; + var createAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createOffer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createAnswer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setLocalDescription = withCallback; + + withCallback = function(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setRemoteDescription = withCallback; + + withCallback = function(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.addIceCandidate = withCallback; + }, + shimGetUserMedia: function(window) { + var navigator = window && window.navigator; + + if (!navigator.getUserMedia) { + if (navigator.webkitGetUserMedia) { + navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + } else if (navigator.mediaDevices && + navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function(constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints) + .then(cb, errcb); + }.bind(navigator); + } + } + }, + shimRTCIceServerUrls: function(window) { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if ('generateCertificate' in window.RTCPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + }, + shimTrackEventTransceiver: function(window) { + // Add event.transceiver member over deprecated event.receiver + if (typeof window === 'object' && window.RTCPeerConnection && + ('receiver' in window.RTCTrackEvent.prototype) && + // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is + // defined for some reason even when window.RTCTransceiver is not. + !window.RTCTransceiver) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimCreateOfferLegacy: function(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function(offerOptions) { + var pc = this; + if (offerOptions) { + var audioTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'audio'; + }); + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && + !audioTransceiver) { + pc.addTransceiver('audio'); + } + + var videoTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'video'; + }); + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + videoTransceiver.setDirection('sendonly'); + } else if (videoTransceiver.direction === 'recvonly') { + videoTransceiver.setDirection('inactive'); + } + } else if (offerOptions.offerToReceiveVideo === true && + !videoTransceiver) { + pc.addTransceiver('video'); + } + } + return origCreateOffer.apply(pc, arguments); + }; + } +}; + +},{"../utils":13}],13:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var logDisabled_ = true; +var deprecationWarnings_ = true; + +/** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ +function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); +} + +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object. +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function(e) { + cb(wrapper(e)); + }; + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, + wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap + || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, + unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function() { + return this['_on' + eventNameToWrap]; + }, + set: function(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, + this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, + this['_on' + eventNameToWrap] = cb); + } + } + }); +} + +// Utility methods. +module.exports = { + extractVersion: extractVersion, + wrapPeerConnectionEvent: wrapPeerConnectionEvent, + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + /** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + disableWarnings: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + deprecated: function(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + + ' instead.'); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function(window) { + var navigator = window && window.navigator; + + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + if (navigator.mozGetUserMedia) { // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, + /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, + /Chrom(e|ium)\/(\d+)\./, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. + result.browser = 'edge'; + result.version = extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, + /AppleWebKit\/(\d+)\./, 1); + } else { // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } +}; + +},{}]},{},[3])(3) +}); \ No newline at end of file diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js index 78f8bc450e..2ac8e89e7e 100644 --- a/html5/verto/video_demo/js/verto-min.js +++ b/html5/verto/video_demo/js/verto-min.js @@ -1,630 +1,9508 @@ +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.FSRTC.js - WebRTC Glue code + * + */ -(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=0;if(iOS){self.options.useVideo.setAttribute("playsinline",true);self.options.useVideo.setAttribute("controls",true);}} -var element=self.options.useAudio;console.log("REMOTE STREAM",stream,element);FSRTCattachMediaStream(element,stream);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.stopPeer=function(){if(self.peer){console.log("stopping peer");self.peer.stop();}} -$.FSRTC.prototype.stop=function(){var self=this;if(self.options.useVideo){self.options.useVideo.style.display='none';self.options.useVideo['src']='';} -if(self.localStream){if(typeof self.localStream.stop=='function'){self.localStream.stop();}else{if(self.localStream.active){var tracks=self.localStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}} -self.localStream=null;} -if(self.options.localVideo){self.options.localVideo.style.display='none';self.options.localVideo['src']='';} -if(self.options.localVideoStream){if(typeof self.options.localVideoStream.stop=='function'){self.options.localVideoStream.stop();}else{if(self.options.localVideoStream.active){var tracks=self.options.localVideoStream.getTracks();console.log(tracks);tracks.forEach(function(track,index){console.log(track);track.stop();})}}} -if(self.peer){console.log("stopping peer");self.peer.stop();}};$.FSRTC.prototype.getMute=function(){var self=this;return self.audioEnabled;} -$.FSRTC.prototype.setMute=function(what){var self=this;var audioTracks=self.localStream.getAudioTracks();for(var i=0,len=audioTracks.length;i=w&&$.FSRTC.validRes[i][1]>=h){w=$.FSRTC.validRes[i][0];h=$.FSRTC.validRes[i][1];}} -return[w,h];} -var resList=[[160,120],[320,180],[320,240],[640,360],[640,480],[1280,720],[1920,1080]];var resI=0;var ttl=0;var checkRes=function(cam,func){if(resI>=resList.length){var res={'validRes':$.FSRTC.validRes,'bestResSupported':$.FSRTC.bestResSupported()};localStorage.setItem("res_"+cam,$.toJSON(res));if(func)return func(res);return;} -var video={} -if(cam){video.deviceId={exact:cam};} -w=resList[resI][0];h=resList[resI][1];resI++;video={width:{exact:w},height:{exact:h}};getUserMedia({constraints:{audio:ttl++==0,video:video},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info(w+"x"+h+" supported.");$.FSRTC.validRes.push([w,h]);checkRes(cam,func);},onerror:function(e){console.warn(w+"x"+h+" not supported.");checkRes(cam,func);}});} -$.FSRTC.getValidRes=function(cam,func){var used=[];var cached=localStorage.getItem("res_"+cam);if(cached){var cache=$.parseJSON(cached);if(cache){$.FSRTC.validRes=cache.validRes;console.log("CACHED RES FOR CAM "+cam,cache);}else{console.error("INVALID CACHE");} -return func?func(cache):null;} -$.FSRTC.validRes=[];resI=0;checkRes(cam,func);} -$.FSRTC.checkPerms=function(runtime,check_audio,check_video){getUserMedia({constraints:{audio:check_audio,video:check_video,},onsuccess:function(e){e.getTracks().forEach(function(track){track.stop();});console.info("media perm init complete");if(runtime){setTimeout(runtime,100,true);}},onerror:function(e){if(check_video&&check_audio){console.error("error, retesting with audio params only");return $.FSRTC.checkPerms(runtime,check_audio,false);} -console.error("media perm init error");if(runtime){runtime(false)}}});}})(jQuery);(function($){$.JsonRpcClient=function(options){var self=this;this.options=$.extend({ajaxUrl:null,socketUrl:null,onmessage:null,login:null,passwd:null,sessid:null,loginParams:null,userVariables:null,getSocket:function(onmessage_cb){return self._getSocket(onmessage_cb);}},options);self.ws_cnt=0;this.wsOnMessage=function(event){self._wsOnMessage(event);};};$.JsonRpcClient.prototype._ws_socket=null;$.JsonRpcClient.prototype._ws_callbacks={};$.JsonRpcClient.prototype._current_id=1;$.JsonRpcClient.prototype.speedTest=function(bytes,cb){var socket=this.options.getSocket(this.wsOnMessage);if(socket!==null){this.speedCB=cb;this.speedBytes=bytes;socket.send("#SPU "+bytes);var loops=bytes/1024;var rem=bytes%1024;var i;var data=new Array(1024).join(".");for(i=0;i1){return false;} -return true;};$.JsonRpcClient.prototype.closeSocket=function(){var self=this;if(self.socketReady()){self._ws_socket.onclose=function(w){console.log("Closing Socket");};self._ws_socket.close();}};$.JsonRpcClient.prototype.loginData=function(params){var self=this;self.options.login=params.login;self.options.passwd=params.passwd;self.options.loginParams=params.loginParams;self.options.userVariables=params.userVariables;};$.JsonRpcClient.prototype.connectSocket=function(onmessage_cb){var self=this;if(self.to){clearTimeout(self.to);} -if(!self.socketReady()){self.authing=false;if(self._ws_socket){delete self._ws_socket;} -self._ws_socket=new WebSocket(self.options.socketUrl);if(self._ws_socket){self._ws_socket.onmessage=onmessage_cb;self._ws_socket.onclose=function(w){if(!self.ws_sleep){self.ws_sleep=1000;} -if(self.options.onWSClose){self.options.onWSClose(self);} -console.error("Websocket Lost "+self.ws_cnt+" sleep: "+self.ws_sleep+"msec");self.to=setTimeout(function(){console.log("Attempting Reconnection....");self.connectSocket(onmessage_cb);},self.ws_sleep);self.ws_cnt++;if(self.ws_sleep<3000&&(self.ws_cnt%10)===0){self.ws_sleep+=1000;}};self._ws_socket.onopen=function(){if(self.to){clearTimeout(self.to);} -self.ws_sleep=1000;self.ws_cnt=0;if(self.options.onWSConnect){self.options.onWSConnect(self);} -var req;while((req=$.JsonRpcClient.q.pop())){self._ws_socket.send(req);}};}} -return self._ws_socket?true:false;};$.JsonRpcClient.prototype.stopRetrying=function(){if(self.to) -clearTimeout(self.to);} -$.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;if(event.data[0]=="#"&&event.data[1]=="S"&&event.data[2]=="P"){if(event.data[3]=="U"){this.up_dur=parseInt(event.data.substring(4));}else if(this.speedCB&&event.data[3]=="D"){this.down_dur=parseInt(event.data.substring(4));var up_kps=(((this.speedBytes*8)/(this.up_dur/1000))/1024).toFixed(0);var down_kps=(((this.speedBytes*8)/(this.down_dur/1000))/1024).toFixed(0);console.info("Speed Test: Up: "+up_kps+" Down: "+down_kps);this.speedCB(event,{upDur:this.up_dur,downDur:this.down_dur,upKPS:up_kps,downKPS:down_kps});this.speedCB=null;} -return;} -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,loginParams:self.options.loginParams,userVariables:self.options.userVariables},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 i=0;var call;var success_cb;var error_cb;var socket=self.jsonrpcclient.options.getSocket(self.jsonrpcclient.wsOnMessage);if(socket!==null){for(i=0;i0){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);dialog.setState($.verto.enum.state.recovering);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.punt':verto.purge();verto.logout();break;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&&key===verto.sessid){if(verto.callbacks.onMessage){verto.callbacks.onMessage(verto,null,$.verto.enum.message.pvtEvent,data.params);}}else 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.debug("MESSAGE from: "+data.params.msg.from,data.params.msg.body);break;case'verto.clientReady':verto.callbacks.onMessage(verto,null,$.verto.enum.message.clientReady,data.params);console.debug("CLIENT READY",data.params);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=array.length){array.push(name);}else{var x=0;var n=[];var len=array.length;for(var i=0;i":"\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;} -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.sendCommand=function(cmd,obj){var self=la;self.broadcast(self.context,{liveArray:{command:cmd,context:self.context,name:self.name,obj:obj}});};la.bootstrap=function(obj){var self=la;la.sendCommand("bootstrap",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);};function genRow(data){if(typeof(data[4])==="string"&&data[4].indexOf("{")>-1){var tmp=$.parseJSON(data[4]);data[4]=tmp.oldStatus;data[5]=null;} -return data;} -function genArray(obj){var data=obj.asArray();for(var i in data){data[i]=genRow(data[i]);} -return data;} -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;}} -if(config.onChange){config.onChange(obj,args);} -try{switch(args.action){case"bootObj":if(!args.data){console.error("missing data");return;} -dt.fnClearTable();dt.fnAddData(genArray(obj));dt.fnAdjustColumnSizing();break;case"add":if(!args.data){console.error("missing data");return;} -if(args.redraw>-1){dt.fnClearTable();dt.fnAddData(genArray(obj));}else{dt.fnAddData(genRow(args.data));} -dt.fnAdjustColumnSizing();break;case"modify":if(!args.data){return;} -dt.fnUpdate(genRow(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(genArray(obj));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;}};la.onChange(la,{action:"init"});};var CONFMAN_SERNO=1;$.verto.conf=function(verto,params){var conf=this;conf.params=$.extend({dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);conf.verto=verto;conf.serno=CONFMAN_SERNO++;createMainModeratorMethods();verto.subscribe(conf.params.laData.modChannel,{handler:function(v,e){if(conf.params.onBroadcast){conf.params.onBroadcast(verto,conf,e.data);}}});verto.subscribe(conf.params.laData.infoChannel,{handler:function(v,e){if(typeof(conf.params.infoCallback)==="function"){conf.params.infoCallback(v,e);}}});verto.subscribe(conf.params.laData.chatChannel,{handler:function(v,e){if(typeof(conf.params.chatCallback)==="function"){conf.params.chatCallback(v,e);}}});};$.verto.conf.prototype.modCommand=function(cmd,id,value){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.conf.prototype.destroy=function(){var conf=this;conf.destroyed=true;conf.params.onBroadcast(conf.verto,conf,'destroy');if(conf.params.laData.modChannel){conf.verto.unsubscribe(conf.params.laData.modChannel);} -if(conf.params.laData.chatChannel){conf.verto.unsubscribe(conf.params.laData.chatChannel);} -if(conf.params.laData.infoChannel){conf.verto.unsubscribe(conf.params.laData.infoChannel);}};function createMainModeratorMethods(){$.verto.conf.prototype.listVideoLayouts=function(){this.modCommand("list-videoLayouts",null,null);};$.verto.conf.prototype.play=function(file){this.modCommand("play",null,file);};$.verto.conf.prototype.stop=function(){this.modCommand("stop",null,"all");};$.verto.conf.prototype.deaf=function(memberID){this.modCommand("deaf",parseInt(memberID));};$.verto.conf.prototype.undeaf=function(memberID){this.modCommand("undeaf",parseInt(memberID));};$.verto.conf.prototype.record=function(file){this.modCommand("recording",null,["start",file]);};$.verto.conf.prototype.stopRecord=function(){this.modCommand("recording",null,["stop","all"]);};$.verto.conf.prototype.snapshot=function(file){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-write-png",null,file);};$.verto.conf.prototype.setVideoLayout=function(layout,canvasID){if(!this.params.hasVid){throw'Conference has no video';} -if(canvasID){this.modCommand("vid-layout",null,[layout,canvasID]);}else{this.modCommand("vid-layout",null,layout);}};$.verto.conf.prototype.kick=function(memberID){this.modCommand("kick",parseInt(memberID));};$.verto.conf.prototype.muteMic=function(memberID){this.modCommand("tmute",parseInt(memberID));};$.verto.conf.prototype.muteVideo=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("tvmute",parseInt(memberID));};$.verto.conf.prototype.presenter=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-res-id",parseInt(memberID),"presenter");};$.verto.conf.prototype.videoFloor=function(memberID){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-floor",parseInt(memberID),"force");};$.verto.conf.prototype.banner=function(memberID,text){if(!this.params.hasVid){throw'Conference has no video';} -this.modCommand("vid-banner",parseInt(memberID),escape(text));};$.verto.conf.prototype.volumeDown=function(memberID){this.modCommand("volume_out",parseInt(memberID),"down");};$.verto.conf.prototype.volumeUp=function(memberID){this.modCommand("volume_out",parseInt(memberID),"up");};$.verto.conf.prototype.gainDown=function(memberID){this.modCommand("volume_in",parseInt(memberID),"down");};$.verto.conf.prototype.gainUp=function(memberID){this.modCommand("volume_in",parseInt(memberID),"up");};$.verto.conf.prototype.transfer=function(memberID,exten){this.modCommand("transfer",parseInt(memberID),exten);};$.verto.conf.prototype.sendChat=function(message,type){var conf=this;conf.verto.rpcClient.call("verto.broadcast",{"eventChannel":conf.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};} -$.verto.modfuncs={};$.verto.confMan=function(verto,params){var confMan=this;confMan.params=$.extend({tableID:null,statusID:null,mainModID:null,dialog:null,hasVid:false,laData:null,onBroadcast:null,onLaChange:null,onLaRow:null},params);confMan.verto=verto;confMan.serno=CONFMAN_SERNO++;confMan.canvasCount=confMan.params.laData.canvasCount;function genMainMod(jq){var play_id="play_"+confMan.serno;var stop_id="stop_"+confMan.serno;var recording_id="recording_"+confMan.serno;var snapshot_id="snapshot_"+confMan.serno;var rec_stop_id="recording_stop"+confMan.serno;var div_id="confman_"+confMan.serno;var html="

"+""+""+""+""+ -(confMan.params.hasVid?"":"")+"

";jq.html(html);$.verto.modfuncs.change_video_layout=function(id,canvas_id){var val=$("#"+id+" option:selected").text();if(val!=="none"){confMan.modCommand("vid-layout",null,[val,canvas_id]);}};if(confMan.params.hasVid){for(var j=0;j
"+"Video Layout Canvas "+(j+1)+" "+"

";jq.append(vlhtml);} -$("#"+snapshot_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("vid-write-png",null,file);}});} -$("#"+play_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("play",null,file);}});$("#"+stop_id).click(function(){confMan.modCommand("stop",null,"all");});$("#"+recording_id).click(function(){var file=prompt("Please enter file name","");if(file){confMan.modCommand("recording",null,["start",file]);}});$("#"+rec_stop_id).click(function(){confMan.modCommand("recording",null,["stop","all"]);});} -function genControls(jq,rowid){var x=parseInt(rowid);var kick_id="kick_"+x;var canvas_in_next_id="canvas_in_next_"+x;var canvas_in_prev_id="canvas_in_prev_"+x;var canvas_out_next_id="canvas_out_next_"+x;var canvas_out_prev_id="canvas_out_prev_"+x;var canvas_in_set_id="canvas_in_set_"+x;var canvas_out_set_id="canvas_out_set_"+x;var layer_set_id="layer_set_"+x;var layer_next_id="layer_next_"+x;var layer_prev_id="layer_prev_"+x;var tmute_id="tmute_"+x;var tvmute_id="tvmute_"+x;var vbanner_id="vbanner_"+x;var tvpresenter_id="tvpresenter_"+x;var tvfloor_id="tvfloor_"+x;var box_id="box_"+x;var gainup_id="gain_in_up"+x;var gaindn_id="gain_in_dn"+x;var volup_id="vol_in_up"+x;var voldn_id="vol_in_dn"+x;var transfer_id="transfer"+x;var html="
";html+="General Controls
";html+=""+""+""+""+""+""+"";if(confMan.params.hasVid){html+="

Video Controls
";html+=""+""+""+"";if(confMan.canvasCount>1){html+="

Canvas Controls
"+""+""+""+"
"+""+""+"";} -html+="
"+""+""+""+"
";} -jq.html(html);if(!jq.data("mouse")){$("#"+box_id).hide();} -jq.mouseover(function(e){jq.data({"mouse":true});$("#"+box_id).show();});jq.mouseout(function(e){jq.data({"mouse":false});$("#"+box_id).hide();});$("#"+transfer_id).click(function(){var xten=prompt("Enter Extension");if(xten){confMan.modCommand("transfer",x,xten);}});$("#"+kick_id).click(function(){confMan.modCommand("kick",x);});$("#"+layer_set_id).click(function(){var cid=prompt("Please enter layer ID","");if(cid){confMan.modCommand("vid-layer",x,cid);}});$("#"+layer_next_id).click(function(){confMan.modCommand("vid-layer",x,"next");});$("#"+layer_prev_id).click(function(){confMan.modCommand("vid-layer",x,"prev");});$("#"+canvas_in_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-canvas",x,cid);}});$("#"+canvas_out_set_id).click(function(){var cid=prompt("Please enter canvas ID","");if(cid){confMan.modCommand("vid-watching-canvas",x,cid);}});$("#"+canvas_in_next_id).click(function(){confMan.modCommand("vid-canvas",x,"next");});$("#"+canvas_in_prev_id).click(function(){confMan.modCommand("vid-canvas",x,"prev");});$("#"+canvas_out_next_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"next");});$("#"+canvas_out_prev_id).click(function(){confMan.modCommand("vid-watching-canvas",x,"prev");});$("#"+tmute_id).click(function(){confMan.modCommand("tmute",x);});if(confMan.params.hasVid){$("#"+tvmute_id).click(function(){confMan.modCommand("tvmute",x);});$("#"+tvpresenter_id).click(function(){confMan.modCommand("vid-res-id",x,"presenter");});$("#"+tvfloor_id).click(function(){confMan.modCommand("vid-floor",x,"force");});$("#"+vbanner_id).click(function(){var text=prompt("Please enter text","");if(text){confMan.modCommand("vid-banner",x,escape(text));}});} -$("#"+gainup_id).click(function(){confMan.modCommand("volume_in",x,"up");});$("#"+gaindn_id).click(function(){confMan.modCommand("volume_in",x,"down");});$("#"+volup_id).click(function(){confMan.modCommand("volume_out",x,"up");});$("#"+voldn_id).click(function(){confMan.modCommand("volume_out",x,"down");});return html;} -var atitle="";var awidth=0;verto.subscribe(confMan.params.laData.infoChannel,{handler:function(v,e){if(typeof(confMan.params.infoCallback)==="function"){confMan.params.infoCallback(v,e);}}});verto.subscribe(confMan.params.laData.chatChannel,{handler:function(v,e){if(typeof(confMan.params.chatCallback)==="function"){confMan.params.chatCallback(v,e);}}});if(confMan.params.laData.role==="moderator"){atitle="Action";awidth=600;if(confMan.params.mainModID){genMainMod($(confMan.params.mainModID));$(confMan.params.displayID).html("Moderator Controls Ready

");}else{$(confMan.params.mainModID).html("");} -verto.subscribe(confMan.params.laData.modChannel,{handler:function(v,e){if(confMan.params.onBroadcast){confMan.params.onBroadcast(verto,confMan,e.data);} -if(e.data["conf-command"]==="list-videoLayouts"){for(var j=0;jb)?1:-1));});for(var i in options){$(vlselect_id).append(new Option(options[i],options[i]));x++;}} -if(x){$(vlselect_id).selectmenu('refresh',true);}else{$(vlayout_id).hide();}}}else{if(!confMan.destroyed&&confMan.params.displayID){$(confMan.params.displayID).html(e.data.response+"

");if(confMan.lastTimeout){clearTimeout(confMan.lastTimeout);confMan.lastTimeout=0;} -confMan.lastTimeout=setTimeout(function(){$(confMan.params.displayID).html(confMan.destroyed?"":"Moderator Controls Ready

");},4000);}}}});if(confMan.params.hasVid){confMan.modCommand("list-videoLayouts",null,null);}} -var row_callback=null;if(confMan.params.laData.role==="moderator"){row_callback=function(nRow,aData,iDisplayIndex,iDisplayIndexFull){if(!aData[5]){var $row=$('td:eq(5)',nRow);genControls($row,aData);if(confMan.params.onLaRow){confMan.params.onLaRow(verto,confMan,$row,aData);}}};} -confMan.lt=new $.verto.liveTable(verto,confMan.params.laData.laChannel,confMan.params.laData.laName,$(confMan.params.tableID),{subParams:{callID:confMan.params.dialog?confMan.params.dialog.callID:null},"onChange":function(obj,args){$(confMan.params.statusID).text("Conference Members: "+" ("+obj.arrayLen()+" Total)");if(confMan.params.onLaChange){confMan.params.onLaChange(verto,confMan,$.verto.enum.confEvent.laChange,obj,args);}},"aaData":[],"aoColumns":[{"sTitle":"ID","sWidth":"50"},{"sTitle":"Number","sWidth":"250"},{"sTitle":"Name","sWidth":"250"},{"sTitle":"Codec","sWidth":"100"},{"sTitle":"Status","sWidth":confMan.params.hasVid?"200px":"150px"},{"sTitle":atitle,"sWidth":awidth,}],"bAutoWidth":true,"bDestroy":true,"bSort":false,"bInfo":false,"bFilter":false,"bLengthChange":false,"bPaginate":false,"iDisplayLength":1400,"oLanguage":{"sEmptyTable":"The Conference is Empty....."},"fnRowCallback":row_callback});};$.verto.confMan.prototype.modCommand=function(cmd,id,value){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.modChannel,"data":{"application":"conf-control","command":cmd,"id":id,"value":value}});};$.verto.confMan.prototype.sendChat=function(message,type){var confMan=this;confMan.verto.rpcClient.call("verto.broadcast",{"eventChannel":confMan.params.laData.chatChannel,"data":{"action":"send","message":message,"type":type}});};$.verto.confMan.prototype.destroy=function(){var confMan=this;confMan.destroyed=true;if(confMan.lt){confMan.lt.destroy();} -if(confMan.params.laData.chatChannel){confMan.verto.unsubscribe(confMan.params.laData.chatChannel);} -if(confMan.params.laData.modChannel){confMan.verto.unsubscribe(confMan.params.laData.modChannel);} -if(confMan.params.mainModID){$(confMan.params.mainModID).html("");}};$.verto.dialog=function(direction,verto,params){var dialog=this;dialog.params=$.extend({useVideo:verto.options.useVideo,useStereo:verto.options.useStereo,screenShare:false,useCamera:false,useMic:verto.options.deviceParams.useMic,useSpeak:verto.options.deviceParams.useSpeak,tag:verto.options.tag,localTag:verto.options.localTag,login:verto.options.login,videoParams:verto.options.videoParams},params);if(!dialog.params.screenShare){dialog.params.useCamera=verto.options.deviceParams.useCamera;} -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;dialog.screenShare=params.screenShare||false;dialog.useCamera=dialog.params.useCamera;dialog.useMic=dialog.params.useMic;dialog.useSpeak=dialog.params.useSpeak;if(dialog.params.callID){dialog.callID=dialog.params.callID;}else{dialog.callID=dialog.params.callID=generateGUID();} -if(typeof(dialog.params.tag)==="function"){dialog.params.tag=dialog.params.tag();} -if(dialog.params.tag){dialog.audioStream=document.getElementById(dialog.params.tag);if(dialog.params.useVideo){dialog.videoStream=dialog.audioStream;}} -if(dialog.params.localTag){dialog.localVideo=document.getElementById(dialog.params.localTag);} -dialog.verto.dialogs[dialog.callID]=dialog;var RTCcallbacks={};if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.params.display_direction==="outbound"){dialog.params.remote_caller_id_name=dialog.params.caller_id_name;dialog.params.remote_caller_id_number=dialog.params.caller_id_number;}else{dialog.params.remote_caller_id_name=dialog.params.callee_id_name;dialog.params.remote_caller_id_number=dialog.params.callee_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){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;} -if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{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.onStream=function(rtc,stream){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onGranted==='function'){dialog.verto.options.permissionCallback.onGranted(stream);} -console.log("stream started");};RTCcallbacks.onError=function(e){if(dialog.verto.options.permissionCallback&&typeof dialog.verto.options.permissionCallback.onDenied==='function'){dialog.verto.options.permissionCallback.onDenied();} -console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});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;} -if((obj.noDialogParams&&i!="callID")){continue;} -obj.dialogParams[i]=dialog.params[i];} -delete obj.noDialogParams;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;} -function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}} -return id;} -$.verto.dialog.prototype.setAudioPlaybackDevice=function(sinkId,callback,arg){var dialog=this;var element=dialog.audioStream;if(typeof element.sinkId!=='undefined'){var devname=find_name(sinkId);console.info("Dialog: "+dialog.callID+" Setting speaker:",element,devname);element.setSinkId(sinkId).then(function(){console.log("Dialog: "+dialog.callID+' Success, audio output device attached: '+sinkId);if(callback){callback(true,devname,arg);}}).catch(function(error){var errorMessage=error;if(error.name==='SecurityError'){errorMessage="Dialog: "+dialog.callID+' You need to use HTTPS for selecting audio output '+'device: '+error;} -if(callback){callback(false,null,arg);} -console.error(errorMessage);});}else{console.warn("Dialog: "+dialog.callID+' Browser does not support output device selection.');if(callback){callback(false,null,arg);}}} -$.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.log("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.early:case $.verto.enum.state.active:var speaker=dialog.useSpeak;console.info("Using Speaker: ",speaker);if(speaker&&speaker!=="any"&&speaker!=="none"){setTimeout(function(){dialog.setAudioPlaybackDevice(speaker);},500);} -break;case $.verto.enum.state.trying:setTimeout(function(){if(dialog.state==$.verto.enum.state.trying){dialog.setState($.verto.enum.state.hangup);}},30000);break;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:if(typeof(dialog.verto.options.tag)==="function"){$('#'+dialog.params.tag).remove();} -delete dialog.verto.dialogs[dialog.callID];if(dialog.params.screenShare){dialog.rtc.stopPeer();}else{dialog.rtc.stop();} -break;} -return true;};$.verto.dialog.prototype.processReply=function(method,success,e){var dialog=this;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(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;} -if(params.cause){dialog.cause=params.cause;}} -if(!dialog.cause&&!dialog.causeCode){dialog.cause="NORMAL_CLEARING";} -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,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.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.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;} -pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.replace=function(replaceCallID,params){var dialog=this;if(replaceCallID){dialog.sendMethod("verto.modify",{action:"replace",replaceCallID:replaceCallID,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;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){params={};} -params.sdp=dialog.params.sdp;if(params){if(params.useVideo){dialog.useVideo(true);} -dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_number=params.callee_id_number;if(params.useCamera){dialog.useCamera=params.useCamera;} -if(params.useMic){dialog.useMic=params.useMic;} -if(params.useSpeak){dialog.useSpeak=params.useSpeak;}} -dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;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{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" <":" <")+dialog.params.remote_caller_id_number+(enc?">":">");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);};$.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(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;} -dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.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,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,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 recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent clientReady");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();} -if($.verto.haltClosure) -return $.verto.haltClosure();for(var i in $.verto.saved){var verto=$.verto.saved[i];if(verto){verto.purge();verto.logout();}} -return $.verto.warnOnUnload;});$.verto.videoDevices=[];$.verto.audioInDevices=[];$.verto.audioOutDevices=[];var checkDevices=function(runtime){console.info("enumerating devices");var aud_in=[],aud_out=[],vid=[];var has_video=0,has_audio=0;var Xstream;function gotDevices(deviceInfos){for(var i=0;i!==deviceInfos.length;++i){var deviceInfo=deviceInfos[i];var text="";console.log(deviceInfo);console.log(deviceInfo.kind+": "+deviceInfo.label+" id = "+deviceInfo.deviceId);if(deviceInfo.kind==='audioinput'){text=deviceInfo.label||'microphone '+(aud_in.length+1);aud_in.push({id:deviceInfo.deviceId,kind:"audio_in",label:text});}else if(deviceInfo.kind==='audiooutput'){text=deviceInfo.label||'speaker '+(aud_out.length+1);aud_out.push({id:deviceInfo.deviceId,kind:"audio_out",label:text});}else if(deviceInfo.kind==='videoinput'){text=deviceInfo.label||'camera '+(vid.length+1);vid.push({id:deviceInfo.deviceId,kind:"video",label:text});}else{console.log('Some other kind of source/device: ',deviceInfo);}} -$.verto.videoDevices=vid;$.verto.audioInDevices=aud_in;$.verto.audioOutDevices=aud_out;console.info("Audio IN Devices",$.verto.audioInDevices);console.info("Audio Out Devices",$.verto.audioOutDevices);console.info("Video Devices",$.verto.videoDevices);if(Xstream){Xstream.getTracks().forEach(function(track){track.stop();});} -if(runtime){runtime(true);}} -function handleError(error){console.log('device enumeration error: ',error);if(runtime)runtime(false);} -function checkTypes(devs){for(var i=0;i!==devs.length;++i){if(devs[i].kind==='audioinput'){has_audio++;}else if(devs[i].kind==='videoinput'){has_video++;}} -navigator.getUserMedia({audio:(has_audio>0?true:false),video:(has_video>0?true:false)},function(stream){Xstream=stream;navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError);},function(err){console.log("The following error occurred: "+err.name);});} -navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError);};$.verto.refreshDevices=function(runtime){checkDevices(runtime);} -$.verto.init=function(obj,runtime){if(!obj){obj={};} -if(!obj.skipPermCheck&&!obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){checkDevices(runtime);},true,true);}else if(obj.skipPermCheck&&!obj.skipDeviceCheck){checkDevices(runtime);}else if(!obj.skipPermCheck&&obj.skipDeviceCheck){$.FSRTC.checkPerms(function(status){runtime(status);},true,true);}else{runtime(null);}} -$.verto.genUUID=function(){return generateGUID();}})(jQuery);(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter=f()}})(function(){var define,module,exports;return(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=14393&&url.indexOf('?transport=udp')===-1;});delete server.url;server.urls=isString?urls[0]:urls;return!!urls.length;}});} -function getCommonCapabilities(localCapabilities,remoteCapabilities){var commonCapabilities={codecs:[],headerExtensions:[],fecMechanisms:[]};var findCodecByPayloadType=function(pt,codecs){pt=parseInt(pt,10);for(var i=0;i0;i--){this._iceGatherers=new window.RTCIceGatherer({iceServers:config.iceServers,gatherPolicy:config.iceTransportPolicy});}}else{config.iceCandidatePoolSize=0;} -this._config=config;this.transceivers=[];this._sdpSessionId=SDPUtils.generateSessionId();this._sdpSessionVersion=0;this._dtlsRole=undefined;this._isClosed=false;};RTCPeerConnection.prototype.onicecandidate=null;RTCPeerConnection.prototype.onaddstream=null;RTCPeerConnection.prototype.ontrack=null;RTCPeerConnection.prototype.onremovestream=null;RTCPeerConnection.prototype.onsignalingstatechange=null;RTCPeerConnection.prototype.oniceconnectionstatechange=null;RTCPeerConnection.prototype.onicegatheringstatechange=null;RTCPeerConnection.prototype.onnegotiationneeded=null;RTCPeerConnection.prototype.ondatachannel=null;RTCPeerConnection.prototype._dispatchEvent=function(name,event){if(this._isClosed){return;} -this.dispatchEvent(event);if(typeof this['on'+name]==='function'){this['on'+name](event);}};RTCPeerConnection.prototype._emitGatheringStateChange=function(){var event=new Event('icegatheringstatechange');this._dispatchEvent('icegatheringstatechange',event);};RTCPeerConnection.prototype.getConfiguration=function(){return this._config;};RTCPeerConnection.prototype.getLocalStreams=function(){return this.localStreams;};RTCPeerConnection.prototype.getRemoteStreams=function(){return this.remoteStreams;};RTCPeerConnection.prototype._createTransceiver=function(kind){var hasBundleTransport=this.transceivers.length>0;var transceiver={track:null,iceGatherer:null,iceTransport:null,dtlsTransport:null,localCapabilities:null,remoteCapabilities:null,rtpSender:null,rtpReceiver:null,kind:kind,mid:null,sendEncodingParameters:null,recvEncodingParameters:null,stream:null,associatedRemoteMediaStreams:[],wantReceive:true};if(this.usingBundle&&hasBundleTransport){transceiver.iceTransport=this.transceivers[0].iceTransport;transceiver.dtlsTransport=this.transceivers[0].dtlsTransport;}else{var transports=this._createIceAndDtlsTransports();transceiver.iceTransport=transports.iceTransport;transceiver.dtlsTransport=transports.dtlsTransport;} -this.transceivers.push(transceiver);return transceiver;};RTCPeerConnection.prototype.addTrack=function(track,stream){var alreadyExists=this.transceivers.find(function(s){return s.track===track;});if(alreadyExists){throw makeError('InvalidAccessError','Track already exists.');} -if(this.signalingState==='closed'){throw makeError('InvalidStateError','Attempted to call addTrack on a closed peerconnection.');} -var transceiver;for(var i=0;i=15025){stream.getTracks().forEach(function(track){pc.addTrack(track,stream);});}else{var clonedStream=stream.clone();stream.getTracks().forEach(function(track,idx){var clonedTrack=clonedStream.getTracks()[idx];track.addEventListener('enabled',function(event){clonedTrack.enabled=event.enabled;});});clonedStream.getTracks().forEach(function(track){pc.addTrack(track,clonedStream);});}};RTCPeerConnection.prototype.removeTrack=function(sender){if(!(sender instanceof window.RTCRtpSender)){throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.');} -var transceiver=this.transceivers.find(function(t){return t.rtpSender===sender;});if(!transceiver){throw makeError('InvalidAccessError','Sender was not created by this connection.');} -var stream=transceiver.stream;transceiver.rtpSender.stop();transceiver.rtpSender=null;transceiver.track=null;transceiver.stream=null;var localStreams=this.transceivers.map(function(t){return t.stream;});if(localStreams.indexOf(stream)===-1&&this.localStreams.indexOf(stream)>-1){this.localStreams.splice(this.localStreams.indexOf(stream),1);} -this._maybeFireNegotiationNeeded();};RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;stream.getTracks().forEach(function(track){var sender=pc.getSenders().find(function(s){return s.track===track;});if(sender){pc.removeTrack(sender);}});};RTCPeerConnection.prototype.getSenders=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpSender;}).map(function(transceiver){return transceiver.rtpSender;});};RTCPeerConnection.prototype.getReceivers=function(){return this.transceivers.filter(function(transceiver){return!!transceiver.rtpReceiver;}).map(function(transceiver){return transceiver.rtpReceiver;});};RTCPeerConnection.prototype._createIceGatherer=function(sdpMLineIndex,usingBundle){var pc=this;if(usingBundle&&sdpMLineIndex>0){return this.transceivers[0].iceGatherer;}else if(this._iceGatherers.length){return this._iceGatherers.shift();} -var iceGatherer=new window.RTCIceGatherer({iceServers:this._config.iceServers,gatherPolicy:this._config.iceTransportPolicy});Object.defineProperty(iceGatherer,'state',{value:'new',writable:true});this.transceivers[sdpMLineIndex].candidates=[];this.transceivers[sdpMLineIndex].bufferCandidates=function(event){var end=!event.candidate||Object.keys(event.candidate).length===0;iceGatherer.state=end?'completed':'gathering';if(pc.transceivers[sdpMLineIndex].candidates!==null){pc.transceivers[sdpMLineIndex].candidates.push(event.candidate);}};iceGatherer.addEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);return iceGatherer;};RTCPeerConnection.prototype._gather=function(mid,sdpMLineIndex){var pc=this;var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer.onlocalcandidate){return;} -var candidates=this.transceivers[sdpMLineIndex].candidates;this.transceivers[sdpMLineIndex].candidates=null;iceGatherer.removeEventListener('localcandidate',this.transceivers[sdpMLineIndex].bufferCandidates);iceGatherer.onlocalcandidate=function(evt){if(pc.usingBundle&&sdpMLineIndex>0){return;} -var event=new Event('icecandidate');event.candidate={sdpMid:mid,sdpMLineIndex:sdpMLineIndex};var cand=evt.candidate;var end=!cand||Object.keys(cand).length===0;if(end){if(iceGatherer.state==='new'||iceGatherer.state==='gathering'){iceGatherer.state='completed';}}else{if(iceGatherer.state==='new'){iceGatherer.state='gathering';} -cand.component=1;event.candidate.candidate=SDPUtils.writeCandidate(cand);} -var sections=SDPUtils.splitSections(pc.localDescription.sdp);if(!end){sections[event.candidate.sdpMLineIndex+1]+='a='+event.candidate.candidate+'\r\n';}else{sections[event.candidate.sdpMLineIndex+1]+='a=end-of-candidates\r\n';} -pc.localDescription.sdp=sections.join('');var complete=pc.transceivers.every(function(transceiver){return transceiver.iceGatherer&&transceiver.iceGatherer.state==='completed';});if(pc.iceGatheringState!=='gathering'){pc.iceGatheringState='gathering';pc._emitGatheringStateChange();} -if(!end){pc._dispatchEvent('icecandidate',event);} -if(complete){pc._dispatchEvent('icecandidate',new Event('icecandidate'));pc.iceGatheringState='complete';pc._emitGatheringStateChange();}};window.setTimeout(function(){candidates.forEach(function(candidate){var e=new Event('RTCIceGatherEvent');e.candidate=candidate;iceGatherer.onlocalcandidate(e);});},0);};RTCPeerConnection.prototype._createIceAndDtlsTransports=function(){var pc=this;var iceTransport=new window.RTCIceTransport(null);iceTransport.onicestatechange=function(){pc._updateConnectionState();};var dtlsTransport=new window.RTCDtlsTransport(iceTransport);dtlsTransport.ondtlsstatechange=function(){pc._updateConnectionState();};dtlsTransport.onerror=function(){Object.defineProperty(dtlsTransport,'state',{value:'failed',writable:true});pc._updateConnectionState();};return{iceTransport:iceTransport,dtlsTransport:dtlsTransport};};RTCPeerConnection.prototype._disposeIceAndDtlsTransports=function(sdpMLineIndex){var iceGatherer=this.transceivers[sdpMLineIndex].iceGatherer;if(iceGatherer){delete iceGatherer.onlocalcandidate;delete this.transceivers[sdpMLineIndex].iceGatherer;} -var iceTransport=this.transceivers[sdpMLineIndex].iceTransport;if(iceTransport){delete iceTransport.onicestatechange;delete this.transceivers[sdpMLineIndex].iceTransport;} -var dtlsTransport=this.transceivers[sdpMLineIndex].dtlsTransport;if(dtlsTransport){delete dtlsTransport.ondtlsstatechange;delete dtlsTransport.onerror;delete this.transceivers[sdpMLineIndex].dtlsTransport;}};RTCPeerConnection.prototype._transceive=function(transceiver,send,recv){var params=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);if(send&&transceiver.rtpSender){params.encodings=transceiver.sendEncodingParameters;params.rtcp={cname:SDPUtils.localCName,compound:transceiver.rtcpParameters.compound};if(transceiver.recvEncodingParameters.length){params.rtcp.ssrc=transceiver.recvEncodingParameters[0].ssrc;} -transceiver.rtpSender.send(params);} -if(recv&&transceiver.rtpReceiver&¶ms.codecs.length>0){if(transceiver.kind==='video'&&transceiver.recvEncodingParameters&&edgeVersion<15019){transceiver.recvEncodingParameters.forEach(function(p){delete p.rtx;});} -if(transceiver.recvEncodingParameters.length){params.encodings=transceiver.recvEncodingParameters;} -params.rtcp={compound:transceiver.rtcpParameters.compound};if(transceiver.rtcpParameters.cname){params.rtcp.cname=transceiver.rtcpParameters.cname;} -if(transceiver.sendEncodingParameters.length){params.rtcp.ssrc=transceiver.sendEncodingParameters[0].ssrc;} -transceiver.rtpReceiver.receive(params);}};RTCPeerConnection.prototype.setLocalDescription=function(description){var pc=this;if(!isActionAllowedInSignalingState('setLocalDescription',description.type,this.signalingState)||this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set local '+description.type+' in state '+pc.signalingState));} -var sections;var sessionpart;if(description.type==='offer'){sections=SDPUtils.splitSections(description.sdp);sessionpart=sections.shift();sections.forEach(function(mediaSection,sdpMLineIndex){var caps=SDPUtils.parseRtpParameters(mediaSection);pc.transceivers[sdpMLineIndex].localCapabilities=caps;});this.transceivers.forEach(function(transceiver,sdpMLineIndex){pc._gather(transceiver.mid,sdpMLineIndex);});}else if(description.type==='answer'){sections=SDPUtils.splitSections(pc.remoteDescription.sdp);sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;sections.forEach(function(mediaSection,sdpMLineIndex){var transceiver=pc.transceivers[sdpMLineIndex];var iceGatherer=transceiver.iceGatherer;var iceTransport=transceiver.iceTransport;var dtlsTransport=transceiver.dtlsTransport;var localCapabilities=transceiver.localCapabilities;var remoteCapabilities=transceiver.remoteCapabilities;var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;if(!rejected&&!transceiver.isDatachannel){var remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);var remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);if(isIceLite){remoteDtlsParameters.role='server';} -if(!pc.usingBundle||sdpMLineIndex===0){pc._gather(transceiver.mid,sdpMLineIndex);if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,isIceLite?'controlling':'controlled');} -if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}} -var params=getCommonCapabilities(localCapabilities,remoteCapabilities);pc._transceive(transceiver,params.codecs.length>0,false);}});} -this.localDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-local-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -return Promise.resolve();};RTCPeerConnection.prototype.setRemoteDescription=function(description){var pc=this;if(!isActionAllowedInSignalingState('setRemoteDescription',description.type,this.signalingState)||this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not set remote '+description.type+' in state '+pc.signalingState));} -var streams={};this.remoteStreams.forEach(function(stream){streams[stream.id]=stream;});var receiverList=[];var sections=SDPUtils.splitSections(description.sdp);var sessionpart=sections.shift();var isIceLite=SDPUtils.matchPrefix(sessionpart,'a=ice-lite').length>0;var usingBundle=SDPUtils.matchPrefix(sessionpart,'a=group:BUNDLE ').length>0;this.usingBundle=usingBundle;var iceOptions=SDPUtils.matchPrefix(sessionpart,'a=ice-options:')[0];if(iceOptions){this.canTrickleIceCandidates=iceOptions.substr(14).split(' ').indexOf('trickle')>=0;}else{this.canTrickleIceCandidates=false;} -sections.forEach(function(mediaSection,sdpMLineIndex){var lines=SDPUtils.splitLines(mediaSection);var kind=SDPUtils.getKind(mediaSection);var rejected=SDPUtils.isRejected(mediaSection)&&SDPUtils.matchPrefix(mediaSection,'a=bundle-only').length===0;var protocol=lines[0].substr(2).split(' ')[2];var direction=SDPUtils.getDirection(mediaSection,sessionpart);var remoteMsid=SDPUtils.parseMsid(mediaSection);var mid=SDPUtils.getMid(mediaSection)||SDPUtils.generateIdentifier();if(kind==='application'&&protocol==='DTLS/SCTP'){pc.transceivers[sdpMLineIndex]={mid:mid,isDatachannel:true};return;} -var transceiver;var iceGatherer;var iceTransport;var dtlsTransport;var rtpReceiver;var sendEncodingParameters;var recvEncodingParameters;var localCapabilities;var track;var remoteCapabilities=SDPUtils.parseRtpParameters(mediaSection);var remoteIceParameters;var remoteDtlsParameters;if(!rejected){remoteIceParameters=SDPUtils.getIceParameters(mediaSection,sessionpart);remoteDtlsParameters=SDPUtils.getDtlsParameters(mediaSection,sessionpart);remoteDtlsParameters.role='client';} -recvEncodingParameters=SDPUtils.parseRtpEncodingParameters(mediaSection);var rtcpParameters=SDPUtils.parseRtcpParameters(mediaSection);var isComplete=SDPUtils.matchPrefix(mediaSection,'a=end-of-candidates',sessionpart).length>0;var cands=SDPUtils.matchPrefix(mediaSection,'a=candidate:').map(function(cand){return SDPUtils.parseCandidate(cand);}).filter(function(cand){return cand.component===1;});if((description.type==='offer'||description.type==='answer')&&!rejected&&usingBundle&&sdpMLineIndex>0&&pc.transceivers[sdpMLineIndex]){pc._disposeIceAndDtlsTransports(sdpMLineIndex);pc.transceivers[sdpMLineIndex].iceGatherer=pc.transceivers[0].iceGatherer;pc.transceivers[sdpMLineIndex].iceTransport=pc.transceivers[0].iceTransport;pc.transceivers[sdpMLineIndex].dtlsTransport=pc.transceivers[0].dtlsTransport;if(pc.transceivers[sdpMLineIndex].rtpSender){pc.transceivers[sdpMLineIndex].rtpSender.setTransport(pc.transceivers[0].dtlsTransport);} -if(pc.transceivers[sdpMLineIndex].rtpReceiver){pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(pc.transceivers[0].dtlsTransport);}} -if(description.type==='offer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex]||pc._createTransceiver(kind);transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,usingBundle);} -if(cands.length&&transceiver.iceTransport.state==='new'){if(isComplete&&(!usingBundle||sdpMLineIndex===0)){transceiver.iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}} -localCapabilities=window.RTCRtpReceiver.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+2)*1001}];var isNewTrack=false;if(direction==='sendrecv'||direction==='sendonly'){isNewTrack=!transceiver.rtpReceiver;rtpReceiver=transceiver.rtpReceiver||new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);if(isNewTrack){var stream;track=rtpReceiver.track;if(remoteMsid&&remoteMsid.stream==='-'){}else if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();Object.defineProperty(streams[remoteMsid.stream],'id',{get:function(){return remoteMsid.stream;}});} -Object.defineProperty(track,'id',{get:function(){return remoteMsid.track;}});stream=streams[remoteMsid.stream];}else{if(!streams.default){streams.default=new window.MediaStream();} -stream=streams.default;} -if(stream){addTrackToStreamAndFireEvent(track,stream);transceiver.associatedRemoteMediaStreams.push(stream);} -receiverList.push([track,rtpReceiver,stream]);}}else if(transceiver.rtpReceiver&&transceiver.rtpReceiver.track){transceiver.associatedRemoteMediaStreams.forEach(function(s){var nativeTrack=s.getTracks().find(function(t){return t.id===transceiver.rtpReceiver.track.id;});if(nativeTrack){removeTrackFromStreamAndFireEvent(nativeTrack,s);}});transceiver.associatedRemoteMediaStreams=[];} -transceiver.localCapabilities=localCapabilities;transceiver.remoteCapabilities=remoteCapabilities;transceiver.rtpReceiver=rtpReceiver;transceiver.rtcpParameters=rtcpParameters;transceiver.sendEncodingParameters=sendEncodingParameters;transceiver.recvEncodingParameters=recvEncodingParameters;pc._transceive(pc.transceivers[sdpMLineIndex],false,isNewTrack);}else if(description.type==='answer'&&!rejected){transceiver=pc.transceivers[sdpMLineIndex];iceGatherer=transceiver.iceGatherer;iceTransport=transceiver.iceTransport;dtlsTransport=transceiver.dtlsTransport;rtpReceiver=transceiver.rtpReceiver;sendEncodingParameters=transceiver.sendEncodingParameters;localCapabilities=transceiver.localCapabilities;pc.transceivers[sdpMLineIndex].recvEncodingParameters=recvEncodingParameters;pc.transceivers[sdpMLineIndex].remoteCapabilities=remoteCapabilities;pc.transceivers[sdpMLineIndex].rtcpParameters=rtcpParameters;if(cands.length&&iceTransport.state==='new'){if((isIceLite||isComplete)&&(!usingBundle||sdpMLineIndex===0)){iceTransport.setRemoteCandidates(cands);}else{cands.forEach(function(candidate){maybeAddCandidate(transceiver.iceTransport,candidate);});}} -if(!usingBundle||sdpMLineIndex===0){if(iceTransport.state==='new'){iceTransport.start(iceGatherer,remoteIceParameters,'controlling');} -if(dtlsTransport.state==='new'){dtlsTransport.start(remoteDtlsParameters);}} -pc._transceive(transceiver,direction==='sendrecv'||direction==='recvonly',direction==='sendrecv'||direction==='sendonly');if(rtpReceiver&&(direction==='sendrecv'||direction==='sendonly')){track=rtpReceiver.track;if(remoteMsid){if(!streams[remoteMsid.stream]){streams[remoteMsid.stream]=new window.MediaStream();} -addTrackToStreamAndFireEvent(track,streams[remoteMsid.stream]);receiverList.push([track,rtpReceiver,streams[remoteMsid.stream]]);}else{if(!streams.default){streams.default=new window.MediaStream();} -addTrackToStreamAndFireEvent(track,streams.default);receiverList.push([track,rtpReceiver,streams.default]);}}else{delete transceiver.rtpReceiver;}}});if(this._dtlsRole===undefined){this._dtlsRole=description.type==='offer'?'active':'passive';} -this.remoteDescription={type:description.type,sdp:description.sdp};switch(description.type){case'offer':this._updateSignalingState('have-remote-offer');break;case'answer':this._updateSignalingState('stable');break;default:throw new TypeError('unsupported type "'+description.type+'"');} -Object.keys(streams).forEach(function(sid){var stream=streams[sid];if(stream.getTracks().length){if(pc.remoteStreams.indexOf(stream)===-1){pc.remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;window.setTimeout(function(){pc._dispatchEvent('addstream',event);});} -receiverList.forEach(function(item){var track=item[0];var receiver=item[1];if(stream.id!==item[2].id){return;} -fireAddTrack(pc,track,receiver,[stream]);});}});receiverList.forEach(function(item){if(item[2]){return;} -fireAddTrack(pc,item[0],item[1],[]);});window.setTimeout(function(){if(!(pc&&pc.transceivers)){return;} -pc.transceivers.forEach(function(transceiver){if(transceiver.iceTransport&&transceiver.iceTransport.state==='new'&&transceiver.iceTransport.getRemoteCandidates().length>0){console.warn('Timeout for addRemoteCandidate. Consider sending '+'an end-of-candidates notification');transceiver.iceTransport.addRemoteCandidate({});}});},4000);return Promise.resolve();};RTCPeerConnection.prototype.close=function(){this.transceivers.forEach(function(transceiver){if(transceiver.iceTransport){transceiver.iceTransport.stop();} -if(transceiver.dtlsTransport){transceiver.dtlsTransport.stop();} -if(transceiver.rtpSender){transceiver.rtpSender.stop();} -if(transceiver.rtpReceiver){transceiver.rtpReceiver.stop();}});this._isClosed=true;this._updateSignalingState('closed');};RTCPeerConnection.prototype._updateSignalingState=function(newState){this.signalingState=newState;var event=new Event('signalingstatechange');this._dispatchEvent('signalingstatechange',event);};RTCPeerConnection.prototype._maybeFireNegotiationNeeded=function(){var pc=this;if(this.signalingState!=='stable'||this.needNegotiation===true){return;} -this.needNegotiation=true;window.setTimeout(function(){if(pc.needNegotiation===false){return;} -pc.needNegotiation=false;var event=new Event('negotiationneeded');pc._dispatchEvent('negotiationneeded',event);},0);};RTCPeerConnection.prototype._updateConnectionState=function(){var newState;var states={'new':0,closed:0,connecting:0,checking:0,connected:0,completed:0,disconnected:0,failed:0};this.transceivers.forEach(function(transceiver){states[transceiver.iceTransport.state]++;states[transceiver.dtlsTransport.state]++;});states.connected+=states.completed;newState='new';if(states.failed>0){newState='failed';}else if(states.connecting>0||states.checking>0){newState='connecting';}else if(states.disconnected>0){newState='disconnected';}else if(states.new>0){newState='new';}else if(states.connected>0||states.completed>0){newState='connected';} -if(newState!==this.iceConnectionState){this.iceConnectionState=newState;var event=new Event('iceconnectionstatechange');this._dispatchEvent('iceconnectionstatechange',event);}};RTCPeerConnection.prototype.createOffer=function(){var pc=this;if(this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createOffer after close'));} -var numAudioTracks=this.transceivers.filter(function(t){return t.kind==='audio';}).length;var numVideoTracks=this.transceivers.filter(function(t){return t.kind==='video';}).length;var offerOptions=arguments[0];if(offerOptions){if(offerOptions.mandatory||offerOptions.optional){throw new TypeError('Legacy mandatory/optional constraints not supported.');} -if(offerOptions.offerToReceiveAudio!==undefined){if(offerOptions.offerToReceiveAudio===true){numAudioTracks=1;}else if(offerOptions.offerToReceiveAudio===false){numAudioTracks=0;}else{numAudioTracks=offerOptions.offerToReceiveAudio;}} -if(offerOptions.offerToReceiveVideo!==undefined){if(offerOptions.offerToReceiveVideo===true){numVideoTracks=1;}else if(offerOptions.offerToReceiveVideo===false){numVideoTracks=0;}else{numVideoTracks=offerOptions.offerToReceiveVideo;}}} -this.transceivers.forEach(function(transceiver){if(transceiver.kind==='audio'){numAudioTracks--;if(numAudioTracks<0){transceiver.wantReceive=false;}}else if(transceiver.kind==='video'){numVideoTracks--;if(numVideoTracks<0){transceiver.wantReceive=false;}}});while(numAudioTracks>0||numVideoTracks>0){if(numAudioTracks>0){this._createTransceiver('audio');numAudioTracks--;} -if(numVideoTracks>0){this._createTransceiver('video');numVideoTracks--;}} -var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId,this._sdpSessionVersion++);this.transceivers.forEach(function(transceiver,sdpMLineIndex){var track=transceiver.track;var kind=transceiver.kind;var mid=SDPUtils.generateIdentifier();transceiver.mid=mid;if(!transceiver.iceGatherer){transceiver.iceGatherer=pc._createIceGatherer(sdpMLineIndex,pc.usingBundle);} -var localCapabilities=window.RTCRtpSender.getCapabilities(kind);if(edgeVersion<15019){localCapabilities.codecs=localCapabilities.codecs.filter(function(codec){return codec.name!=='rtx';});} -localCapabilities.codecs.forEach(function(codec){if(codec.name==='H264'&&codec.parameters['level-asymmetry-allowed']===undefined){codec.parameters['level-asymmetry-allowed']='1';}});var sendEncodingParameters=transceiver.sendEncodingParameters||[{ssrc:(2*sdpMLineIndex+1)*1001}];if(track){if(edgeVersion>=15019&&kind==='video'&&!sendEncodingParameters[0].rtx){sendEncodingParameters[0].rtx={ssrc:sendEncodingParameters[0].ssrc+1};}} -if(transceiver.wantReceive){transceiver.rtpReceiver=new window.RTCRtpReceiver(transceiver.dtlsTransport,kind);} -transceiver.localCapabilities=localCapabilities;transceiver.sendEncodingParameters=sendEncodingParameters;});if(this._config.bundlePolicy!=='max-compat'){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -sdp+='a=ice-options:trickle\r\n';this.transceivers.forEach(function(transceiver,sdpMLineIndex){sdp+=writeMediaSection(transceiver,transceiver.localCapabilities,'offer',transceiver.stream,pc._dtlsRole);sdp+='a=rtcp-rsize\r\n';if(transceiver.iceGatherer&&pc.iceGatheringState!=='new'&&(sdpMLineIndex===0||!pc.usingBundle)){transceiver.iceGatherer.getLocalCandidates().forEach(function(cand){cand.component=1;sdp+='a='+SDPUtils.writeCandidate(cand)+'\r\n';});if(transceiver.iceGatherer.state==='completed'){sdp+='a=end-of-candidates\r\n';}}});var desc=new window.RTCSessionDescription({type:'offer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.createAnswer=function(){var pc=this;if(this._isClosed){return Promise.reject(makeError('InvalidStateError','Can not call createAnswer after close'));} -var sdp=SDPUtils.writeSessionBoilerplate(this._sdpSessionId,this._sdpSessionVersion++);if(this.usingBundle){sdp+='a=group:BUNDLE '+this.transceivers.map(function(t){return t.mid;}).join(' ')+'\r\n';} -var mediaSectionsInOffer=SDPUtils.splitSections(this.remoteDescription.sdp).length-1;this.transceivers.forEach(function(transceiver,sdpMLineIndex){if(sdpMLineIndex+1>mediaSectionsInOffer){return;} -if(transceiver.isDatachannel){sdp+='m=application 0 DTLS/SCTP 5000\r\n'+'c=IN IP4 0.0.0.0\r\n'+'a=mid:'+transceiver.mid+'\r\n';return;} -if(transceiver.stream){var localTrack;if(transceiver.kind==='audio'){localTrack=transceiver.stream.getAudioTracks()[0];}else if(transceiver.kind==='video'){localTrack=transceiver.stream.getVideoTracks()[0];} -if(localTrack){if(edgeVersion>=15019&&transceiver.kind==='video'&&!transceiver.sendEncodingParameters[0].rtx){transceiver.sendEncodingParameters[0].rtx={ssrc:transceiver.sendEncodingParameters[0].ssrc+1};}}} -var commonCapabilities=getCommonCapabilities(transceiver.localCapabilities,transceiver.remoteCapabilities);var hasRtx=commonCapabilities.codecs.filter(function(c){return c.name.toLowerCase()==='rtx';}).length;if(!hasRtx&&transceiver.sendEncodingParameters[0].rtx){delete transceiver.sendEncodingParameters[0].rtx;} -sdp+=writeMediaSection(transceiver,commonCapabilities,'answer',transceiver.stream,pc._dtlsRole);if(transceiver.rtcpParameters&&transceiver.rtcpParameters.reducedSize){sdp+='a=rtcp-rsize\r\n';}});var desc=new window.RTCSessionDescription({type:'answer',sdp:sdp});return Promise.resolve(desc);};RTCPeerConnection.prototype.addIceCandidate=function(candidate){var sections;if(!candidate||candidate.candidate===''){for(var j=0;j0?SDPUtils.parseCandidate(candidate.candidate):{};if(cand.protocol==='tcp'&&(cand.port===0||cand.port===9)){return Promise.resolve();} -if(cand.component&&cand.component!==1){return Promise.resolve();} -if(sdpMLineIndex===0||(sdpMLineIndex>0&&transceiver.iceTransport!==this.transceivers[0].iceTransport)){if(!maybeAddCandidate(transceiver.iceTransport,cand)){return Promise.reject(makeError('OperationError','Can not add ICE candidate'));}} -var candidateString=candidate.candidate.trim();if(candidateString.indexOf('a=')===0){candidateString=candidateString.substr(2);} -sections=SDPUtils.splitSections(this.remoteDescription.sdp);sections[sdpMLineIndex+1]+='a='+ -(cand.type?candidateString:'end-of-candidates') -+'\r\n';this.remoteDescription.sdp=sections.join('');}else{return Promise.reject(makeError('OperationError','Can not add ICE candidate'));}} -return Promise.resolve();};RTCPeerConnection.prototype.getStats=function(){var promises=[];this.transceivers.forEach(function(transceiver){['rtpSender','rtpReceiver','iceGatherer','iceTransport','dtlsTransport'].forEach(function(method){if(transceiver[method]){promises.push(transceiver[method].getStats());}});});var fixStatsType=function(stat){return{inboundrtp:'inbound-rtp',outboundrtp:'outbound-rtp',candidatepair:'candidate-pair',localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[stat.type]||stat.type;};return new Promise(function(resolve){var results=new Map();Promise.all(promises).then(function(res){res.forEach(function(result){Object.keys(result).forEach(function(id){result[id].type=fixStatsType(result[id]);results.set(id,result[id]);});});resolve(results);});});};var methods=['createOffer','createAnswer'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[0]==='function'||typeof args[1]==='function'){return nativeMethod.apply(this,[arguments[2]]).then(function(description){if(typeof args[0]==='function'){args[0].apply(null,[description]);}},function(error){if(typeof args[1]==='function'){args[1].apply(null,[error]);}});} -return nativeMethod.apply(this,arguments);};});methods=['setLocalDescription','setRemoteDescription','addIceCandidate'];methods.forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'||typeof args[2]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}},function(error){if(typeof args[2]==='function'){args[2].apply(null,[error]);}});} -return nativeMethod.apply(this,arguments);};});['getStats'].forEach(function(method){var nativeMethod=RTCPeerConnection.prototype[method];RTCPeerConnection.prototype[method]=function(){var args=arguments;if(typeof args[1]==='function'){return nativeMethod.apply(this,arguments).then(function(){if(typeof args[1]==='function'){args[1].apply(null);}});} -return nativeMethod.apply(this,arguments);};});return RTCPeerConnection;};},{"sdp":2}],2:[function(require,module,exports){'use strict';var SDPUtils={};SDPUtils.generateIdentifier=function(){return Math.random().toString(36).substr(2,10);};SDPUtils.localCName=SDPUtils.generateIdentifier();SDPUtils.splitLines=function(blob){return blob.trim().split('\n').map(function(line){return line.trim();});};SDPUtils.splitSections=function(blob){var parts=blob.split('\nm=');return parts.map(function(part,index){return(index>0?'m='+part:part).trim()+'\r\n';});};SDPUtils.matchPrefix=function(blob,prefix){return SDPUtils.splitLines(blob).filter(function(line){return line.indexOf(prefix)===0;});};SDPUtils.parseCandidate=function(line){var parts;if(line.indexOf('a=candidate:')===0){parts=line.substring(12).split(' ');}else{parts=line.substring(10).split(' ');} -var candidate={foundation:parts[0],component:parseInt(parts[1],10),protocol:parts[2].toLowerCase(),priority:parseInt(parts[3],10),ip:parts[4],port:parseInt(parts[5],10),type:parts[7]};for(var i=8;i0?parts[0].split('/')[1]:'sendrecv',uri:parts[1]};};SDPUtils.writeExtmap=function(headerExtension){return'a=extmap:'+(headerExtension.id||headerExtension.preferredId)+ -(headerExtension.direction&&headerExtension.direction!=='sendrecv'?'/'+headerExtension.direction:'')+' '+headerExtension.uri+'\r\n';};SDPUtils.parseFmtp=function(line){var parsed={};var kv;var parts=line.substr(line.indexOf(' ')+1).split(';');for(var j=0;j-1){parts.attribute=line.substr(sp+1,colon-sp-1);parts.value=line.substr(colon+1);}else{parts.attribute=line.substr(sp+1);} -return parts;};SDPUtils.getMid=function(mediaSection){var mid=SDPUtils.matchPrefix(mediaSection,'a=mid:')[0];if(mid){return mid.substr(6);}} -SDPUtils.parseFingerprint=function(line){var parts=line.substr(14).split(' ');return{algorithm:parts[0].toLowerCase(),value:parts[1]};};SDPUtils.getDtlsParameters=function(mediaSection,sessionpart){var lines=SDPUtils.matchPrefix(mediaSection+sessionpart,'a=fingerprint:');return{role:'auto',fingerprints:lines.map(SDPUtils.parseFingerprint)};};SDPUtils.writeDtlsParameters=function(params,setupType){var sdp='a=setup:'+setupType+'\r\n';params.fingerprints.forEach(function(fp){sdp+='a=fingerprint:'+fp.algorithm+' '+fp.value+'\r\n';});return sdp;};SDPUtils.getIceParameters=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);lines=lines.concat(SDPUtils.splitLines(sessionpart));var iceParameters={usernameFragment:lines.filter(function(line){return line.indexOf('a=ice-ufrag:')===0;})[0].substr(12),password:lines.filter(function(line){return line.indexOf('a=ice-pwd:')===0;})[0].substr(10)};return iceParameters;};SDPUtils.writeIceParameters=function(params){return'a=ice-ufrag:'+params.usernameFragment+'\r\n'+'a=ice-pwd:'+params.password+'\r\n';};SDPUtils.parseRtpParameters=function(mediaSection){var description={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]};var lines=SDPUtils.splitLines(mediaSection);var mline=lines[0].split(' ');for(var i=3;i0?'9':'0';sdp+=' UDP/TLS/RTP/SAVPF ';sdp+=caps.codecs.map(function(codec){if(codec.preferredPayloadType!==undefined){return codec.preferredPayloadType;} -return codec.payloadType;}).join(' ')+'\r\n';sdp+='c=IN IP4 0.0.0.0\r\n';sdp+='a=rtcp:9 IN IP4 0.0.0.0\r\n';caps.codecs.forEach(function(codec){sdp+=SDPUtils.writeRtpMap(codec);sdp+=SDPUtils.writeFmtp(codec);sdp+=SDPUtils.writeRtcpFb(codec);});var maxptime=0;caps.codecs.forEach(function(codec){if(codec.maxptime>maxptime){maxptime=codec.maxptime;}});if(maxptime>0){sdp+='a=maxptime:'+maxptime+'\r\n';} -sdp+='a=rtcp-mux\r\n';caps.headerExtensions.forEach(function(extension){sdp+=SDPUtils.writeExtmap(extension);});return sdp;};SDPUtils.parseRtpEncodingParameters=function(mediaSection){var encodingParameters=[];var description=SDPUtils.parseRtpParameters(mediaSection);var hasRed=description.fecMechanisms.indexOf('RED')!==-1;var hasUlpfec=description.fecMechanisms.indexOf('ULPFEC')!==-1;var ssrcs=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='cname';});var primarySsrc=ssrcs.length>0&&ssrcs[0].ssrc;var secondarySsrc;var flows=SDPUtils.matchPrefix(mediaSection,'a=ssrc-group:FID').map(function(line){var parts=line.split(' ');parts.shift();return parts.map(function(part){return parseInt(part,10);});});if(flows.length>0&&flows[0].length>1&&flows[0][0]===primarySsrc){secondarySsrc=flows[0][1];} -description.codecs.forEach(function(codec){if(codec.name.toUpperCase()==='RTX'&&codec.parameters.apt){var encParam={ssrc:primarySsrc,codecPayloadType:parseInt(codec.parameters.apt,10),rtx:{ssrc:secondarySsrc}};encodingParameters.push(encParam);if(hasRed){encParam=JSON.parse(JSON.stringify(encParam));encParam.fec={ssrc:secondarySsrc,mechanism:hasUlpfec?'red+ulpfec':'red'};encodingParameters.push(encParam);}}});if(encodingParameters.length===0&&primarySsrc){encodingParameters.push({ssrc:primarySsrc});} -var bandwidth=SDPUtils.matchPrefix(mediaSection,'b=');if(bandwidth.length){if(bandwidth[0].indexOf('b=TIAS:')===0){bandwidth=parseInt(bandwidth[0].substr(7),10);}else if(bandwidth[0].indexOf('b=AS:')===0){bandwidth=parseInt(bandwidth[0].substr(5),10)*1000*0.95 --(50*40*8);}else{bandwidth=undefined;} -encodingParameters.forEach(function(params){params.maxBitrate=bandwidth;});} -return encodingParameters;};SDPUtils.parseRtcpParameters=function(mediaSection){var rtcpParameters={};var cname;var remoteSsrc=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(obj){return obj.attribute==='cname';})[0];if(remoteSsrc){rtcpParameters.cname=remoteSsrc.value;rtcpParameters.ssrc=remoteSsrc.ssrc;} -var rsize=SDPUtils.matchPrefix(mediaSection,'a=rtcp-rsize');rtcpParameters.reducedSize=rsize.length>0;rtcpParameters.compound=rsize.length===0;var mux=SDPUtils.matchPrefix(mediaSection,'a=rtcp-mux');rtcpParameters.mux=mux.length>0;return rtcpParameters;};SDPUtils.parseMsid=function(mediaSection){var parts;var spec=SDPUtils.matchPrefix(mediaSection,'a=msid:');if(spec.length===1){parts=spec[0].substr(7).split(' ');return{stream:parts[0],track:parts[1]};} -var planB=SDPUtils.matchPrefix(mediaSection,'a=ssrc:').map(function(line){return SDPUtils.parseSsrcMedia(line);}).filter(function(parts){return parts.attribute==='msid';});if(planB.length>0){parts=planB[0].value.split(' ');return{stream:parts[0],track:parts[1]};}};SDPUtils.generateSessionId=function(){return Math.random().toString().substr(2,21);};SDPUtils.writeSessionBoilerplate=function(sessId,sessVer){var sessionId;var version=sessVer!==undefined?sessVer:2;if(sessId){sessionId=sessId;}else{sessionId=SDPUtils.generateSessionId();} -return'v=0\r\n'+'o=thisisadapterortc '+sessionId+' '+version+' IN IP4 127.0.0.1\r\n'+'s=-\r\n'+'t=0 0\r\n';};SDPUtils.writeMediaSection=function(transceiver,caps,type,stream){var sdp=SDPUtils.writeRtpDescription(transceiver.kind,caps);sdp+=SDPUtils.writeIceParameters(transceiver.iceGatherer.getLocalParameters());sdp+=SDPUtils.writeDtlsParameters(transceiver.dtlsTransport.getLocalParameters(),type==='offer'?'actpass':'active');sdp+='a=mid:'+transceiver.mid+'\r\n';if(transceiver.direction){sdp+='a='+transceiver.direction+'\r\n';}else if(transceiver.rtpSender&&transceiver.rtpReceiver){sdp+='a=sendrecv\r\n';}else if(transceiver.rtpSender){sdp+='a=sendonly\r\n';}else if(transceiver.rtpReceiver){sdp+='a=recvonly\r\n';}else{sdp+='a=inactive\r\n';} -if(transceiver.rtpSender){var msid='msid:'+stream.id+' '+ -transceiver.rtpSender.track.id+'\r\n';sdp+='a='+msid;sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' '+msid;if(transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' '+msid;sdp+='a=ssrc-group:FID '+ -transceiver.sendEncodingParameters[0].ssrc+' '+ -transceiver.sendEncodingParameters[0].rtx.ssrc+'\r\n';}} -sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].ssrc+' cname:'+SDPUtils.localCName+'\r\n';if(transceiver.rtpSender&&transceiver.sendEncodingParameters[0].rtx){sdp+='a=ssrc:'+transceiver.sendEncodingParameters[0].rtx.ssrc+' cname:'+SDPUtils.localCName+'\r\n';} -return sdp;};SDPUtils.getDirection=function(mediaSection,sessionpart){var lines=SDPUtils.splitLines(mediaSection);for(var i=0;i=65){return this.shimAddTrackRemoveTrackWithNative(window);} -var origGetLocalStreams=window.RTCPeerConnection.prototype.getLocalStreams;window.RTCPeerConnection.prototype.getLocalStreams=function(){var pc=this;var nativeStreams=origGetLocalStreams.apply(this);pc._reverseStreams=pc._reverseStreams||{};return nativeStreams.map(function(stream){return pc._reverseStreams[stream.id];});};var origAddStream=window.RTCPeerConnection.prototype.addStream;window.RTCPeerConnection.prototype.addStream=function(stream){var pc=this;pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};stream.getTracks().forEach(function(track){var alreadyExists=pc.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');}});if(!pc._reverseStreams[stream.id]){var newStream=new window.MediaStream(stream.getTracks());pc._streams[stream.id]=newStream;pc._reverseStreams[newStream.id]=stream;stream=newStream;} -origAddStream.apply(pc,[stream]);};var origRemoveStream=window.RTCPeerConnection.prototype.removeStream;window.RTCPeerConnection.prototype.removeStream=function(stream){var pc=this;pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};origRemoveStream.apply(pc,[(pc._streams[stream.id]||stream)]);delete pc._reverseStreams[(pc._streams[stream.id]?pc._streams[stream.id].id:stream.id)];delete pc._streams[stream.id];};window.RTCPeerConnection.prototype.addTrack=function(track,stream){var pc=this;if(pc.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');} -var streams=[].slice.call(arguments,1);if(streams.length!==1||!streams[0].getTracks().find(function(t){return t===track;})){throw new DOMException('The adapter.js addTrack polyfill only supports a single '+' stream which is associated with the specified track.','NotSupportedError');} -var alreadyExists=pc.getSenders().find(function(s){return s.track===track;});if(alreadyExists){throw new DOMException('Track already exists.','InvalidAccessError');} -pc._streams=pc._streams||{};pc._reverseStreams=pc._reverseStreams||{};var oldStream=pc._streams[stream.id];if(oldStream){oldStream.addTrack(track);Promise.resolve().then(function(){pc.dispatchEvent(new Event('negotiationneeded'));});}else{var newStream=new window.MediaStream([track]);pc._streams[stream.id]=newStream;pc._reverseStreams[newStream.id]=stream;pc.addStream(newStream);} -return pc.getSenders().find(function(s){return s.track===track;});};function replaceInternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(internalStream.id,'g'),externalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});} -function replaceExternalStreamId(pc,description){var sdp=description.sdp;Object.keys(pc._reverseStreams||[]).forEach(function(internalId){var externalStream=pc._reverseStreams[internalId];var internalStream=pc._streams[externalStream.id];sdp=sdp.replace(new RegExp(externalStream.id,'g'),internalStream.id);});return new RTCSessionDescription({type:description.type,sdp:sdp});} -['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var pc=this;var args=arguments;var isLegacyCall=arguments.length&&typeof arguments[0]==='function';if(isLegacyCall){return nativeMethod.apply(pc,[function(description){var desc=replaceInternalStreamId(pc,description);args[0].apply(null,[desc]);},function(err){if(args[1]){args[1].apply(null,err);}},arguments[2]]);} -return nativeMethod.apply(pc,arguments).then(function(description){return replaceInternalStreamId(pc,description);});};});var origSetLocalDescription=window.RTCPeerConnection.prototype.setLocalDescription;window.RTCPeerConnection.prototype.setLocalDescription=function(){var pc=this;if(!arguments.length||!arguments[0].type){return origSetLocalDescription.apply(pc,arguments);} -arguments[0]=replaceExternalStreamId(pc,arguments[0]);return origSetLocalDescription.apply(pc,arguments);};var origLocalDescription=Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype,'localDescription');Object.defineProperty(window.RTCPeerConnection.prototype,'localDescription',{get:function(){var pc=this;var description=origLocalDescription.get.apply(this);if(description.type===''){return description;} -return replaceInternalStreamId(pc,description);}});window.RTCPeerConnection.prototype.removeTrack=function(sender){var pc=this;if(pc.signalingState==='closed'){throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.','InvalidStateError');} -if(!sender._pc){throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack '+'does not implement interface RTCRtpSender.','TypeError');} -var isLocal=sender._pc===pc;if(!isLocal){throw new DOMException('Sender was not created by this connection.','InvalidAccessError');} -pc._streams=pc._streams||{};var stream;Object.keys(pc._streams).forEach(function(streamid){var hasTrack=pc._streams[streamid].getTracks().find(function(track){return sender.track===track;});if(hasTrack){stream=pc._streams[streamid];}});if(stream){if(stream.getTracks().length===1){pc.removeStream(pc._reverseStreams[stream.id]);}else{stream.removeTrack(sender.track);} -pc.dispatchEvent(new Event('negotiationneeded'));}};},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(!window.RTCPeerConnection&&window.webkitRTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){logging('PeerConnection');if(pcConfig&&pcConfig.iceTransportPolicy){pcConfig.iceTransports=pcConfig.iceTransportPolicy;} -return new window.webkitRTCPeerConnection(pcConfig,pcConstraints);};window.RTCPeerConnection.prototype=window.webkitRTCPeerConnection.prototype;if(window.webkitRTCPeerConnection.generateCertificate){Object.defineProperty(window.RTCPeerConnection,'generateCertificate',{get:function(){return window.webkitRTCPeerConnection.generateCertificate;}});}}else{var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i0&&typeof selector==='function'){return origGetStats.apply(this,arguments);} -if(origGetStats.length===0&&(arguments.length===0||typeof arguments[0]!=='function')){return origGetStats.apply(this,[]);} -var fixChromeStats_=function(response){var standardReport={};var reports=response.result();reports.forEach(function(report){var standardStats={id:report.id,timestamp:report.timestamp,type:{localcandidate:'local-candidate',remotecandidate:'remote-candidate'}[report.type]||report.type};report.names().forEach(function(name){standardStats[name]=report.stat(name);});standardReport[standardStats.id]=standardStats;});return standardReport;};var makeMapStats=function(stats){return new Map(Object.keys(stats).map(function(key){return[key,stats[key]];}));};if(arguments.length>=2){var successCallbackWrapper_=function(response){args[1](makeMapStats(fixChromeStats_(response)));};return origGetStats.apply(this,[successCallbackWrapper_,arguments[0]]);} -return new Promise(function(resolve,reject){origGetStats.apply(pc,[function(response){resolve(makeMapStats(fixChromeStats_(response)));},reject]);}).then(successCallback,errorCallback);};if(browserDetails.version<51){['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var args=arguments;var pc=this;var promise=new Promise(function(resolve,reject){nativeMethod.apply(pc,[args[0],resolve,reject]);});if(args.length<2){return promise;} -return promise.then(function(){args[1].apply(null,[]);},function(err){if(args.length>=3){args[2].apply(null,[err]);}});};});} -if(browserDetails.version<52){['createOffer','createAnswer'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){var pc=this;if(arguments.length<1||(arguments.length===1&&typeof arguments[0]==='object')){var opts=arguments.length===1?arguments[0]:undefined;return new Promise(function(resolve,reject){nativeMethod.apply(pc,[resolve,reject,opts]);});} -return nativeMethod.apply(this,arguments);};});} -['setLocalDescription','setRemoteDescription','addIceCandidate'].forEach(function(method){var nativeMethod=window.RTCPeerConnection.prototype[method];window.RTCPeerConnection.prototype[method]=function(){arguments[0]=new((method==='addIceCandidate')?window.RTCIceCandidate:window.RTCSessionDescription)(arguments[0]);return nativeMethod.apply(this,arguments);};});var nativeAddIceCandidate=window.RTCPeerConnection.prototype.addIceCandidate;window.RTCPeerConnection.prototype.addIceCandidate=function(){if(!arguments[0]){if(arguments[1]){arguments[1].apply(null);} -return Promise.resolve();} -return nativeAddIceCandidate.apply(this,arguments);};}};},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){'use strict';var utils=require('../utils.js');var logging=utils.log;module.exports=function(window){var browserDetails=utils.detectBrowser(window);var navigator=window&&window.navigator;var constraintsToChrome_=function(c){if(typeof c!=='object'||c.mandatory||c.optional){return c;} -var cc={};Object.keys(c).forEach(function(key){if(key==='require'||key==='advanced'||key==='mediaSource'){return;} -var r=(typeof c[key]==='object')?c[key]:{ideal:c[key]};if(r.exact!==undefined&&typeof r.exact==='number'){r.min=r.max=r.exact;} -var oldname_=function(prefix,name){if(prefix){return prefix+name.charAt(0).toUpperCase()+name.slice(1);} -return(name==='deviceId')?'sourceId':name;};if(r.ideal!==undefined){cc.optional=cc.optional||[];var oc={};if(typeof r.ideal==='number'){oc[oldname_('min',key)]=r.ideal;cc.optional.push(oc);oc={};oc[oldname_('max',key)]=r.ideal;cc.optional.push(oc);}else{oc[oldname_('',key)]=r.ideal;cc.optional.push(oc);}} -if(r.exact!==undefined&&typeof r.exact!=='number'){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_('',key)]=r.exact;}else{['min','max'].forEach(function(mix){if(r[mix]!==undefined){cc.mandatory=cc.mandatory||{};cc.mandatory[oldname_(mix,key)]=r[mix];}});}});if(c.advanced){cc.optional=(cc.optional||[]).concat(c.advanced);} -return cc;};var shimConstraints_=function(constraints,func){if(browserDetails.version>=61){return func(constraints);} -constraints=JSON.parse(JSON.stringify(constraints));if(constraints&&typeof constraints.audio==='object'){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};constraints=JSON.parse(JSON.stringify(constraints));remap(constraints.audio,'autoGainControl','googAutoGainControl');remap(constraints.audio,'noiseSuppression','googNoiseSuppression');constraints.audio=constraintsToChrome_(constraints.audio);} -if(constraints&&typeof constraints.video==='object'){var face=constraints.video.facingMode;face=face&&((typeof face==='object')?face:{ideal:face});var getSupportedFacingModeLies=browserDetails.version<66;if((face&&(face.exact==='user'||face.exact==='environment'||face.ideal==='user'||face.ideal==='environment'))&&!(navigator.mediaDevices.getSupportedConstraints&&navigator.mediaDevices.getSupportedConstraints().facingMode&&!getSupportedFacingModeLies)){delete constraints.video.facingMode;var matches;if(face.exact==='environment'||face.ideal==='environment'){matches=['back','rear'];}else if(face.exact==='user'||face.ideal==='user'){matches=['front'];} -if(matches){return navigator.mediaDevices.enumerateDevices().then(function(devices){devices=devices.filter(function(d){return d.kind==='videoinput';});var dev=devices.find(function(d){return matches.some(function(match){return d.label.toLowerCase().indexOf(match)!==-1;});});if(!dev&&devices.length&&matches.indexOf('back')!==-1){dev=devices[devices.length-1];} -if(dev){constraints.video.deviceId=face.exact?{exact:dev.deviceId}:{ideal:dev.deviceId};} -constraints.video=constraintsToChrome_(constraints.video);logging('chrome: '+JSON.stringify(constraints));return func(constraints);});}} -constraints.video=constraintsToChrome_(constraints.video);} -logging('chrome: '+JSON.stringify(constraints));return func(constraints);};var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError',InvalidStateError:'NotReadableError',DevicesNotFoundError:'NotFoundError',ConstraintNotSatisfiedError:'OverconstrainedError',TrackStartError:'NotReadableError',MediaDeviceFailedDueToShutdown:'NotReadableError',MediaDeviceKillSwitchOn:'NotReadableError'}[e.name]||e.name,message:e.message,constraint:e.constraintName,toString:function(){return this.name+(this.message&&': ')+this.message;}};};var getUserMedia_=function(constraints,onSuccess,onError){shimConstraints_(constraints,function(c){navigator.webkitGetUserMedia(c,onSuccess,function(e){if(onError){onError(shimError_(e));}});});};navigator.getUserMedia=getUserMedia_;var getUserMediaPromise_=function(constraints){return new Promise(function(resolve,reject){navigator.getUserMedia(constraints,resolve,reject);});};if(!navigator.mediaDevices){navigator.mediaDevices={getUserMedia:getUserMediaPromise_,enumerateDevices:function(){return new Promise(function(resolve){var kinds={audio:'audioinput',video:'videoinput'};return window.MediaStreamTrack.getSources(function(devices){resolve(devices.map(function(device){return{label:device.label,kind:kinds[device.kind],deviceId:device.id,groupId:''};}));});});},getSupportedConstraints:function(){return{deviceId:true,echoCancellation:true,facingMode:true,frameRate:true,height:true,width:true};}};} -if(!navigator.mediaDevices.getUserMedia){navigator.mediaDevices.getUserMedia=function(constraints){return getUserMediaPromise_(constraints);};}else{var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(cs){return shimConstraints_(cs,function(c){return origGetUserMedia(c).then(function(stream){if(c.audio&&!stream.getAudioTracks().length||c.video&&!stream.getVideoTracks().length){stream.getTracks().forEach(function(track){track.stop();});throw new DOMException('','NotFoundError');} -return stream;},function(e){return Promise.reject(shimError_(e));});});};} -if(typeof navigator.mediaDevices.addEventListener==='undefined'){navigator.mediaDevices.addEventListener=function(){logging('Dummy mediaDevices.addEventListener called.');};} -if(typeof navigator.mediaDevices.removeEventListener==='undefined'){navigator.mediaDevices.removeEventListener=function(){logging('Dummy mediaDevices.removeEventListener called.');};}};},{"../utils.js":13}],7:[function(require,module,exports){'use strict';var SDPUtils=require('sdp');var utils=require('./utils');module.exports={shimRTCIceCandidate:function(window){if(window.RTCIceCandidate&&'foundation'in -window.RTCIceCandidate.prototype){return;} -var NativeRTCIceCandidate=window.RTCIceCandidate;window.RTCIceCandidate=function(args){if(typeof args==='object'&&args.candidate&&args.candidate.indexOf('a=')===0){args=JSON.parse(JSON.stringify(args));args.candidate=args.candidate.substr(2);} -var nativeCandidate=new NativeRTCIceCandidate(args);var parsedCandidate=SDPUtils.parseCandidate(args.candidate);var augmentedCandidate=Object.assign(nativeCandidate,parsedCandidate);augmentedCandidate.toJSON=function(){return{candidate:augmentedCandidate.candidate,sdpMid:augmentedCandidate.sdpMid,sdpMLineIndex:augmentedCandidate.sdpMLineIndex,usernameFragment:augmentedCandidate.usernameFragment,};};return augmentedCandidate;};utils.wrapPeerConnectionEvent(window,'icecandidate',function(e){if(e.candidate){Object.defineProperty(e,'candidate',{value:new window.RTCIceCandidate(e.candidate),writable:'false'});} -return e;});},shimCreateObjectURL:function(window){var URL=window&&window.URL;if(!(typeof window==='object'&&window.HTMLMediaElement&&'srcObject'in window.HTMLMediaElement.prototype&&URL.createObjectURL&&URL.revokeObjectURL)){return undefined;} -var nativeCreateObjectURL=URL.createObjectURL.bind(URL);var nativeRevokeObjectURL=URL.revokeObjectURL.bind(URL);var streams=new Map(),newId=0;URL.createObjectURL=function(stream){if('getTracks'in stream){var url='polyblob:'+(++newId);streams.set(url,stream);utils.deprecated('URL.createObjectURL(stream)','elem.srcObject = stream');return url;} -return nativeCreateObjectURL(stream);};URL.revokeObjectURL=function(url){nativeRevokeObjectURL(url);streams.delete(url);};var dsc=Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype,'src');Object.defineProperty(window.HTMLMediaElement.prototype,'src',{get:function(){return dsc.get.apply(this);},set:function(url){this.srcObject=streams.get(url)||null;return dsc.set.apply(this,[url]);}});var nativeSetAttribute=window.HTMLMediaElement.prototype.setAttribute;window.HTMLMediaElement.prototype.setAttribute=function(){if(arguments.length===2&&(''+arguments[0]).toLowerCase()==='src'){this.srcObject=streams.get(arguments[1])||null;} -return nativeSetAttribute.apply(this,arguments);};},shimMaxMessageSize:function(window){if(window.RTCSctpTransport||!window.RTCPeerConnection){return;} -var browserDetails=utils.detectBrowser(window);if(!('sctp'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'sctp',{get:function(){return typeof this._sctp==='undefined'?null:this._sctp;}});} -var sctpInDescription=function(description){var sections=SDPUtils.splitSections(description.sdp);sections.shift();return sections.some(function(mediaSection){var mLine=SDPUtils.parseMLine(mediaSection);return mLine&&mLine.kind==='application'&&mLine.protocol.indexOf('SCTP')!==-1;});};var getRemoteFirefoxVersion=function(description){var match=description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(match===null||match.length<2){return-1;} -var version=parseInt(match[1],10);return version!==version?-1:version;};var getCanSendMaxMessageSize=function(remoteIsFirefox){var canSendMaxMessageSize=65536;if(browserDetails.browser==='firefox'){if(browserDetails.version<57){if(remoteIsFirefox===-1){canSendMaxMessageSize=16384;}else{canSendMaxMessageSize=2147483637;}}else{canSendMaxMessageSize=browserDetails.version===57?65535:65536;}} -return canSendMaxMessageSize;};var getMaxMessageSize=function(description,remoteIsFirefox){var maxMessageSize=65536;if(browserDetails.browser==='firefox'&&browserDetails.version===57){maxMessageSize=65535;} -var match=SDPUtils.matchPrefix(description.sdp,'a=max-message-size:');if(match.length>0){maxMessageSize=parseInt(match[0].substr(19),10);}else if(browserDetails.browser==='firefox'&&remoteIsFirefox!==-1){maxMessageSize=2147483637;} -return maxMessageSize;};var origSetRemoteDescription=window.RTCPeerConnection.prototype.setRemoteDescription;window.RTCPeerConnection.prototype.setRemoteDescription=function(){var pc=this;pc._sctp=null;if(sctpInDescription(arguments[0])){var isFirefox=getRemoteFirefoxVersion(arguments[0]);var canSendMMS=getCanSendMaxMessageSize(isFirefox);var remoteMMS=getMaxMessageSize(arguments[0],isFirefox);var maxMessageSize;if(canSendMMS===0&&remoteMMS===0){maxMessageSize=Number.POSITIVE_INFINITY;}else if(canSendMMS===0||remoteMMS===0){maxMessageSize=Math.max(canSendMMS,remoteMMS);}else{maxMessageSize=Math.min(canSendMMS,remoteMMS);} -var sctp={};Object.defineProperty(sctp,'maxMessageSize',{get:function(){return maxMessageSize;}});pc._sctp=sctp;} -return origSetRemoteDescription.apply(pc,arguments);};},shimSendThrowTypeError:function(window){var origCreateDataChannel=window.RTCPeerConnection.prototype.createDataChannel;window.RTCPeerConnection.prototype.createDataChannel=function(){var pc=this;var dataChannel=origCreateDataChannel.apply(pc,arguments);var origDataChannelSend=dataChannel.send;dataChannel.send=function(){var dc=this;var data=arguments[0];var length=data.length||data.size||data.byteLength;if(length>pc.sctp.maxMessageSize){throw new DOMException('Message too large (can send a maximum of '+ -pc.sctp.maxMessageSize+' bytes)','TypeError');} -return origDataChannelSend.apply(dc,arguments);};return dataChannel;};}};},{"./utils":13,"sdp":2}],8:[function(require,module,exports){'use strict';var utils=require('../utils');var shimRTCPeerConnection=require('rtcpeerconnection-shim');module.exports={shimGetUserMedia:require('./getusermedia'),shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(window.RTCIceGatherer){if(!window.RTCIceCandidate){window.RTCIceCandidate=function(args){return args;};} -if(!window.RTCSessionDescription){window.RTCSessionDescription=function(args){return args;};} -if(browserDetails.version<15025){var origMSTEnabled=Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype,'enabled');Object.defineProperty(window.MediaStreamTrack.prototype,'enabled',{set:function(value){origMSTEnabled.set.call(this,value);var ev=new Event('enabled');ev.enabled=value;this.dispatchEvent(ev);}});}} -if(window.RTCRtpSender&&!('dtmf'in window.RTCRtpSender.prototype)){Object.defineProperty(window.RTCRtpSender.prototype,'dtmf',{get:function(){if(this._dtmf===undefined){if(this.track.kind==='audio'){this._dtmf=new window.RTCDtmfSender(this);}else if(this.track.kind==='video'){this._dtmf=null;}} -return this._dtmf;}});} -window.RTCPeerConnection=shimRTCPeerConnection(window,browserDetails.version);},shimReplaceTrack:function(window){if(window.RTCRtpSender&&!('replaceTrack'in window.RTCRtpSender.prototype)){window.RTCRtpSender.prototype.replaceTrack=window.RTCRtpSender.prototype.setTrack;}}};},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){'use strict';module.exports=function(window){var navigator=window&&window.navigator;var shimError_=function(e){return{name:{PermissionDeniedError:'NotAllowedError'}[e.name]||e.name,message:e.message,constraint:e.constraint,toString:function(){return this.name;}};};var origGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){return origGetUserMedia(c).catch(function(e){return Promise.reject(shimError_(e));});};};},{}],10:[function(require,module,exports){'use strict';var utils=require('../utils');module.exports={shimGetUserMedia:require('./getusermedia'),shimOnTrack:function(window){if(typeof window==='object'&&window.RTCPeerConnection&&!('ontrack'in -window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'ontrack',{get:function(){return this._ontrack;},set:function(f){if(this._ontrack){this.removeEventListener('track',this._ontrack);this.removeEventListener('addstream',this._ontrackpoly);} -this.addEventListener('track',this._ontrack=f);this.addEventListener('addstream',this._ontrackpoly=function(e){e.stream.getTracks().forEach(function(track){var event=new Event('track');event.track=track;event.receiver={track:track};event.transceiver={receiver:event.receiver};event.streams=[e.stream];this.dispatchEvent(event);}.bind(this));}.bind(this));}});} -if(typeof window==='object'&&window.RTCTrackEvent&&('receiver'in window.RTCTrackEvent.prototype)&&!('transceiver'in window.RTCTrackEvent.prototype)){Object.defineProperty(window.RTCTrackEvent.prototype,'transceiver',{get:function(){return{receiver:this.receiver};}});}},shimSourceObject:function(window){if(typeof window==='object'){if(window.HTMLMediaElement&&!('srcObject'in window.HTMLMediaElement.prototype)){Object.defineProperty(window.HTMLMediaElement.prototype,'srcObject',{get:function(){return this.mozSrcObject;},set:function(stream){this.mozSrcObject=stream;}});}}},shimPeerConnection:function(window){var browserDetails=utils.detectBrowser(window);if(typeof window!=='object'||!(window.RTCPeerConnection||window.mozRTCPeerConnection)){return;} -if(!window.RTCPeerConnection){window.RTCPeerConnection=function(pcConfig,pcConstraints){if(browserDetails.version<38){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i55&&'autoGainControl'in navigator.mediaDevices.getSupportedConstraints())){var remap=function(obj,a,b){if(a in obj&&!(b in obj)){obj[b]=obj[a];delete obj[a];}};var nativeGetUserMedia=navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);navigator.mediaDevices.getUserMedia=function(c){if(typeof c==='object'&&typeof c.audio==='object'){c=JSON.parse(JSON.stringify(c));remap(c.audio,'autoGainControl','mozAutoGainControl');remap(c.audio,'noiseSuppression','mozNoiseSuppression');} -return nativeGetUserMedia(c);};if(MediaStreamTrack&&MediaStreamTrack.prototype.getSettings){var nativeGetSettings=MediaStreamTrack.prototype.getSettings;MediaStreamTrack.prototype.getSettings=function(){var obj=nativeGetSettings.apply(this,arguments);remap(obj,'mozAutoGainControl','autoGainControl');remap(obj,'mozNoiseSuppression','noiseSuppression');return obj;};} -if(MediaStreamTrack&&MediaStreamTrack.prototype.applyConstraints){var nativeApplyConstraints=MediaStreamTrack.prototype.applyConstraints;MediaStreamTrack.prototype.applyConstraints=function(c){if(this.kind==='audio'&&typeof c==='object'){c=JSON.parse(JSON.stringify(c));remap(c,'autoGainControl','mozAutoGainControl');remap(c,'noiseSuppression','mozNoiseSuppression');} -return nativeApplyConstraints.apply(this,[c]);};}} -navigator.getUserMedia=function(constraints,onSuccess,onError){if(browserDetails.version<44){return getUserMedia_(constraints,onSuccess,onError);} -utils.deprecated('navigator.getUserMedia','navigator.mediaDevices.getUserMedia');navigator.mediaDevices.getUserMedia(constraints).then(onSuccess,onError);};};},{"../utils":13}],12:[function(require,module,exports){'use strict';var utils=require('../utils');module.exports={shimLocalStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getLocalStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getLocalStreams=function(){if(!this._localStreams){this._localStreams=[];} -return this._localStreams;};} -if(!('getStreamById'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getStreamById=function(id){var result=null;if(this._localStreams){this._localStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -if(this._remoteStreams){this._remoteStreams.forEach(function(stream){if(stream.id===id){result=stream;}});} -return result;};} -if(!('addStream'in window.RTCPeerConnection.prototype)){var _addTrack=window.RTCPeerConnection.prototype.addTrack;window.RTCPeerConnection.prototype.addStream=function(stream){if(!this._localStreams){this._localStreams=[];} -if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);} -var pc=this;stream.getTracks().forEach(function(track){_addTrack.call(pc,track,stream);});};window.RTCPeerConnection.prototype.addTrack=function(track,stream){if(stream){if(!this._localStreams){this._localStreams=[stream];}else if(this._localStreams.indexOf(stream)===-1){this._localStreams.push(stream);}} -return _addTrack.call(this,track,stream);};} -if(!('removeStream'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.removeStream=function(stream){if(!this._localStreams){this._localStreams=[];} -var index=this._localStreams.indexOf(stream);if(index===-1){return;} -this._localStreams.splice(index,1);var pc=this;var tracks=stream.getTracks();this.getSenders().forEach(function(sender){if(tracks.indexOf(sender.track)!==-1){pc.removeTrack(sender);}});};}},shimRemoteStreamsAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -if(!('getRemoteStreams'in window.RTCPeerConnection.prototype)){window.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[];};} -if(!('onaddstream'in window.RTCPeerConnection.prototype)){Object.defineProperty(window.RTCPeerConnection.prototype,'onaddstream',{get:function(){return this._onaddstream;},set:function(f){var pc=this;if(this._onaddstream){this.removeEventListener('addstream',this._onaddstream);this.removeEventListener('track',this._onaddstreampoly);} -this.addEventListener('addstream',this._onaddstream=f);this.addEventListener('track',this._onaddstreampoly=function(e){e.streams.forEach(function(stream){if(!pc._remoteStreams){pc._remoteStreams=[];} -if(pc._remoteStreams.indexOf(stream)>=0){return;} -pc._remoteStreams.push(stream);var event=new Event('addstream');event.stream=stream;pc.dispatchEvent(event);});});}});}},shimCallbacksAPI:function(window){if(typeof window!=='object'||!window.RTCPeerConnection){return;} -var prototype=window.RTCPeerConnection.prototype;var createOffer=prototype.createOffer;var createAnswer=prototype.createAnswer;var setLocalDescription=prototype.setLocalDescription;var setRemoteDescription=prototype.setRemoteDescription;var addIceCandidate=prototype.addIceCandidate;prototype.createOffer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createOffer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.createAnswer=function(successCallback,failureCallback){var options=(arguments.length>=2)?arguments[2]:arguments[0];var promise=createAnswer.apply(this,[options]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};var withCallback=function(description,successCallback,failureCallback){var promise=setLocalDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setLocalDescription=withCallback;withCallback=function(description,successCallback,failureCallback){var promise=setRemoteDescription.apply(this,[description]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.setRemoteDescription=withCallback;withCallback=function(candidate,successCallback,failureCallback){var promise=addIceCandidate.apply(this,[candidate]);if(!failureCallback){return promise;} -promise.then(successCallback,failureCallback);return Promise.resolve();};prototype.addIceCandidate=withCallback;},shimGetUserMedia:function(window){var navigator=window&&window.navigator;if(!navigator.getUserMedia){if(navigator.webkitGetUserMedia){navigator.getUserMedia=navigator.webkitGetUserMedia.bind(navigator);}else if(navigator.mediaDevices&&navigator.mediaDevices.getUserMedia){navigator.getUserMedia=function(constraints,cb,errcb){navigator.mediaDevices.getUserMedia(constraints).then(cb,errcb);}.bind(navigator);}}},shimRTCIceServerUrls:function(window){var OrigPeerConnection=window.RTCPeerConnection;window.RTCPeerConnection=function(pcConfig,pcConstraints){if(pcConfig&&pcConfig.iceServers){var newIceServers=[];for(var i=0;i=pos&&parseInt(match[pos],10);} -function wrapPeerConnectionEvent(window,eventNameToWrap,wrapper){if(!window.RTCPeerConnection){return;} -var proto=window.RTCPeerConnection.prototype;var nativeAddEventListener=proto.addEventListener;proto.addEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap){return nativeAddEventListener.apply(this,arguments);} -var wrappedCallback=function(e){cb(wrapper(e));};this._eventMap=this._eventMap||{};this._eventMap[cb]=wrappedCallback;return nativeAddEventListener.apply(this,[nativeEventName,wrappedCallback]);};var nativeRemoveEventListener=proto.removeEventListener;proto.removeEventListener=function(nativeEventName,cb){if(nativeEventName!==eventNameToWrap||!this._eventMap||!this._eventMap[cb]){return nativeRemoveEventListener.apply(this,arguments);} -var unwrappedCb=this._eventMap[cb];delete this._eventMap[cb];return nativeRemoveEventListener.apply(this,[nativeEventName,unwrappedCb]);};Object.defineProperty(proto,'on'+eventNameToWrap,{get:function(){return this['_on'+eventNameToWrap];},set:function(cb){if(this['_on'+eventNameToWrap]){this.removeEventListener(eventNameToWrap,this['_on'+eventNameToWrap]);delete this['_on'+eventNameToWrap];} -if(cb){this.addEventListener(eventNameToWrap,this['_on'+eventNameToWrap]=cb);}}});} -module.exports={extractVersion:extractVersion,wrapPeerConnectionEvent:wrapPeerConnectionEvent,disableLog:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');} -logDisabled_=bool;return(bool)?'adapter.js logging disabled':'adapter.js logging enabled';},disableWarnings:function(bool){if(typeof bool!=='boolean'){return new Error('Argument type: '+typeof bool+'. Please use a boolean.');} -deprecationWarnings_=!bool;return'adapter.js deprecation warnings '+(bool?'disabled':'enabled');},log:function(){if(typeof window==='object'){if(logDisabled_){return;} -if(typeof console!=='undefined'&&typeof console.log==='function'){console.log.apply(console,arguments);}}},deprecated:function(oldMethod,newMethod){if(!deprecationWarnings_){return;} -console.warn(oldMethod+' is deprecated, please use '+newMethod+' instead.');},detectBrowser:function(window){var navigator=window&&window.navigator;var result={};result.browser=null;result.version=null;if(typeof window==='undefined'||!window.navigator){result.browser='Not a browser.';return result;} -if(navigator.mozGetUserMedia){result.browser='firefox';result.version=extractVersion(navigator.userAgent,/Firefox\/(\d+)\./,1);}else if(navigator.webkitGetUserMedia){result.browser='chrome';result.version=extractVersion(navigator.userAgent,/Chrom(e|ium)\/(\d+)\./,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)){result.browser='edge';result.version=extractVersion(navigator.userAgent,/Edge\/(\d+).(\d+)$/,2);}else if(navigator.mediaDevices&&navigator.userAgent.match(/AppleWebKit\/(\d+)\./)){result.browser='safari';result.version=extractVersion(navigator.userAgent,/AppleWebKit\/(\d+)\./,1);}else{result.browser='Not a supported browser.';return result;} -return result;}};},{}]},{},[3])(3)}); \ No newline at end of file +(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, + localVideo: null, + screenShare: false, + useCamera: "any", + iceServers: false, + videoParams: {}, + audioParams: {}, + callbacks: { + onICEComplete: function() {}, + onICE: function() {}, + onOfferSDP: function() {} + }, + useStream: null, + }, options); + + this.audioEnabled = true; + this.videoEnabled = true; + + + this.mediaData = { + SDP: null, + profile: {}, + candidateList: [] + }; + + this.constraints = { + offerToReceiveAudio: this.options.useSpeak === "none" ? false : true, + offerToReceiveVideo: this.options.useVideo ? true : false, + }; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + } + + setCompat(); + checkCompat(); + }; + + $.FSRTC.validRes = []; + + $.FSRTC.prototype.useVideo = function(obj, local) { + var self = this; + + if (obj) { + self.options.useVideo = obj; + self.options.localVideo = local; + self.constraints.offerToReceiveVideo = true; + } else { + self.options.useVideo = null; + self.options.localVideo = null; + self.constraints.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) { + return sdp; + } else { + opusPayload = getCodecPayloadType(sdpLines[opusIndex]); + } + + // Find the payload in fmtp line. + var fmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString()); + + if (fmtpLineIndex === null) { + // create an fmtp line + sdpLines[opusIndex] = sdpLines[opusIndex] + '\r\na=fmtp:' + opusPayload.toString() + " stereo=1; sprop-stereo=1" + } else { + // Append stereo=1 to fmtp line. + sdpLines[fmtpLineIndex] = sdpLines[fmtpLineIndex].concat('; stereo=1; sprop-stereo=1'); + } + + sdp = sdpLines.join('\r\n'); + return sdp; + }; + + function setCompat() { + } + + function checkCompat() { + return true; + } + + function onStreamError(self, e) { + console.log('There has been a problem retrieving the streams - did you allow access? Check Device Resolution', e); + doCallback(self, "onError", e); + } + + function onStreamSuccess(self, stream) { + console.log("Stream Success"); + doCallback(self, "onStream", stream); + } + + function onRemoteStreamSuccess(self, stream) { + console.log("Remote Stream Success"); + doCallback(self, "onRemoteStream", stream); + } + + 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); + } + + FSRTCattachMediaStream = function(element, stream) { + if (typeof element.srcObject !== 'undefined') { + element.srcObject = stream; + } else { + console.error('Error attaching stream to element.'); + } + } + + function onRemoteStream(self, stream) { + if (self.options.useVideo) { + self.options.useVideo.style.display = 'block'; + + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + + if (iOS) { + self.options.useVideo.setAttribute("playsinline", true); + } + } + + var element = self.options.useAudio; + console.log("REMOTE STREAM", stream, element); + + FSRTCattachMediaStream(element, stream); + + + + //self.options.useAudio.play(); + self.remoteStream = stream; + onRemoteStreamSuccess(self, 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.stopPeer = function() { + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + } + + $.FSRTC.prototype.stop = function() { + var self = this; + + if (self.options.useVideo) { + self.options.useVideo.style.display = 'none'; + self.options.useVideo['src'] = ''; + } + + if (self.localStream && !self.options.useStream) { + if(typeof self.localStream.stop == 'function') { + self.localStream.stop(); + } else { + if (self.localStream.active){ + var tracks = self.localStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + self.localStream = null; + } + + if (self.options.localVideo) { + deactivateLocalVideo(self.options.localVideo); + } + + if (self.options.localVideoStream && !self.options.useStream) { + if(typeof self.options.localVideoStream.stop == 'function') { + self.options.localVideoStream.stop(); + } else { + if (self.options.localVideoStream.active){ + var tracks = self.options.localVideoStream.getTracks(); + console.log(tracks); + tracks.forEach(function(track, index){ + console.log(track); + track.stop(); + }) + } + } + } + + if (self.peer) { + console.log("stopping peer"); + self.peer.stop(); + } + }; + + $.FSRTC.prototype.getMute = function() { + var self = this; + return self.audioEnabled; + } + + $.FSRTC.prototype.setMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var audioTracks = self.localStream.getAudioTracks(); + + for (var i = 0, len = audioTracks.length; i < len; i++ ) { + switch(what) { + case "on": + audioTracks[i].enabled = true; + break; + case "off": + audioTracks[i].enabled = false; + break; + case "toggle": + audioTracks[i].enabled = !audioTracks[i].enabled; + default: + break; + } + + self.audioEnabled = audioTracks[i].enabled; + } + + return !self.audioEnabled; + } + + $.FSRTC.prototype.getVideoMute = function() { + var self = this; + return self.videoEnabled; + } + + $.FSRTC.prototype.setVideoMute = function(what) { + var self = this; + if (!self.localStream) { + return false; + } + var videoTracks = self.localStream.getVideoTracks(); + + for (var i = 0, len = videoTracks.length; i < len; i++ ) { + switch(what) { + case "on": + videoTracks[i].enabled = true; + break; + case "off": + videoTracks[i].enabled = false; + break; + case "toggle": + videoTracks[i].enabled = !videoTracks[i].enabled; + default: + break; + } + + self.videoEnabled = videoTracks[i].enabled; + } + + return !self.videoEnabled; + } + + $.FSRTC.prototype.createAnswer = function(params) { + var self = this; + self.type = "answer"; + self.remoteSDP = params.sdp; + console.debug("inbound sdp: ", params.sdp); + + function onSuccess(stream) { + self.localStream = stream; + + self.peer = FSRTCPeerConnection({ + 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, + iceServers: self.options.iceServers, + offerSDP: { + type: "offer", + sdp: self.remoteSDP + }, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useVideo && self.options.localVideo && !self.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: params.useCamera }, + }, + localVideo: self.options.localVideo, + onsuccess: function(e) {self.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else { + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + } + + }; + + function getMediaParams(obj) { + + var audio; + + if (obj.options.useMic && obj.options.useMic === "none") { + console.log("Microphone Disabled"); + audio = false; + } else if (obj.options.videoParams && obj.options.screenShare) {//obj.options.videoParams.chromeMediaSource == 'desktop') { + console.error("SCREEN SHARE", obj.options.videoParams); + audio = false; + } else { + audio = { + }; + + if (obj.options.audioParams) { + audio = obj.options.audioParams; + } + + if (obj.options.useMic !== "any") { + //audio.optional = [{sourceId: obj.options.useMic}]; + audio.deviceId = {exact: obj.options.useMic}; + } + } + + if (obj.options.useVideo && obj.options.localVideo && !obj.options.useStream) { + getUserMedia({ + constraints: { + audio: false, + video: { deviceId: obj.options.useCamera }, + }, + localVideo: obj.options.localVideo, + onsuccess: function(e) {obj.options.localVideoStream = e; console.log("local video ready");}, + onerror: function(e) {console.error("local video error!");} + }); + } + + var video = {}; + var bestFrameRate = obj.options.videoParams.vertoBestFrameRate; + var minFrameRate = obj.options.videoParams.minFrameRate || 15; + delete obj.options.videoParams.vertoBestFrameRate; + + if (obj.options.screenShare) { + if (!obj.options.useCamera && !!navigator.mozGetUserMedia) { + //This is an issue, only FireFox needs to ask this additional question if its screen or window we need a better way + var dowin = window.confirm("Do you want to share an application window? If not you can share an entire screen."); + + video = { + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight}, + mediaSource: dowin ? "window" : "screen" + } + } else { + var opt = []; + if (obj.options.useCamera) { + opt.push({sourceId: obj.options.useCamera}); + } + + if (bestFrameRate) { + opt.push({minFrameRate: bestFrameRate}); + opt.push({maxFrameRate: bestFrameRate}); + } + + video = { + mandatory: obj.options.videoParams, + optional: opt + }; + // NOTE: This is a workaround for + // https://bugs.chromium.org/p/chromium/issues/detail?id=862325 + if (!!navigator.userAgent.match(/Android/i)) { + delete video.frameRate.min; + } + } + } else { + + video = { + //mandatory: obj.options.videoParams, + width: {min: obj.options.videoParams.minWidth, max: obj.options.videoParams.maxWidth}, + height: {min: obj.options.videoParams.minHeight, max: obj.options.videoParams.maxHeight} + }; + + + + var useVideo = obj.options.useVideo; + + if (useVideo && obj.options.useCamera && obj.options.useCamera !== "none") { + //if (!video.optional) { + //video.optional = []; + //} + + + if (obj.options.useCamera !== "any") { + //video.optional.push({sourceId: obj.options.useCamera}); + video.deviceId = { + exact: obj.options.useCamera, + }; + } + + if (bestFrameRate) { + //video.optional.push({minFrameRate: bestFrameRate}); + //video.optional.push({maxFrameRate: bestFrameRate}); + video.frameRate = {ideal: bestFrameRate, min: minFrameRate, max: 30}; + } + + } else { + console.log("Camera Disabled"); + video = false; + useVideo = false; + } + } + + return {audio: audio, video: video, useVideo: useVideo}; + } + + $.FSRTC.prototype.call = function(profile) { + checkCompat(); + + var self = this; + var screen = false; + + self.type = "offer"; + + if (self.options.videoParams && self.options.screenShare) { //self.options.videoParams.chromeMediaSource == 'desktop') { + screen = true; + } + + function onSuccess(stream) { + self.localStream = stream; + + if (screen) { + self.constraints.offerToReceiveVideo = false; + self.constraints.offerToReceiveAudio = false; + self.constraints.offerToSendAudio = false; + } + + self.peer = FSRTCPeerConnection({ + type: self.type, + attachStream: self.localStream, + onICE: function(candidate) { + return onICE(self, candidate); + }, + onICEComplete: function() { + return onICEComplete(self); + }, + onRemoteStream: screen ? function(stream) {} : 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, + iceServers: self.options.iceServers, + turnServer: self.options.turnServer + }); + + onStreamSuccess(self, stream); + } + + function onError(e) { + onStreamError(self, e); + } + + var mediaParams = getMediaParams(self); + + console.log("Audio constraints", mediaParams.audio); + console.log("Video constraints", mediaParams.video); + + if (self.options.useStream) { + if (self.options.useVideo) { + self.options.localVideoStream = self.options.useStream; + if (self.options.localVideo) { + activateLocalVideo(self.options.localVideo, self.options.useStream); + } + } + onSuccess(self.options.useStream); + } + else if (mediaParams.audio || mediaParams.video) { + + getUserMedia({ + constraints: { + audio: mediaParams.audio, + video: mediaParams.video + }, + video: mediaParams.useVideo, + onsuccess: onSuccess, + onerror: onError + }); + + } else { + onSuccess(null); + } + + + + /* + navigator.getUserMedia({ + video: self.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 + + function FSRTCPeerConnection(options) { + var gathering = false, done = false; + var config = {}; + var default_ice = [{ urls: ['stun:stun.l.google.com:19302'] }]; + + if (self.options.turnServer) { + default_ice.push(self.options.turnServer) + } + + if (options.iceServers) { + if (typeof(options.iceServers) === "boolean") { + config.iceServers = default_ice; + } else { + config.iceServers = options.iceServers; + } + } + + config.bundlePolicy = "max-compat"; + + var peer = new window.RTCPeerConnection(config); + + openOffererChannel(); + var x = 0; + + function ice_handler() { + + done = true; + gathering = null; + + if (options.onICEComplete) { + options.onICEComplete(); + } + + if (options.type == "offer") { + options.onICESDP(peer.localDescription); + } else { + if (!x && options.onICESDP) { + options.onICESDP(peer.localDescription); + } + } + } + + peer.onicecandidate = function(event) { + + if (done) { + return; + } + + if (!gathering) { + gathering = setTimeout(ice_handler, 1000); + } + + if (event) { + if (event.candidate) { + options.onICE(event.candidate); + } + } else { + done = true; + + if (gathering) { + clearTimeout(gathering); + gathering = null; + } + + ice_handler(); + } + }; + + // 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.oninactive = 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 || { + // 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); + }, + onSdpError, options.constraints); + } + + // onAnswerSDP(RTCSessionDescription) + function createAnswer() { + if (options.type != "answer") return; + + //options.offerSDP.sdp = addStereo(options.offerSDP.sdp); + peer.setRemoteDescription(new window.RTCSessionDescription(options.offerSDP), onSdpSuccess, onSdpError); + peer.createAnswer(function(sessionDescription) { + sessionDescription.sdp = serializeSdp(sessionDescription.sdp); + peer.setLocalDescription(sessionDescription); + if (options.onAnswerSDP) { + options.onAnswerSDP(sessionDescription); + } + }, + onSdpError); + } + + + if ((options.onChannelMessage) || !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) { + return sdp; + } + + // DataChannel management + var channel; + + function openOffererChannel() { + if (!options.onChannelMessage) return; + + _openOffererChannel(); + + return; + } + + function _openOffererChannel() { + channel = peer.createDataChannel(options.channel || 'RTCDataChannel', { + reliable: false + }); + + 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); + }; + } + + function openAnswererChannel() { + peer.ondatachannel = function(event) { + channel = event.channel; + channel.binaryType = 'blob'; + setChannelEvents(); + }; + + return; + } + + // 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 window.RTCSessionDescription(sdp), cbSuccess ? cbSuccess : onSdpSuccess, cbError ? cbError : onSdpError); + }, + addICE: function(candidate) { + peer.addIceCandidate(new window.RTCIceCandidate({ + 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) { + if(typeof options.attachStream.stop == 'function') { + options.attachStream.stop(); + } else { + options.attachStream.active = false; + } + } + } + + }; + } + + // getUserMedia + var video_constraints = { + //mandatory: {}, + //optional: [] + }; + + function activateLocalVideo(el, stream) { + el.srcObject = stream; + el.style.display = 'block'; + } + + function deactivateLocalVideo(el) { + el.srcObject = null; + el.style.display = 'none'; + } + + function getUserMedia(options) { + var n = navigator, + media; + n.getMedia = n.getUserMedia; + n.getMedia(options.constraints || { + audio: true, + video: video_constraints + }, + streaming, options.onerror || + function(e) { + console.error(e); + }); + + function streaming(stream) { + if (options.localVideo) { + activateLocalVideo(options.localVideo, stream); + } + + if (options.onsuccess) { + options.onsuccess(stream); + } + + media = stream; + } + + return media; + } + + $.FSRTC.resSupported = function(w, h) { + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] == w && $.FSRTC.validRes[i][1] == h) { + return true; + } + } + + return false; + } + + $.FSRTC.bestResSupported = function() { + var w = 0, h = 0; + + for (var i in $.FSRTC.validRes) { + if ($.FSRTC.validRes[i][0] >= w && $.FSRTC.validRes[i][1] >= h) { + w = $.FSRTC.validRes[i][0]; + h = $.FSRTC.validRes[i][1]; + } + } + + return [w, h]; + } + + var resList = [[160, 120], [320, 180], [320, 240], [640, 360], [640, 480], [1280, 720], [1920, 1080]]; + var resI = 0; + var ttl = 0; + + var checkRes = function (cam, func) { + + if (resI >= resList.length) { + var res = { + 'validRes': $.FSRTC.validRes, + 'bestResSupported': $.FSRTC.bestResSupported() + }; + + localStorage.setItem("res_" + cam, $.toJSON(res)); + + if (func) return func(res); + return; + } + + w = resList[resI][0]; + h = resList[resI][1]; + resI++; + + var video = { + width: {exact: w}, + height: {exact: h} + }; + + if (cam !== "any") { + video.deviceId = { + exact: cam, + }; + } + + getUserMedia({ + constraints: { + audio: ttl++ == 0, + video: video + }, + onsuccess: function(e) { + e.getTracks().forEach(function(track) {track.stop();}); + console.info(w + "x" + h + " supported."); $.FSRTC.validRes.push([w, h]); checkRes(cam, func);}, + onerror: function(e) {console.warn( w + "x" + h + " not supported."); checkRes(cam, func);} + }); + } + + + $.FSRTC.getValidRes = function (cam, func) { + var used = []; + var cached = localStorage.getItem("res_" + cam); + + if (cached) { + var cache = $.parseJSON(cached); + + if (cache) { + $.FSRTC.validRes = cache.validRes; + console.log("CACHED RES FOR CAM " + cam, cache); + } else { + console.error("INVALID CACHE"); + } + return func ? func(cache) : null; + } + + + $.FSRTC.validRes = []; + resI = 0; + + checkRes(cam, func); + } + + $.FSRTC.checkPerms = function (runtime, check_audio, check_video) { + getUserMedia({ + constraints: { + audio: check_audio, + video: check_video, + }, + onsuccess: function(e) { + + e.getTracks().forEach(function(track) {track.stop();}); + + console.info("media perm init complete"); + if (runtime) { + setTimeout(runtime, 100, true); + } + }, + onerror: function(e) { + if (check_video && check_audio) { + console.error("error, retesting with audio params only"); + return $.FSRTC.checkPerms(runtime, check_audio, false); + } + + console.error("media perm init error"); + + if (runtime) { + runtime(false) + } + } + }); + } + +})(jQuery); +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * + * 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, + loginParams : null, + userVariables : null, + getSocket : function(onmessage_cb) { return self._getSocket(onmessage_cb); } + }, options); + + self.ws_cnt = 0; + + // 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 : { success_cb: cb, error_cb: cb } + $.JsonRpcClient.prototype._ws_callbacks = {}; + + /// The next JSON-RPC request id. + $.JsonRpcClient.prototype._current_id = 1; + + + $.JsonRpcClient.prototype.speedTest = function (bytes, cb) { + var socket = this.options.getSocket(this.wsOnMessage); + if (socket !== null) { + this.speedCB = cb; + this.speedBytes = bytes; + socket.send("#SPU " + bytes); + + var loops = bytes / 1024; + var rem = bytes % 1024; + var i; + var data = new Array(1024).join("."); + for (i = 0; i < loops; i++) { + socket.send("#SPB " + data); + } + + if (rem) { + socket.send("#SPB " + data); + } + + socket.send("#SPE"); + } + }; + + + + /** + * @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() { + var self = this; + if (self.socketReady()) { + self._ws_socket.onclose = function (w) {console.log("Closing Socket");}; + self._ws_socket.close(); + } + }; + + $.JsonRpcClient.prototype.loginData = function(params) { + var self = this; + self.options.login = params.login; + self.options.passwd = params.passwd; + self.options.loginParams = params.loginParams; + self.options.userVariables = params.userVariables; + }; + + $.JsonRpcClient.prototype.connectSocket = function(onmessage_cb) { + var self = this; + + if (self.to) { + clearTimeout(self.to); + } + + if (!self.socketReady()) { + self.authing = false; + + if (self._ws_socket) { + delete self._ws_socket; + } + + // No socket, or dying socket, let's get a new one. + self._ws_socket = new WebSocket(self.options.socketUrl); + + if (self._ws_socket) { + // Set up onmessage handler. + self._ws_socket.onmessage = onmessage_cb; + self._ws_socket.onclose = function (w) { + if (!self.ws_sleep) { + self.ws_sleep = 1000; + } + + if (self.options.onWSClose) { + self.options.onWSClose(self); + } + + if (self.ws_cnt > 10 && self.options.wsFallbackURL) { + self.options.socketUrl = self.options.wsFallbackURL; + } + + console.error("Websocket Lost " + self.ws_cnt + " sleep: " + self.ws_sleep + "msec"); + + self.to = setTimeout(function() { + console.log("Attempting Reconnection...."); + self.connectSocket(onmessage_cb); + }, self.ws_sleep); + + self.ws_cnt++; + + if (self.ws_sleep < 3000 && (self.ws_cnt % 10) === 0) { + self.ws_sleep += 1000; + } + }; + + // Set up sending of message for when the socket is open. + self._ws_socket.onopen = function() { + if (self.to) { + clearTimeout(self.to); + } + self.ws_sleep = 1000; + self.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 self._ws_socket ? true : false; + }; + + $.JsonRpcClient.prototype.stopRetrying = function() { + if (self.to) + clearTimeout(self.to); + } + + $.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; + + // Special sub proto + if (event.data[0] == "#" && event.data[1] == "S" && event.data[2] == "P") { + if (event.data[3] == "U") { + this.up_dur = parseInt(event.data.substring(4)); + } else if (this.speedCB && event.data[3] == "D") { + this.down_dur = parseInt(event.data.substring(4)); + + var up_kps = (((this.speedBytes * 8) / (this.up_dur / 1000)) / 1024).toFixed(0); + var down_kps = (((this.speedBytes * 8) / (this.down_dur / 1000)) / 1024).toFixed(0); + + console.info("Speed Test: Up: " + up_kps + " Down: " + down_kps); + var cb = this.speedCB; + this.speedCB = null; + cb(event, { + upDur: this.up_dur, + downDur: this.down_dur, + upKPS: up_kps, + downKPS: down_kps + }); + } + + return; + } + + + 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; + } else if ('error' in response && this._ws_callbacks[response.id]) { + // If this is an object with error, it is an error response. + + // 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, loginParams: self.options.loginParams, + userVariables: self.options.userVariables}, + 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 = {}; + var i = 0; + var call; + var success_cb; + var error_cb; + + // 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 (i = 0; i < this._requests.length; i++) { + call = this._requests[i]; + success_cb = ('success_cb' in call) ? call.success_cb : undefined; + 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 (i = 0; i < this._requests.length; i++) { + 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 + }; + } + } + + 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); + +/* + * Verto HTML5/Javascript Telephony Signaling and Control Protocol Stack for FreeSWITCH + * Copyright (C) 2005-2014, Anthony Minessale II + * + * 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 + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * jquery.verto.js - Main interface + * + */ + +(function($) { + var sources = []; + + var generateGUID = (typeof(window.crypto) !== 'undefined' && typeof(window.crypto.getRandomValues) !== 'undefined') ? + function() { + // If we have a cryptographically secure PRNG, use that + // http://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript + 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() { + // Otherwise, just use Math.random + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 + 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); + }); + }; + + /// MASTER OBJ + $.verto = function(options, callbacks) { + var verto = this; + + $.verto.saved.push(verto); + + verto.options = $.extend({ + login: null, + passwd: null, + socketUrl: null, + tag: null, + localTag: null, + videoParams: {}, + audioParams: {}, + loginParams: {}, + deviceParams: {onResCheck: null}, + userVariables: {}, + iceServers: false, + ringSleep: 6000, + sessid: null, + useStream: null + }, options); + + if (verto.options.deviceParams.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, verto.options.deviceParams.onResCheck); + } + + if (!verto.options.deviceParams.useMic) { + verto.options.deviceParams.useMic = "any"; + } + + if (!verto.options.deviceParams.useSpeak) { + verto.options.deviceParams.useSpeak = "any"; + } + + if (verto.options.sessid) { + verto.sessid = verto.options.sessid; + } else { + verto.sessid = localStorage.getItem("verto_session_uuid") || generateGUID(); + localStorage.setItem("verto_session_uuid", verto.sessid); + } + + verto.dialogs = {}; + verto.callbacks = callbacks || {}; + verto.eventSUBS = {}; + + verto.rpcClient = new $.JsonRpcClient({ + login: verto.options.login, + passwd: verto.options.passwd, + socketUrl: verto.options.socketUrl, + wsFallbackURL: verto.options.wsFallbackURL, + turnServer: verto.options.turnServer, + loginParams: verto.options.loginParams, + userVariables: verto.options.userVariables, + 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(); + } + }); + + var tag = verto.options.tag; + if (typeof(tag) === "function") { + tag = tag(); + } + + if (verto.options.ringFile && verto.options.tag) { + verto.ringer = $("#" + tag); + } + + verto.rpcClient.call('login', {}); + + }; + + + $.verto.prototype.deviceParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.deviceParams[i] = obj[i]; + } + + if (obj.useCamera) { + $.FSRTC.getValidRes(verto.options.deviceParams.useCamera, obj ? obj.onResCheck : undefined); + } + }; + + $.verto.prototype.videoParams = function(obj) { + var verto = this; + + for (var i in obj) { + verto.options.videoParams[i] = obj[i]; + } + }; + + $.verto.prototype.iceServers = function(obj) { + var verto = this; + verto.options.iceServers = obj; + }; + + $.verto.prototype.loginData = function(params) { + var verto = this; + 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(); + if (verto.callbacks.onWSClose) { + verto.callbacks.onWSClose(verto, false); + } + 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": + //console.error(e); + break; + } + }; + + $.verto.prototype.sendMethod = function(method, params) { + var verto = this; + + verto.rpcClient.call(method, params, + + function(e) { + /* Success */ + verto.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + if (args["useCamera"]) { + verto.options.deviceParams["useCamera"] = args["useCamera"]; + } + + var dialog = new $.verto.dialog($.verto.enum.direction.outbound, this, args); + + if (callbacks) { + dialog.callbacks = callbacks; + } + + dialog.invite(); + + return dialog; + }; + + $.verto.prototype.handleMessage = function(data) { + var verto = this; + + if (!(data && data.method)) { + console.error("Invalid Data", data); + return; + } + + if (data.params.callID) { + var dialog = verto.dialogs[data.params.callID]; + + if (data.method === "verto.attach" && dialog) { + delete dialog.verto.dialogs[dialog.callID]; + dialog.rtc.stop(); + dialog = null; + } + + if (dialog) { + + switch (data.method) { + case 'verto.bye': + dialog.hangup(data.params); + break; + case 'verto.answer': + dialog.handleAnswer(data.params); + break; + case 'verto.media': + dialog.handleMedia(data.params); + 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", dialog, 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); + dialog.setState($.verto.enum.state.recovering); + + 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.punt': + verto.purge(); + verto.logout(); + break; + 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 && key === verto.sessid) { + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.pvtEvent, data.params); + } + } else 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; + + case 'verto.clientReady': + if (verto.callbacks.onMessage) { + verto.callbacks.onMessage(verto, null, $.verto.enum.message.clientReady, data.params); + } + console.debug("CLIENT READY", data.params); + 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 ? "
" : "\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; + + // Inherit methods of hashArray + hashArray.call(la); + + // Save the hashArray add, del, reorder, clear methods so we can make our own. + 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)) { + + //la.clear(); + 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 + }); + } + } + }; + + // @param serno La is the serial number for la particular request. + // @param key If looking at it as a hash table, la represents the key in the hashArray object where you want to store the val object. + // @param index If looking at it as an array, la represents the position in the array where you want to store the val object. + // @param val La is the object you want to store at the key or index location in the hash table / array. + 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; + + //console.error("READ:", packet); + + if (packet.name != la.name) { + return; + } + + 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.sendCommand = function(cmd, obj) { + var self = la; + self.broadcast(self.context, { + liveArray: { + command: cmd, + context: self.context, + name: self.name, + obj: obj + } + }); + }; + + la.bootstrap = function(obj) { + var self = la; + la.sendCommand("bootstrap", obj); + //self.heartbeat(); + }; + + 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); + }; + + /* back compat so jsonstatus can always be enabled */ + function genRow(data) { + if (typeof(data[4]) === "string" && data[4].indexOf("{") > -1) { + var tmp = $.parseJSON(data[4]); + data[4] = tmp.oldStatus; + data[5] = null; + } + return data; + } + + function genArray(obj) { + var data = obj.asArray(); + + for (var i in data) { + data[i] = genRow(data[i]); + } + + return data; + } + + + 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; + } + } + + if (config.onChange) { + config.onChange(obj, args); + } + + try { + switch (args.action) { + case "bootObj": + if (!args.data) { + console.error("missing data"); + return; + } + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + dt.fnAdjustColumnSizing(); + break; + case "add": + if (!args.data) { + console.error("missing data"); + return; + } + if (args.redraw > -1) { + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + } else { + dt.fnAddData(genRow(args.data)); + } + dt.fnAdjustColumnSizing(); + break; + case "modify": + if (!args.data) { + return; + } + //console.debug(args, index); + dt.fnUpdate(genRow(args.data), index); + dt.fnAdjustColumnSizing(); + break; + case "del": + dt.fnDeleteRow(index); + dt.fnAdjustColumnSizing(); + break; + case "clear": + dt.fnClearTable(); + break; + case "reorder": + // specific position, more costly + dt.fnClearTable(); + dt.fnAddData(genArray(obj)); + 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; + } + + }; + + la.onChange(la, { + action: "init" + }); + + }; + + var CONFMAN_SERNO = 1; + + /* + Conference Manager without jQuery table. + */ + + $.verto.conf = function(verto, params) { + var conf = this; + + conf.params = $.extend({ + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + conf.verto = verto; + conf.serno = CONFMAN_SERNO++; + + createMainModeratorMethods(); + + verto.subscribe(conf.params.laData.modChannel, { + handler: function(v, e) { + if (conf.params.onBroadcast) { + conf.params.onBroadcast(verto, conf, e.data); + } + } + }); + + verto.subscribe(conf.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(conf.params.infoCallback) === "function") { + conf.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(conf.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(conf.params.chatCallback) === "function") { + conf.params.chatCallback(v,e); + } + } + }); + }; + + $.verto.conf.prototype.modCommand = function(cmd, id, value) { + var conf = this; + + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.conf.prototype.destroy = function() { + var conf = this; + + conf.destroyed = true; + conf.params.onBroadcast(conf.verto, conf, 'destroy'); + + if (conf.params.laData.modChannel) { + conf.verto.unsubscribe(conf.params.laData.modChannel); + } + + if (conf.params.laData.chatChannel) { + conf.verto.unsubscribe(conf.params.laData.chatChannel); + } + + if (conf.params.laData.infoChannel) { + conf.verto.unsubscribe(conf.params.laData.infoChannel); + } + }; + + function createMainModeratorMethods() { + $.verto.conf.prototype.listVideoLayouts = function() { + this.modCommand("list-videoLayouts", null, null); + }; + + $.verto.conf.prototype.play = function(file) { + this.modCommand("play", null, file); + }; + + $.verto.conf.prototype.stop = function() { + this.modCommand("stop", null, "all"); + }; + + $.verto.conf.prototype.deaf = function(memberID) { + this.modCommand("deaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.undeaf = function(memberID) { + this.modCommand("undeaf", parseInt(memberID)); + }; + + $.verto.conf.prototype.record = function(file) { + this.modCommand("recording", null, ["start", file]); + }; + + $.verto.conf.prototype.stopRecord = function() { + this.modCommand("recording", null, ["stop", "all"]); + }; + + $.verto.conf.prototype.snapshot = function(file) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-write-png", null, file); + }; + + $.verto.conf.prototype.setVideoLayout = function(layout, canvasID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + if (canvasID) { + this.modCommand("vid-layout", null, [layout, canvasID]); + } else { + this.modCommand("vid-layout", null, layout); + } + }; + + $.verto.conf.prototype.kick = function(memberID) { + this.modCommand("kick", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteMic = function(memberID) { + this.modCommand("tmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.muteVideo = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("tvmute", parseInt(memberID)); + }; + + $.verto.conf.prototype.presenter = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-res-id", parseInt(memberID), "presenter"); + }; + + $.verto.conf.prototype.videoFloor = function(memberID) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-floor", parseInt(memberID), "force"); + }; + + $.verto.conf.prototype.banner = function(memberID, text) { + if (!this.params.hasVid) { + throw 'Conference has no video'; + } + this.modCommand("vid-banner", parseInt(memberID), escape(text)); + }; + + $.verto.conf.prototype.volumeDown = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.volumeUp = function(memberID) { + this.modCommand("volume_out", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.gainDown = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "down"); + }; + + $.verto.conf.prototype.gainUp = function(memberID) { + this.modCommand("volume_in", parseInt(memberID), "up"); + }; + + $.verto.conf.prototype.transfer = function(memberID, exten) { + this.modCommand("transfer", parseInt(memberID), exten); + }; + + $.verto.conf.prototype.sendChat = function(message, type) { + var conf = this; + conf.verto.rpcClient.call("verto.broadcast", { + "eventChannel": conf.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + } + + $.verto.modfuncs = {}; + + $.verto.confMan = function(verto, params) { + var confMan = this; + + confMan.params = $.extend({ + tableID: null, + statusID: null, + mainModID: null, + dialog: null, + hasVid: false, + laData: null, + onBroadcast: null, + onLaChange: null, + onLaRow: null + }, params); + + confMan.verto = verto; + confMan.serno = CONFMAN_SERNO++; + confMan.canvasCount = confMan.params.laData.canvasCount; + + function genMainMod(jq) { + var play_id = "play_" + confMan.serno; + var stop_id = "stop_" + confMan.serno; + var recording_id = "recording_" + confMan.serno; + var snapshot_id = "snapshot_" + confMan.serno; + var rec_stop_id = "recording_stop" + confMan.serno; + var div_id = "confman_" + confMan.serno; + + var html = "

" + + "" + + "" + + "" + + "" + + (confMan.params.hasVid ? "" : "") + + "

"; + + jq.html(html); + + $.verto.modfuncs.change_video_layout = function(id, canvas_id) { + var val = $("#" + id + " option:selected").text(); + if (val !== "none") { + confMan.modCommand("vid-layout", null, [val, canvas_id]); + } + }; + + if (confMan.params.hasVid) { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlayout_id = "confman_vid_layout_" + j + "_" + confMan.serno; + var vlselect_id = "confman_vl_select_" + j + "_" + confMan.serno; + + + var vlhtml = "

" + + "Video Layout Canvas " + (j+1) + + " " + + "

"; + jq.append(vlhtml); + } + + $("#" + snapshot_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("vid-write-png", null, file); + } + }); + } + + $("#" + play_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("play", null, file); + } + }); + + $("#" + stop_id).click(function() { + confMan.modCommand("stop", null, "all"); + }); + + $("#" + recording_id).click(function() { + var file = prompt("Please enter file name", ""); + if (file) { + confMan.modCommand("recording", null, ["start", file]); + } + }); + + $("#" + rec_stop_id).click(function() { + confMan.modCommand("recording", null, ["stop", "all"]); + }); + + } + + function genControls(jq, rowid) { + var x = parseInt(rowid); + var kick_id = "kick_" + x; + var canvas_in_next_id = "canvas_in_next_" + x; + var canvas_in_prev_id = "canvas_in_prev_" + x; + var canvas_out_next_id = "canvas_out_next_" + x; + var canvas_out_prev_id = "canvas_out_prev_" + x; + + var canvas_in_set_id = "canvas_in_set_" + x; + var canvas_out_set_id = "canvas_out_set_" + x; + + var layer_set_id = "layer_set_" + x; + var layer_next_id = "layer_next_" + x; + var layer_prev_id = "layer_prev_" + x; + + var tmute_id = "tmute_" + x; + var tvmute_id = "tvmute_" + x; + var vbanner_id = "vbanner_" + x; + var tvpresenter_id = "tvpresenter_" + x; + var tvfloor_id = "tvfloor_" + x; + var box_id = "box_" + x; + var gainup_id = "gain_in_up" + x; + var gaindn_id = "gain_in_dn" + x; + var volup_id = "vol_in_up" + x; + var voldn_id = "vol_in_dn" + x; + var transfer_id = "transfer" + x; + + + var html = "
"; + + html += "General Controls
"; + + html += "" + + "" + + "" + + "" + + "" + + "" + + ""; + + if (confMan.params.hasVid) { + html += "

Video Controls
"; + + + html += "" + + "" + + "" + + ""; + + if (confMan.canvasCount > 1) { + html += "

Canvas Controls
" + + "" + + "" + + "" + + + "
" + + + "" + + "" + + ""; + } + + html += "
" + + + "" + + "" + + "" + + + + + "
"; + } + + jq.html(html); + + + if (!jq.data("mouse")) { + $("#" + box_id).hide(); + } + + jq.mouseover(function(e) { + jq.data({"mouse": true}); + $("#" + box_id).show(); + }); + + jq.mouseout(function(e) { + jq.data({"mouse": false}); + $("#" + box_id).hide(); + }); + + $("#" + transfer_id).click(function() { + var xten = prompt("Enter Extension"); + if (xten) { + confMan.modCommand("transfer", x, xten); + } + }); + + $("#" + kick_id).click(function() { + confMan.modCommand("kick", x); + }); + + + $("#" + layer_set_id).click(function() { + var cid = prompt("Please enter layer ID", ""); + if (cid) { + confMan.modCommand("vid-layer", x, cid); + } + }); + + $("#" + layer_next_id).click(function() { + confMan.modCommand("vid-layer", x, "next"); + }); + $("#" + layer_prev_id).click(function() { + confMan.modCommand("vid-layer", x, "prev"); + }); + + $("#" + canvas_in_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-canvas", x, cid); + } + }); + + $("#" + canvas_out_set_id).click(function() { + var cid = prompt("Please enter canvas ID", ""); + if (cid) { + confMan.modCommand("vid-watching-canvas", x, cid); + } + }); + + $("#" + canvas_in_next_id).click(function() { + confMan.modCommand("vid-canvas", x, "next"); + }); + $("#" + canvas_in_prev_id).click(function() { + confMan.modCommand("vid-canvas", x, "prev"); + }); + + + $("#" + canvas_out_next_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "next"); + }); + $("#" + canvas_out_prev_id).click(function() { + confMan.modCommand("vid-watching-canvas", x, "prev"); + }); + + $("#" + tmute_id).click(function() { + confMan.modCommand("tmute", x); + }); + + if (confMan.params.hasVid) { + $("#" + tvmute_id).click(function() { + confMan.modCommand("tvmute", x); + }); + $("#" + tvpresenter_id).click(function() { + confMan.modCommand("vid-res-id", x, "presenter"); + }); + $("#" + tvfloor_id).click(function() { + confMan.modCommand("vid-floor", x, "force"); + }); + $("#" + vbanner_id).click(function() { + var text = prompt("Please enter text", ""); + if (text) { + confMan.modCommand("vid-banner", x, escape(text)); + } + }); + } + + $("#" + gainup_id).click(function() { + confMan.modCommand("volume_in", x, "up"); + }); + + $("#" + gaindn_id).click(function() { + confMan.modCommand("volume_in", x, "down"); + }); + + $("#" + volup_id).click(function() { + confMan.modCommand("volume_out", x, "up"); + }); + + $("#" + voldn_id).click(function() { + confMan.modCommand("volume_out", x, "down"); + }); + + return html; + } + + var atitle = ""; + var awidth = 0; + + //$(".jsDataTable").width(confMan.params.hasVid ? "900px" : "800px"); + + verto.subscribe(confMan.params.laData.infoChannel, { + handler: function(v, e) { + if (typeof(confMan.params.infoCallback) === "function") { + confMan.params.infoCallback(v,e); + } + } + }); + + verto.subscribe(confMan.params.laData.chatChannel, { + handler: function(v, e) { + if (typeof(confMan.params.chatCallback) === "function") { + confMan.params.chatCallback(v,e); + } + } + }); + + if (confMan.params.laData.role === "moderator") { + atitle = "Action"; + awidth = 600; + + if (confMan.params.mainModID) { + genMainMod($(confMan.params.mainModID)); + $(confMan.params.displayID).html("Moderator Controls Ready

"); + } else { + $(confMan.params.mainModID).html(""); + } + + verto.subscribe(confMan.params.laData.modChannel, { + handler: function(v, e) { + //console.error("MODDATA:", e.data); + if (confMan.params.onBroadcast) { + confMan.params.onBroadcast(verto, confMan, e.data); + } + + if (e.data["conf-command"] === "list-videoLayouts") { + for (var j = 0; j < confMan.canvasCount; j++) { + var vlselect_id = "#confman_vl_select_" + j + "_" + confMan.serno; + var vlayout_id = "#confman_vid_layout_" + j + "_" + confMan.serno; + + var x = 0; + var options; + + $(vlselect_id).selectmenu({}); + $(vlselect_id).selectmenu("enable"); + $(vlselect_id).empty(); + + $(vlselect_id).append(new Option("Choose a Layout", "none")); + + if (e.data.responseData) { + var rdata = []; + + for (var i in e.data.responseData) { + rdata.push(e.data.responseData[i].name); + } + + options = rdata.sort(function(a, b) { + var ga = a.substring(0, 6) == "group:" ? true : false; + var gb = b.substring(0, 6) == "group:" ? true : false; + + if ((ga || gb) && ga != gb) { + return ga ? -1 : 1; + } + + return ( ( a == b ) ? 0 : ( ( a > b ) ? 1 : -1 ) ); + }); + + for (var i in options) { + $(vlselect_id).append(new Option(options[i], options[i])); + x++; + } + } + + if (x) { + $(vlselect_id).selectmenu('refresh', true); + } else { + $(vlayout_id).hide(); + } + } + } else { + + if (!confMan.destroyed && confMan.params.displayID) { + $(confMan.params.displayID).html(e.data.response + "

"); + if (confMan.lastTimeout) { + clearTimeout(confMan.lastTimeout); + confMan.lastTimeout = 0; + } + confMan.lastTimeout = setTimeout(function() { $(confMan.params.displayID).html(confMan.destroyed ? "" : "Moderator Controls Ready

");}, 4000); + } + } + } + }); + + + if (confMan.params.hasVid) { + confMan.modCommand("list-videoLayouts", null, null); + } + } + + var row_callback = null; + + if (confMan.params.laData.role === "moderator") { + row_callback = function(nRow, aData, iDisplayIndex, iDisplayIndexFull) { + if (!aData[5]) { + var $row = $('td:eq(5)', nRow); + genControls($row, aData); + + if (confMan.params.onLaRow) { + confMan.params.onLaRow(verto, confMan, $row, aData); + } + } + }; + } + + confMan.lt = new $.verto.liveTable(verto, confMan.params.laData.laChannel, confMan.params.laData.laName, $(confMan.params.tableID), { + subParams: { + callID: confMan.params.dialog ? confMan.params.dialog.callID : null + }, + + "onChange": function(obj, args) { + $(confMan.params.statusID).text("Conference Members: " + " (" + obj.arrayLen() + " Total)"); + if (confMan.params.onLaChange) { + confMan.params.onLaChange(verto, confMan, $.verto.enum.confEvent.laChange, obj, args); + } + }, + + "aaData": [], + "aoColumns": [ + { + "sTitle": "ID", + "sWidth": "50" + }, + { + "sTitle": "Number", + "sWidth": "250" + }, + { + "sTitle": "Name", + "sWidth": "250" + }, + { + "sTitle": "Codec", + "sWidth": "100" + }, + { + "sTitle": "Status", + "sWidth": confMan.params.hasVid ? "200px" : "150px" + }, + { + "sTitle": atitle, + "sWidth": awidth, + } + ], + "bAutoWidth": true, + "bDestroy": true, + "bSort": false, + "bInfo": false, + "bFilter": false, + "bLengthChange": false, + "bPaginate": false, + "iDisplayLength": 1400, + + "oLanguage": { + "sEmptyTable": "The Conference is Empty....." + }, + + "fnRowCallback": row_callback + + }); + }; + + $.verto.confMan.prototype.modCommand = function(cmd, id, value) { + var confMan = this; + + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.modChannel, + "data": { + "application": "conf-control", + "command": cmd, + "id": id, + "value": value + } + }); + }; + + $.verto.confMan.prototype.sendChat = function(message, type) { + var confMan = this; + confMan.verto.rpcClient.call("verto.broadcast", { + "eventChannel": confMan.params.laData.chatChannel, + "data": { + "action": "send", + "message": message, + "type": type + } + }); + }; + + + $.verto.confMan.prototype.destroy = function() { + var confMan = this; + + confMan.destroyed = true; + + if (confMan.lt) { + confMan.lt.destroy(); + } + + if (confMan.params.laData.chatChannel) { + confMan.verto.unsubscribe(confMan.params.laData.chatChannel); + } + + if (confMan.params.laData.modChannel) { + confMan.verto.unsubscribe(confMan.params.laData.modChannel); + } + + if (confMan.params.mainModID) { + $(confMan.params.mainModID).html(""); + } + }; + + $.verto.dialog = function(direction, verto, params) { + var dialog = this; + + dialog.params = $.extend({ + useVideo: verto.options.useVideo, + useStereo: verto.options.useStereo, + screenShare: false, + useCamera: false, + useMic: verto.options.deviceParams.useMic, + useSpeak: verto.options.deviceParams.useSpeak, + tag: verto.options.tag, + localTag: verto.options.localTag, + login: verto.options.login, + videoParams: verto.options.videoParams, + useStream: verto.options.useStream, + }, params); + + + if (!dialog.params.screenShare) { + dialog.params.useCamera = verto.options.deviceParams.useCamera; + } + + 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; + dialog.screenShare = params.screenShare || false; + dialog.useCamera = dialog.params.useCamera; + dialog.useMic = dialog.params.useMic; + dialog.useSpeak = dialog.params.useSpeak; + + if (dialog.params.callID) { + dialog.callID = dialog.params.callID; + } else { + dialog.callID = dialog.params.callID = generateGUID(); + } + + if (typeof(dialog.params.tag) === "function") { + dialog.params.tag = dialog.params.tag(); + } + + if (dialog.params.tag) { + dialog.audioStream = document.getElementById(dialog.params.tag); + + if (dialog.params.useVideo) { + dialog.videoStream = dialog.audioStream; + } + } //else conjure one TBD + + if (dialog.params.localTag) { + dialog.localVideo = document.getElementById(dialog.params.localTag); + } + + dialog.verto.dialogs[dialog.callID] = dialog; + + var RTCcallbacks = {}; + + if (dialog.direction == $.verto.enum.direction.inbound) { + if (dialog.params.display_direction === "outbound") { + dialog.params.remote_caller_id_name = dialog.params.caller_id_name; + dialog.params.remote_caller_id_number = dialog.params.caller_id_number; + } else { + dialog.params.remote_caller_id_name = dialog.params.callee_id_name; + dialog.params.remote_caller_id_number = dialog.params.callee_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) { + console.log("RECV " + rtc.type + " SDP", rtc.mediaData.SDP); + + if (dialog.state == $.verto.enum.state.requesting || dialog.state == $.verto.enum.state.answering || dialog.state == $.verto.enum.state.active) { + location.reload(); + return; + } + + if (rtc.type == "offer") { + if (dialog.state == $.verto.enum.state.active) { + dialog.setState($.verto.enum.state.requesting); + dialog.sendMethod("verto.attach", { + sdp: rtc.mediaData.SDP + }); + } else { + dialog.setState($.verto.enum.state.requesting); + + dialog.sendMethod("verto.invite", { + sdp: rtc.mediaData.SDP + }); + } + } else { //answer + dialog.setState($.verto.enum.state.answering); + + dialog.sendMethod(dialog.attach ? "verto.attach" : "verto.answer", { + sdp: dialog.rtc.mediaData.SDP + }); + } + }; + + RTCcallbacks.onICE = function(rtc) { + //console.log("cand", rtc.mediaData.candidate); + if (rtc.type == "offer") { + console.log("offer", rtc.mediaData.candidate); + return; + } + }; + + RTCcallbacks.onStream = function(rtc, stream) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onGranted === 'function') { + dialog.callbacks.permissionCallback.onGranted(stream); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onGranted === 'function'){ + dialog.verto.options.permissionCallback.onGranted(stream); + } + console.log("stream started"); + }; + + RTCcallbacks.onRemoteStream = function(rtc, stream) { + if (typeof dialog.callbacks.onRemoteStream === 'function') { + dialog.callbacks.onRemoteStream(stream, dialog); + } + console.log("remote stream started"); + }; + + RTCcallbacks.onError = function(e) { + if (dialog.callbacks.permissionCallback && + typeof dialog.callbacks.permissionCallback.onDenied === 'function') { + dialog.callbacks.permissionCallback.onDenied(); + } + else if (dialog.verto.options.permissionCallback && + typeof dialog.verto.options.permissionCallback.onDenied === 'function'){ + dialog.verto.options.permissionCallback.onDenied(); + } + console.error("ERROR:", e); + dialog.hangup({cause: "Device or Permission Error"}); + }; + + dialog.rtc = new $.FSRTC({ + callbacks: RTCcallbacks, + localVideo: dialog.screenShare ? null : dialog.localVideo, + useVideo: dialog.params.useVideo ? dialog.videoStream : null, + useAudio: dialog.audioStream, + useStereo: dialog.params.useStereo, + videoParams: dialog.params.videoParams, + audioParams: verto.options.audioParams, + iceServers: verto.options.iceServers, + screenShare: dialog.screenShare, + useCamera: dialog.useCamera, + useMic: dialog.useMic, + useSpeak: dialog.useSpeak, + turnServer: verto.options.turnServer, + useStream: dialog.params.useStream + }); + + 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; + } + + if ((obj.noDialogParams && i != "callID")) { + continue; + } + + obj.dialogParams[i] = dialog.params[i]; + } + + delete obj.noDialogParams; + + dialog.verto.rpcClient.call(method, obj, + + function(e) { + /* Success */ + dialog.processReply(method, true, e); + }, + + function(e) { + /* Error */ + 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; + } + + + // Attach audio output device to video element using device/sink ID. + function find_name(id) { + for (var i in $.verto.audioOutDevices) { + var source = $.verto.audioOutDevices[i]; + if (source.id === id) { + return(source.label); + } + } + + return id; + } + + $.verto.dialog.prototype.setAudioPlaybackDevice = function(sinkId, callback, arg) { + var dialog = this; + var element = dialog.audioStream; + + if (typeof element.sinkId !== 'undefined') { + var devname = find_name(sinkId); + console.info("Dialog: " + dialog.callID + " Setting speaker:", element, devname); + + element.setSinkId(sinkId) + .then(function() { + console.log("Dialog: " + dialog.callID + ' Success, audio output device attached: ' + sinkId); + if (callback) { + callback(true, devname, arg); + } + }) + .catch(function(error) { + var errorMessage = error; + if (error.name === 'SecurityError') { + errorMessage = "Dialog: " + dialog.callID + ' You need to use HTTPS for selecting audio output ' + + 'device: ' + error; + } + if (callback) { + callback(false, null, arg); + } + console.error(errorMessage); + }); + } else { + console.warn("Dialog: " + dialog.callID + ' Browser does not support output device selection.'); + if (callback) { + callback(false, null, arg); + } + } + } + + $.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.log("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.early: + case $.verto.enum.state.active: + + var speaker = dialog.useSpeak; + console.info("Using Speaker: ", speaker); + + if (speaker && speaker !== "any" && speaker !== "none") { + setTimeout(function() { + dialog.setAudioPlaybackDevice(speaker); + }, 500); + } + + break; + + case $.verto.enum.state.trying: + setTimeout(function() { + if (dialog.state == $.verto.enum.state.trying) { + dialog.setState($.verto.enum.state.hangup); + } + }, 30000); + break; + 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: + + if (typeof(dialog.verto.options.tag) === "function") { + $('#' + dialog.params.tag).remove(); + } + + delete dialog.verto.dialogs[dialog.callID]; + if (dialog.params.screenShare) { + dialog.rtc.stopPeer(); + } else { + 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(params) { + var dialog = this; + + if (params) { + if (params.causeCode) { + dialog.causeCode = params.causeCode; + } + + if (params.cause) { + dialog.cause = params.cause; + } + } + + if (!dialog.cause && !dialog.causeCode) { + dialog.cause = "NORMAL_CLEARING"; + } + + 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, dialog.localVideo); + + }; + + $.verto.dialog.prototype.setMute = function(what) { + var dialog = this; + return dialog.rtc.setMute(what); + }; + + $.verto.dialog.prototype.getMute = function() { + var dialog = this; + return dialog.rtc.getMute(); + }; + + $.verto.dialog.prototype.setVideoMute = function(what) { + var dialog = this; + return dialog.rtc.setVideoMute(what); + }; + + $.verto.dialog.prototype.getVideoMute = function() { + var dialog = this; + return dialog.rtc.getVideoMute(); + }; + + $.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.rtt = function(obj) { + var dialog = this; + var pobj = {}; + + if (!obj) { + return false; + } + + pobj.code = obj.code; + pobj.chars = obj.chars; + + + if (pobj.chars || pobj.code) { + dialog.sendMethod("verto.info", { + txt: obj, + noDialogParams: true + }); + } + }; + + $.verto.dialog.prototype.transfer = function(dest, params) { + var dialog = this; + if (dest) { + dialog.sendMethod("verto.modify", { + action: "transfer", + destination: dest, + params: params + }); + } + }; + + $.verto.dialog.prototype.replace = function(replaceCallID, params) { + var dialog = this; + if (replaceCallID) { + dialog.sendMethod("verto.modify", { + action: "replace", + replaceCallID: replaceCallID, + params: params + }); + } + }; + + $.verto.dialog.prototype.hold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "hold", + params: params + }); + }; + + $.verto.dialog.prototype.unhold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "unhold", + params: params + }); + }; + + $.verto.dialog.prototype.toggleHold = function(params) { + var dialog = this; + + dialog.sendMethod("verto.modify", { + action: "toggleHold", + params: params + }); + }; + + $.verto.dialog.prototype.message = function(msg) { + var dialog = this; + var err = 0; + + msg.from = dialog.params.login; + + 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) { + params = {}; + } + + params.sdp = dialog.params.sdp; + + if (params) { + if (params.useVideo) { + dialog.useVideo(true); + } + dialog.params.callee_id_name = params.callee_id_name; + dialog.params.callee_id_number = params.callee_id_number; + + if (params.useCamera) { + dialog.useCamera = params.useCamera; + } + + if (params.useMic) { + dialog.useMic = params.useMic; + } + + if (params.useSpeak) { + dialog.useSpeak = params.useSpeak; + } + } + + dialog.rtc.createAnswer(params); + dialog.answered = true; + } + }; + + $.verto.dialog.prototype.handleAnswer = function(params) { + var dialog = this; + + dialog.gotAnswer = true; + + 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 { + if (dialog.gotEarly) { + console.log("Dialog " + dialog.callID + " Got answer while still establishing early media, delaying..."); + } else { + console.log("Dialog " + dialog.callID + " Answering Channel"); + dialog.rtc.answer(params.sdp, function() { + dialog.setState($.verto.enum.state.active); + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "ANSWER SDP", params.sdp); + } + } + + + }; + + $.verto.dialog.prototype.cidString = function(enc) { + var dialog = this; + var party = dialog.params.remote_caller_id_name + (enc ? " <" : " <") + dialog.params.remote_caller_id_number + (enc ? ">" : ">"); + 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); + + }; + + $.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(params) { + var dialog = this; + + if (dialog.state.val >= $.verto.enum.state.early.val) { + return; + } + + dialog.gotEarly = true; + + dialog.rtc.answer(params.sdp, function() { + console.log("Dialog " + dialog.callID + "Establishing early media"); + dialog.setState($.verto.enum.state.early); + + if (dialog.gotAnswer) { + console.log("Dialog " + dialog.callID + "Answering Channel"); + dialog.setState($.verto.enum.state.active); + } + }, function(e) { + console.error(e); + dialog.hangup(); + }); + console.log("Dialog " + dialog.callID + "EARLY SDP", params.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, + recovering: 1, + ringing: 1, + destroy: 1, + answering: 1, + hangup: 1 + }, + requesting: { + trying: 1, + hangup: 1, + active: 1 + }, + recovering: { + answering: 1, + hangup: 1 + }, + trying: { + active: 1, + early: 1, + hangup: 1 + }, + ringing: { + answering: 1, + hangup: 1 + }, + answering: { + active: 1, + hangup: 1 + }, + active: { + answering: 1, + requesting: 1, + 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 recovering ringing answering early active held hangup destroy purge"); + $.verto.enum.direction = $.verto.ENUM("inbound outbound"); + $.verto.enum.message = $.verto.ENUM("display info pvtEvent clientReady"); + + $.verto.enum = Object.freeze($.verto.enum); + + $.verto.saved = []; + + $.verto.unloadJobs = []; + + var unloadEventName = 'beforeunload'; + // Hacks for Mobile Safari + var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; + if (iOS) { + unloadEventName = 'pagehide'; + } + + $(window).bind(unloadEventName, function() { + for (var f in $.verto.unloadJobs) { + $.verto.unloadJobs[f](); + } + + if ($.verto.haltClosure) + return $.verto.haltClosure(); + + for (var i in $.verto.saved) { + var verto = $.verto.saved[i]; + if (verto) { + verto.purge(); + verto.logout(); + } + } + + return $.verto.warnOnUnload; + }); + + $.verto.videoDevices = []; + $.verto.audioInDevices = []; + $.verto.audioOutDevices = []; + + var checkDevices = function(runtime) { + console.info("enumerating devices"); + var aud_in = [], aud_out = [], vid = []; + var has_video = 0, has_audio = 0; + var Xstream; + + function gotDevices(deviceInfos) { + // Handles being called several times to update labels. Preserve values. + for (var i = 0; i !== deviceInfos.length; ++i) { + var deviceInfo = deviceInfos[i]; + var text = ""; + + console.log(deviceInfo); + console.log(deviceInfo.kind + ": " + deviceInfo.label + " id = " + deviceInfo.deviceId); + + if (deviceInfo.kind === 'audioinput') { + text = deviceInfo.label || 'microphone ' + (aud_in.length + 1); + aud_in.push({id: deviceInfo.deviceId, kind: "audio_in", label: text}); + } else if (deviceInfo.kind === 'audiooutput') { + text = deviceInfo.label || 'speaker ' + (aud_out.length + 1); + aud_out.push({id: deviceInfo.deviceId, kind: "audio_out", label: text}); + } else if (deviceInfo.kind === 'videoinput') { + text = deviceInfo.label || 'camera ' + (vid.length + 1); + vid.push({id: deviceInfo.deviceId, kind: "video", label: text}); + } else { + console.log('Some other kind of source/device: ', deviceInfo); + } + } + + + $.verto.videoDevices = vid; + $.verto.audioInDevices = aud_in; + $.verto.audioOutDevices = aud_out; + + console.info("Audio IN Devices", $.verto.audioInDevices); + console.info("Audio Out Devices", $.verto.audioOutDevices); + console.info("Video Devices", $.verto.videoDevices); + + if (Xstream) { + Xstream.getTracks().forEach(function(track) {track.stop();}); + } + + if (runtime) { + runtime(true); + } + } + + + + + function handleError(error) { + console.log('device enumeration error: ', error); + if (runtime) runtime(false); + } + + + function checkTypes(devs) { + for (var i = 0; i !== devs.length; ++i) { + + if (devs[i].kind === 'audioinput') { + has_audio++; + } else if (devs[i].kind === 'videoinput') { + has_video++; + } + } + + navigator.getUserMedia({ audio: (has_audio > 0 ? true : false), video: (has_video > 0 ? true : false)}, + function(stream) { + Xstream = stream; + navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); + }, + function(err) { + console.log("The following error occurred: " + err.name); + } + ); + } + + navigator.mediaDevices.enumerateDevices().then(checkTypes).catch(handleError); + + }; + + $.verto.refreshDevices = function(runtime) { + checkDevices(runtime); + } + + $.verto.init = function(obj, runtime) { + if (!obj) { + obj = {}; + } + + if (!obj.skipPermCheck && !obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + checkDevices(runtime); + }, true, true); + } else if (obj.skipPermCheck && !obj.skipDeviceCheck) { + checkDevices(runtime); + } else if (!obj.skipPermCheck && obj.skipDeviceCheck) { + $.FSRTC.checkPerms(function(status) { + runtime(status); + }, true, true); + } else { + runtime(null); + } + + } + + $.verto.genUUID = function () { + return generateGUID(); + } + + +})(jQuery); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 14393 && + url.indexOf('?transport=udp') === -1; + }); + + delete server.url; + server.urls = isString ? urls[0] : urls; + return !!urls.length; + } + }); +} + +// Determines the intersection of local and remote capabilities. +function getCommonCapabilities(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + + var findCodecByPayloadType = function(pt, codecs) { + pt = parseInt(pt, 10); + for (var i = 0; i < codecs.length; i++) { + if (codecs[i].payloadType === pt || + codecs[i].preferredPayloadType === pt) { + return codecs[i]; + } + } + }; + + var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { + var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); + var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); + return lCodec && rCodec && + lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); + }; + + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && + lCodec.clockRate === rCodec.clockRate) { + if (lCodec.name.toLowerCase() === 'rtx' && + lCodec.parameters && rCodec.parameters.apt) { + // for RTX we need to find the local rtx that has a apt + // which points to the same local codec as the remote one. + if (!rtxCapabilityMatches(lCodec, rCodec, + localCapabilities.codecs, remoteCapabilities.codecs)) { + continue; + } + } + rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy + // number of channels is the highest common number of channels + rCodec.numChannels = Math.min(lCodec.numChannels, + rCodec.numChannels); + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // determine common feedback mechanisms + rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { + for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { + if (lCodec.rtcpFeedback[j].type === fb.type && + lCodec.rtcpFeedback[j].parameter === fb.parameter) { + return true; + } + } + return false; + }); + // FIXME: also need to determine .parameters + // see https://github.com/openpeer/ortc/issues/569 + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; + i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; +} + +// is action=setLocalDescription with type allowed in signalingState +function isActionAllowedInSignalingState(action, type, signalingState) { + return { + offer: { + setLocalDescription: ['stable', 'have-local-offer'], + setRemoteDescription: ['stable', 'have-remote-offer'] + }, + answer: { + setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], + setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] + } + }[type][action].indexOf(signalingState) !== -1; +} + +function maybeAddCandidate(iceTransport, candidate) { + // Edge's internal representation adds some fields therefore + // not all fieldѕ are taken into account. + var alreadyAdded = iceTransport.getRemoteCandidates() + .find(function(remoteCandidate) { + return candidate.foundation === remoteCandidate.foundation && + candidate.ip === remoteCandidate.ip && + candidate.port === remoteCandidate.port && + candidate.priority === remoteCandidate.priority && + candidate.protocol === remoteCandidate.protocol && + candidate.type === remoteCandidate.type; + }); + if (!alreadyAdded) { + iceTransport.addRemoteCandidate(candidate); + } + return !alreadyAdded; +} + + +// https://w3c.github.io/mediacapture-main/#mediastream +// Helper function to add the track to the stream and +// dispatch the event ourselves. +function addTrackToStreamAndFireEvent(track, stream) { + stream.addTrack(track); + var e = new Event('addtrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function removeTrackFromStreamAndFireEvent(track, stream) { + stream.removeTrack(track); + var e = new Event('removetrack'); // TODO: MediaStreamTrackEvent + e.track = track; + stream.dispatchEvent(e); +} + +function fireAddTrack(pc, track, receiver, streams) { + var trackEvent = new Event('track'); + trackEvent.track = track; + trackEvent.receiver = receiver; + trackEvent.transceiver = {receiver: receiver}; + trackEvent.streams = streams; + window.setTimeout(function() { + pc._dispatchEvent('track', trackEvent); + }); +} + +function makeError(name, description) { + var e = new Error(description); + e.name = name; + return e; +} + +module.exports = function(window, edgeVersion) { + var RTCPeerConnection = function(config) { + var pc = this; + + var _eventTarget = document.createDocumentFragment(); + ['addEventListener', 'removeEventListener', 'dispatchEvent'] + .forEach(function(method) { + pc[method] = _eventTarget[method].bind(_eventTarget); + }); + + this.canTrickleIceCandidates = null; + + this.needNegotiation = false; + + this.localStreams = []; + this.remoteStreams = []; + + this.localDescription = null; + this.remoteDescription = null; + + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + this.iceGatheringState = 'new'; + + config = JSON.parse(JSON.stringify(config || {})); + + this.usingBundle = config.bundlePolicy === 'max-bundle'; + if (config.rtcpMuxPolicy === 'negotiate') { + throw(makeError('NotSupportedError', + 'rtcpMuxPolicy \'negotiate\' is not supported')); + } else if (!config.rtcpMuxPolicy) { + config.rtcpMuxPolicy = 'require'; + } + + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + break; + default: + config.iceTransportPolicy = 'all'; + break; + } + + switch (config.bundlePolicy) { + case 'balanced': + case 'max-compat': + case 'max-bundle': + break; + default: + config.bundlePolicy = 'balanced'; + break; + } + + config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); + + this._iceGatherers = []; + if (config.iceCandidatePoolSize) { + for (var i = config.iceCandidatePoolSize; i > 0; i--) { + this._iceGatherers = new window.RTCIceGatherer({ + iceServers: config.iceServers, + gatherPolicy: config.iceTransportPolicy + }); + } + } else { + config.iceCandidatePoolSize = 0; + } + + this._config = config; + + // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... + // everything that is needed to describe a SDP m-line. + this.transceivers = []; + + this._sdpSessionId = SDPUtils.generateSessionId(); + this._sdpSessionVersion = 0; + + this._dtlsRole = undefined; // role for a=setup to use in answers. + + this._isClosed = false; + }; + + // set up event handlers on prototype + RTCPeerConnection.prototype.onicecandidate = null; + RTCPeerConnection.prototype.onaddstream = null; + RTCPeerConnection.prototype.ontrack = null; + RTCPeerConnection.prototype.onremovestream = null; + RTCPeerConnection.prototype.onsignalingstatechange = null; + RTCPeerConnection.prototype.oniceconnectionstatechange = null; + RTCPeerConnection.prototype.onicegatheringstatechange = null; + RTCPeerConnection.prototype.onnegotiationneeded = null; + RTCPeerConnection.prototype.ondatachannel = null; + + RTCPeerConnection.prototype._dispatchEvent = function(name, event) { + if (this._isClosed) { + return; + } + this.dispatchEvent(event); + if (typeof this['on' + name] === 'function') { + this['on' + name](event); + } + }; + + RTCPeerConnection.prototype._emitGatheringStateChange = function() { + var event = new Event('icegatheringstatechange'); + this._dispatchEvent('icegatheringstatechange', event); + }; + + RTCPeerConnection.prototype.getConfiguration = function() { + return this._config; + }; + + RTCPeerConnection.prototype.getLocalStreams = function() { + return this.localStreams; + }; + + RTCPeerConnection.prototype.getRemoteStreams = function() { + return this.remoteStreams; + }; + + // internal helper to create a transceiver object. + // (whih is not yet the same as the WebRTC 1.0 transceiver) + RTCPeerConnection.prototype._createTransceiver = function(kind) { + var hasBundleTransport = this.transceivers.length > 0; + var transceiver = { + track: null, + iceGatherer: null, + iceTransport: null, + dtlsTransport: null, + localCapabilities: null, + remoteCapabilities: null, + rtpSender: null, + rtpReceiver: null, + kind: kind, + mid: null, + sendEncodingParameters: null, + recvEncodingParameters: null, + stream: null, + associatedRemoteMediaStreams: [], + wantReceive: true + }; + if (this.usingBundle && hasBundleTransport) { + transceiver.iceTransport = this.transceivers[0].iceTransport; + transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; + } else { + var transports = this._createIceAndDtlsTransports(); + transceiver.iceTransport = transports.iceTransport; + transceiver.dtlsTransport = transports.dtlsTransport; + } + this.transceivers.push(transceiver); + return transceiver; + }; + + RTCPeerConnection.prototype.addTrack = function(track, stream) { + var alreadyExists = this.transceivers.find(function(s) { + return s.track === track; + }); + + if (alreadyExists) { + throw makeError('InvalidAccessError', 'Track already exists.'); + } + + if (this.signalingState === 'closed') { + throw makeError('InvalidStateError', + 'Attempted to call addTrack on a closed peerconnection.'); + } + + var transceiver; + for (var i = 0; i < this.transceivers.length; i++) { + if (!this.transceivers[i].track && + this.transceivers[i].kind === track.kind) { + transceiver = this.transceivers[i]; + } + } + if (!transceiver) { + transceiver = this._createTransceiver(track.kind); + } + + this._maybeFireNegotiationNeeded(); + + if (this.localStreams.indexOf(stream) === -1) { + this.localStreams.push(stream); + } + + transceiver.track = track; + transceiver.stream = stream; + transceiver.rtpSender = new window.RTCRtpSender(track, + transceiver.dtlsTransport); + return transceiver.rtpSender; + }; + + RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + if (edgeVersion >= 15025) { + stream.getTracks().forEach(function(track) { + pc.addTrack(track, stream); + }); + } else { + // Clone is necessary for local demos mostly, attaching directly + // to two different senders does not work (build 10547). + // Fixed in 15025 (or earlier) + var clonedStream = stream.clone(); + stream.getTracks().forEach(function(track, idx) { + var clonedTrack = clonedStream.getTracks()[idx]; + track.addEventListener('enabled', function(event) { + clonedTrack.enabled = event.enabled; + }); + }); + clonedStream.getTracks().forEach(function(track) { + pc.addTrack(track, clonedStream); + }); + } + }; + + RTCPeerConnection.prototype.removeTrack = function(sender) { + if (!(sender instanceof window.RTCRtpSender)) { + throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.'); + } + + var transceiver = this.transceivers.find(function(t) { + return t.rtpSender === sender; + }); + + if (!transceiver) { + throw makeError('InvalidAccessError', + 'Sender was not created by this connection.'); + } + var stream = transceiver.stream; + + transceiver.rtpSender.stop(); + transceiver.rtpSender = null; + transceiver.track = null; + transceiver.stream = null; + + // remove the stream from the set of local streams + var localStreams = this.transceivers.map(function(t) { + return t.stream; + }); + if (localStreams.indexOf(stream) === -1 && + this.localStreams.indexOf(stream) > -1) { + this.localStreams.splice(this.localStreams.indexOf(stream), 1); + } + + this._maybeFireNegotiationNeeded(); + }; + + RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + stream.getTracks().forEach(function(track) { + var sender = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (sender) { + pc.removeTrack(sender); + } + }); + }; + + RTCPeerConnection.prototype.getSenders = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpSender; + }) + .map(function(transceiver) { + return transceiver.rtpSender; + }); + }; + + RTCPeerConnection.prototype.getReceivers = function() { + return this.transceivers.filter(function(transceiver) { + return !!transceiver.rtpReceiver; + }) + .map(function(transceiver) { + return transceiver.rtpReceiver; + }); + }; + + + RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, + usingBundle) { + var pc = this; + if (usingBundle && sdpMLineIndex > 0) { + return this.transceivers[0].iceGatherer; + } else if (this._iceGatherers.length) { + return this._iceGatherers.shift(); + } + var iceGatherer = new window.RTCIceGatherer({ + iceServers: this._config.iceServers, + gatherPolicy: this._config.iceTransportPolicy + }); + Object.defineProperty(iceGatherer, 'state', + {value: 'new', writable: true} + ); + + this.transceivers[sdpMLineIndex].candidates = []; + this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { + var end = !event.candidate || Object.keys(event.candidate).length === 0; + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + iceGatherer.state = end ? 'completed' : 'gathering'; + if (pc.transceivers[sdpMLineIndex].candidates !== null) { + pc.transceivers[sdpMLineIndex].candidates.push(event.candidate); + } + }; + iceGatherer.addEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + return iceGatherer; + }; + + // start gathering from an RTCIceGatherer. + RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { + var pc = this; + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer.onlocalcandidate) { + return; + } + var candidates = this.transceivers[sdpMLineIndex].candidates; + this.transceivers[sdpMLineIndex].candidates = null; + iceGatherer.removeEventListener('localcandidate', + this.transceivers[sdpMLineIndex].bufferCandidates); + iceGatherer.onlocalcandidate = function(evt) { + if (pc.usingBundle && sdpMLineIndex > 0) { + // if we know that we use bundle we can drop candidates with + // ѕdpMLineIndex > 0. If we don't do this then our state gets + // confused since we dispose the extra ice gatherer. + return; + } + var event = new Event('icecandidate'); + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + // Edge emits an empty object for RTCIceCandidateComplete‥ + var end = !cand || Object.keys(cand).length === 0; + if (end) { + // polyfill since RTCIceGatherer.state is not implemented in + // Edge 10547 yet. + if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { + iceGatherer.state = 'completed'; + } + } else { + if (iceGatherer.state === 'new') { + iceGatherer.state = 'gathering'; + } + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = 1; + event.candidate.candidate = SDPUtils.writeCandidate(cand); + } + + // update local description. + var sections = SDPUtils.splitSections(pc.localDescription.sdp); + if (!end) { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=' + event.candidate.candidate + '\r\n'; + } else { + sections[event.candidate.sdpMLineIndex + 1] += + 'a=end-of-candidates\r\n'; + } + pc.localDescription.sdp = sections.join(''); + var complete = pc.transceivers.every(function(transceiver) { + return transceiver.iceGatherer && + transceiver.iceGatherer.state === 'completed'; + }); + + if (pc.iceGatheringState !== 'gathering') { + pc.iceGatheringState = 'gathering'; + pc._emitGatheringStateChange(); + } + + // Emit candidate. Also emit null candidate when all gatherers are + // complete. + if (!end) { + pc._dispatchEvent('icecandidate', event); + } + if (complete) { + pc._dispatchEvent('icecandidate', new Event('icecandidate')); + pc.iceGatheringState = 'complete'; + pc._emitGatheringStateChange(); + } + }; + + // emit already gathered candidates. + window.setTimeout(function() { + candidates.forEach(function(candidate) { + var e = new Event('RTCIceGatherEvent'); + e.candidate = candidate; + iceGatherer.onlocalcandidate(e); + }); + }, 0); + }; + + // Create ICE transport and DTLS transport. + RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { + var pc = this; + var iceTransport = new window.RTCIceTransport(null); + iceTransport.onicestatechange = function() { + pc._updateConnectionState(); + }; + + var dtlsTransport = new window.RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + pc._updateConnectionState(); + }; + dtlsTransport.onerror = function() { + // onerror does not set state to failed by itpc. + Object.defineProperty(dtlsTransport, 'state', + {value: 'failed', writable: true}); + pc._updateConnectionState(); + }; + + return { + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + // Destroy ICE gatherer, ICE transport and DTLS transport. + // Without triggering the callbacks. + RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( + sdpMLineIndex) { + var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; + if (iceGatherer) { + delete iceGatherer.onlocalcandidate; + delete this.transceivers[sdpMLineIndex].iceGatherer; + } + var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; + if (iceTransport) { + delete iceTransport.onicestatechange; + delete this.transceivers[sdpMLineIndex].iceTransport; + } + var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; + if (dtlsTransport) { + delete dtlsTransport.ondtlsstatechange; + delete dtlsTransport.onerror; + delete this.transceivers[sdpMLineIndex].dtlsTransport; + } + }; + + // Start the RTP Sender and Receiver for a transceiver. + RTCPeerConnection.prototype._transceive = function(transceiver, + send, recv) { + var params = getCommonCapabilities(transceiver.localCapabilities, + transceiver.remoteCapabilities); + if (send && transceiver.rtpSender) { + params.encodings = transceiver.sendEncodingParameters; + params.rtcp = { + cname: SDPUtils.localCName, + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.recvEncodingParameters.length) { + params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; + } + transceiver.rtpSender.send(params); + } + if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { + // remove RTX field in Edge 14942 + if (transceiver.kind === 'video' + && transceiver.recvEncodingParameters + && edgeVersion < 15019) { + transceiver.recvEncodingParameters.forEach(function(p) { + delete p.rtx; + }); + } + if (transceiver.recvEncodingParameters.length) { + params.encodings = transceiver.recvEncodingParameters; + } + params.rtcp = { + compound: transceiver.rtcpParameters.compound + }; + if (transceiver.rtcpParameters.cname) { + params.rtcp.cname = transceiver.rtcpParameters.cname; + } + if (transceiver.sendEncodingParameters.length) { + params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; + } + transceiver.rtpReceiver.receive(params); + } + }; + + RTCPeerConnection.prototype.setLocalDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setLocalDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set local ' + description.type + + ' in state ' + pc.signalingState)); + } + + var sections; + var sessionpart; + if (description.type === 'offer') { + // VERY limited support for SDP munging. Limited to: + // * changing the order of codecs + sections = SDPUtils.splitSections(description.sdp); + sessionpart = sections.shift(); + sections.forEach(function(mediaSection, sdpMLineIndex) { + var caps = SDPUtils.parseRtpParameters(mediaSection); + pc.transceivers[sdpMLineIndex].localCapabilities = caps; + }); + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + pc._gather(transceiver.mid, sdpMLineIndex); + }); + } else if (description.type === 'answer') { + sections = SDPUtils.splitSections(pc.remoteDescription.sdp); + sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + sections.forEach(function(mediaSection, sdpMLineIndex) { + var transceiver = pc.transceivers[sdpMLineIndex]; + var iceGatherer = transceiver.iceGatherer; + var iceTransport = transceiver.iceTransport; + var dtlsTransport = transceiver.dtlsTransport; + var localCapabilities = transceiver.localCapabilities; + var remoteCapabilities = transceiver.remoteCapabilities; + + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + + if (!rejected && !transceiver.isDatachannel) { + var remoteIceParameters = SDPUtils.getIceParameters( + mediaSection, sessionpart); + var remoteDtlsParameters = SDPUtils.getDtlsParameters( + mediaSection, sessionpart); + if (isIceLite) { + remoteDtlsParameters.role = 'server'; + } + + if (!pc.usingBundle || sdpMLineIndex === 0) { + pc._gather(transceiver.mid, sdpMLineIndex); + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + isIceLite ? 'controlling' : 'controlled'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + // Calculate intersection of capabilities. + var params = getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Start the RTCRtpSender. The RTCRtpReceiver for this + // transceiver has already been started in setRemoteDescription. + pc._transceive(transceiver, + params.codecs.length > 0, + false); + } + }); + } + + this.localDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.setRemoteDescription = function(description) { + var pc = this; + + if (!isActionAllowedInSignalingState('setRemoteDescription', + description.type, this.signalingState) || this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not set remote ' + description.type + + ' in state ' + pc.signalingState)); + } + + var streams = {}; + this.remoteStreams.forEach(function(stream) { + streams[stream.id] = stream; + }); + var receiverList = []; + var sections = SDPUtils.splitSections(description.sdp); + var sessionpart = sections.shift(); + var isIceLite = SDPUtils.matchPrefix(sessionpart, + 'a=ice-lite').length > 0; + var usingBundle = SDPUtils.matchPrefix(sessionpart, + 'a=group:BUNDLE ').length > 0; + this.usingBundle = usingBundle; + var iceOptions = SDPUtils.matchPrefix(sessionpart, + 'a=ice-options:')[0]; + if (iceOptions) { + this.canTrickleIceCandidates = iceOptions.substr(14).split(' ') + .indexOf('trickle') >= 0; + } else { + this.canTrickleIceCandidates = false; + } + + sections.forEach(function(mediaSection, sdpMLineIndex) { + var lines = SDPUtils.splitLines(mediaSection); + var kind = SDPUtils.getKind(mediaSection); + // treat bundle-only as not-rejected. + var rejected = SDPUtils.isRejected(mediaSection) && + SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; + var protocol = lines[0].substr(2).split(' ')[2]; + + var direction = SDPUtils.getDirection(mediaSection, sessionpart); + var remoteMsid = SDPUtils.parseMsid(mediaSection); + + var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); + + // Reject datachannels which are not implemented yet. + if (kind === 'application' && protocol === 'DTLS/SCTP') { + pc.transceivers[sdpMLineIndex] = { + mid: mid, + isDatachannel: true + }; + return; + } + + var transceiver; + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpReceiver; + var sendEncodingParameters; + var recvEncodingParameters; + var localCapabilities; + + var track; + // FIXME: ensure the mediaSection has rtcp-mux set. + var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); + var remoteIceParameters; + var remoteDtlsParameters; + if (!rejected) { + remoteIceParameters = SDPUtils.getIceParameters(mediaSection, + sessionpart); + remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, + sessionpart); + remoteDtlsParameters.role = 'client'; + } + recvEncodingParameters = + SDPUtils.parseRtpEncodingParameters(mediaSection); + + var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); + + var isComplete = SDPUtils.matchPrefix(mediaSection, + 'a=end-of-candidates', sessionpart).length > 0; + var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') + .map(function(cand) { + return SDPUtils.parseCandidate(cand); + }) + .filter(function(cand) { + return cand.component === 1; + }); + + // Check if we can use BUNDLE and dispose transports. + if ((description.type === 'offer' || description.type === 'answer') && + !rejected && usingBundle && sdpMLineIndex > 0 && + pc.transceivers[sdpMLineIndex]) { + pc._disposeIceAndDtlsTransports(sdpMLineIndex); + pc.transceivers[sdpMLineIndex].iceGatherer = + pc.transceivers[0].iceGatherer; + pc.transceivers[sdpMLineIndex].iceTransport = + pc.transceivers[0].iceTransport; + pc.transceivers[sdpMLineIndex].dtlsTransport = + pc.transceivers[0].dtlsTransport; + if (pc.transceivers[sdpMLineIndex].rtpSender) { + pc.transceivers[sdpMLineIndex].rtpSender.setTransport( + pc.transceivers[0].dtlsTransport); + } + if (pc.transceivers[sdpMLineIndex].rtpReceiver) { + pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( + pc.transceivers[0].dtlsTransport); + } + } + if (description.type === 'offer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex] || + pc._createTransceiver(kind); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + usingBundle); + } + + if (cands.length && transceiver.iceTransport.state === 'new') { + if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { + transceiver.iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); + + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + + sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 2) * 1001 + }]; + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + var isNewTrack = false; + if (direction === 'sendrecv' || direction === 'sendonly') { + isNewTrack = !transceiver.rtpReceiver; + rtpReceiver = transceiver.rtpReceiver || + new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); + + if (isNewTrack) { + var stream; + track = rtpReceiver.track; + // FIXME: does not work with Plan B. + if (remoteMsid && remoteMsid.stream === '-') { + // no-op. a stream id of '-' means: no associated stream. + } else if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + Object.defineProperty(streams[remoteMsid.stream], 'id', { + get: function() { + return remoteMsid.stream; + } + }); + } + Object.defineProperty(track, 'id', { + get: function() { + return remoteMsid.track; + } + }); + stream = streams[remoteMsid.stream]; + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + stream = streams.default; + } + if (stream) { + addTrackToStreamAndFireEvent(track, stream); + transceiver.associatedRemoteMediaStreams.push(stream); + } + receiverList.push([track, rtpReceiver, stream]); + } + } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { + transceiver.associatedRemoteMediaStreams.forEach(function(s) { + var nativeTrack = s.getTracks().find(function(t) { + return t.id === transceiver.rtpReceiver.track.id; + }); + if (nativeTrack) { + removeTrackFromStreamAndFireEvent(nativeTrack, s); + } + }); + transceiver.associatedRemoteMediaStreams = []; + } + + transceiver.localCapabilities = localCapabilities; + transceiver.remoteCapabilities = remoteCapabilities; + transceiver.rtpReceiver = rtpReceiver; + transceiver.rtcpParameters = rtcpParameters; + transceiver.sendEncodingParameters = sendEncodingParameters; + transceiver.recvEncodingParameters = recvEncodingParameters; + + // Start the RTCRtpReceiver now. The RTPSender is started in + // setLocalDescription. + pc._transceive(pc.transceivers[sdpMLineIndex], + false, + isNewTrack); + } else if (description.type === 'answer' && !rejected) { + transceiver = pc.transceivers[sdpMLineIndex]; + iceGatherer = transceiver.iceGatherer; + iceTransport = transceiver.iceTransport; + dtlsTransport = transceiver.dtlsTransport; + rtpReceiver = transceiver.rtpReceiver; + sendEncodingParameters = transceiver.sendEncodingParameters; + localCapabilities = transceiver.localCapabilities; + + pc.transceivers[sdpMLineIndex].recvEncodingParameters = + recvEncodingParameters; + pc.transceivers[sdpMLineIndex].remoteCapabilities = + remoteCapabilities; + pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; + + if (cands.length && iceTransport.state === 'new') { + if ((isIceLite || isComplete) && + (!usingBundle || sdpMLineIndex === 0)) { + iceTransport.setRemoteCandidates(cands); + } else { + cands.forEach(function(candidate) { + maybeAddCandidate(transceiver.iceTransport, candidate); + }); + } + } + + if (!usingBundle || sdpMLineIndex === 0) { + if (iceTransport.state === 'new') { + iceTransport.start(iceGatherer, remoteIceParameters, + 'controlling'); + } + if (dtlsTransport.state === 'new') { + dtlsTransport.start(remoteDtlsParameters); + } + } + + pc._transceive(transceiver, + direction === 'sendrecv' || direction === 'recvonly', + direction === 'sendrecv' || direction === 'sendonly'); + + // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams + if (rtpReceiver && + (direction === 'sendrecv' || direction === 'sendonly')) { + track = rtpReceiver.track; + if (remoteMsid) { + if (!streams[remoteMsid.stream]) { + streams[remoteMsid.stream] = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); + receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); + } else { + if (!streams.default) { + streams.default = new window.MediaStream(); + } + addTrackToStreamAndFireEvent(track, streams.default); + receiverList.push([track, rtpReceiver, streams.default]); + } + } else { + // FIXME: actually the receiver should be created later. + delete transceiver.rtpReceiver; + } + } + }); + + if (this._dtlsRole === undefined) { + this._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; + } + + this.remoteDescription = { + type: description.type, + sdp: description.sdp + }; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + default: + throw new TypeError('unsupported type "' + description.type + + '"'); + } + Object.keys(streams).forEach(function(sid) { + var stream = streams[sid]; + if (stream.getTracks().length) { + if (pc.remoteStreams.indexOf(stream) === -1) { + pc.remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + window.setTimeout(function() { + pc._dispatchEvent('addstream', event); + }); + } + + receiverList.forEach(function(item) { + var track = item[0]; + var receiver = item[1]; + if (stream.id !== item[2].id) { + return; + } + fireAddTrack(pc, track, receiver, [stream]); + }); + } + }); + receiverList.forEach(function(item) { + if (item[2]) { + return; + } + fireAddTrack(pc, item[0], item[1], []); + }); + + // check whether addIceCandidate({}) was called within four seconds after + // setRemoteDescription. + window.setTimeout(function() { + if (!(pc && pc.transceivers)) { + return; + } + pc.transceivers.forEach(function(transceiver) { + if (transceiver.iceTransport && + transceiver.iceTransport.state === 'new' && + transceiver.iceTransport.getRemoteCandidates().length > 0) { + console.warn('Timeout for addRemoteCandidate. Consider sending ' + + 'an end-of-candidates notification'); + transceiver.iceTransport.addRemoteCandidate({}); + } + }); + }, 4000); + + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.close = function() { + this.transceivers.forEach(function(transceiver) { + /* not yet + if (transceiver.iceGatherer) { + transceiver.iceGatherer.close(); + } + */ + if (transceiver.iceTransport) { + transceiver.iceTransport.stop(); + } + if (transceiver.dtlsTransport) { + transceiver.dtlsTransport.stop(); + } + if (transceiver.rtpSender) { + transceiver.rtpSender.stop(); + } + if (transceiver.rtpReceiver) { + transceiver.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._isClosed = true; + this._updateSignalingState('closed'); + }; + + // Update the signaling state. + RTCPeerConnection.prototype._updateSignalingState = function(newState) { + this.signalingState = newState; + var event = new Event('signalingstatechange'); + this._dispatchEvent('signalingstatechange', event); + }; + + // Determine whether to fire the negotiationneeded event. + RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { + var pc = this; + if (this.signalingState !== 'stable' || this.needNegotiation === true) { + return; + } + this.needNegotiation = true; + window.setTimeout(function() { + if (pc.needNegotiation === false) { + return; + } + pc.needNegotiation = false; + var event = new Event('negotiationneeded'); + pc._dispatchEvent('negotiationneeded', event); + }, 0); + }; + + // Update the connection state. + RTCPeerConnection.prototype._updateConnectionState = function() { + var newState; + var states = { + 'new': 0, + closed: 0, + connecting: 0, + checking: 0, + connected: 0, + completed: 0, + disconnected: 0, + failed: 0 + }; + this.transceivers.forEach(function(transceiver) { + states[transceiver.iceTransport.state]++; + states[transceiver.dtlsTransport.state]++; + }); + // ICETransport.completed and connected are the same for this purpose. + states.connected += states.completed; + + newState = 'new'; + if (states.failed > 0) { + newState = 'failed'; + } else if (states.connecting > 0 || states.checking > 0) { + newState = 'connecting'; + } else if (states.disconnected > 0) { + newState = 'disconnected'; + } else if (states.new > 0) { + newState = 'new'; + } else if (states.connected > 0 || states.completed > 0) { + newState = 'connected'; + } + + if (newState !== this.iceConnectionState) { + this.iceConnectionState = newState; + var event = new Event('iceconnectionstatechange'); + this._dispatchEvent('iceconnectionstatechange', event); + } + }; + + RTCPeerConnection.prototype.createOffer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createOffer after close')); + } + + var numAudioTracks = this.transceivers.filter(function(t) { + return t.kind === 'audio'; + }).length; + var numVideoTracks = this.transceivers.filter(function(t) { + return t.kind === 'video'; + }).length; + + // Determine number of audio and video tracks we need to send/recv. + var offerOptions = arguments[0]; + if (offerOptions) { + // Reject Chrome legacy constraints. + if (offerOptions.mandatory || offerOptions.optional) { + throw new TypeError( + 'Legacy mandatory/optional constraints not supported.'); + } + if (offerOptions.offerToReceiveAudio !== undefined) { + if (offerOptions.offerToReceiveAudio === true) { + numAudioTracks = 1; + } else if (offerOptions.offerToReceiveAudio === false) { + numAudioTracks = 0; + } else { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + } + if (offerOptions.offerToReceiveVideo !== undefined) { + if (offerOptions.offerToReceiveVideo === true) { + numVideoTracks = 1; + } else if (offerOptions.offerToReceiveVideo === false) { + numVideoTracks = 0; + } else { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + + this.transceivers.forEach(function(transceiver) { + if (transceiver.kind === 'audio') { + numAudioTracks--; + if (numAudioTracks < 0) { + transceiver.wantReceive = false; + } + } else if (transceiver.kind === 'video') { + numVideoTracks--; + if (numVideoTracks < 0) { + transceiver.wantReceive = false; + } + } + }); + + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + this._createTransceiver('audio'); + numAudioTracks--; + } + if (numVideoTracks > 0) { + this._createTransceiver('video'); + numVideoTracks--; + } + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, + // dtls transport, potentially rtpsender and rtpreceiver. + var track = transceiver.track; + var kind = transceiver.kind; + var mid = SDPUtils.generateIdentifier(); + transceiver.mid = mid; + + if (!transceiver.iceGatherer) { + transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, + pc.usingBundle); + } + + var localCapabilities = window.RTCRtpSender.getCapabilities(kind); + // filter RTX until additional stuff needed for RTX is implemented + // in adapter.js + if (edgeVersion < 15019) { + localCapabilities.codecs = localCapabilities.codecs.filter( + function(codec) { + return codec.name !== 'rtx'; + }); + } + localCapabilities.codecs.forEach(function(codec) { + // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 + // by adding level-asymmetry-allowed=1 + if (codec.name === 'H264' && + codec.parameters['level-asymmetry-allowed'] === undefined) { + codec.parameters['level-asymmetry-allowed'] = '1'; + } + }); + + // generate an ssrc now, to be used later in rtpSender.send + var sendEncodingParameters = transceiver.sendEncodingParameters || [{ + ssrc: (2 * sdpMLineIndex + 1) * 1001 + }]; + if (track) { + // add RTX + if (edgeVersion >= 15019 && kind === 'video' && + !sendEncodingParameters[0].rtx) { + sendEncodingParameters[0].rtx = { + ssrc: sendEncodingParameters[0].ssrc + 1 + }; + } + } + + if (transceiver.wantReceive) { + transceiver.rtpReceiver = new window.RTCRtpReceiver( + transceiver.dtlsTransport, kind); + } + + transceiver.localCapabilities = localCapabilities; + transceiver.sendEncodingParameters = sendEncodingParameters; + }); + + // always offer BUNDLE and dispose on return if not supported. + if (this._config.bundlePolicy !== 'max-compat') { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + sdp += 'a=ice-options:trickle\r\n'; + + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + sdp += writeMediaSection(transceiver, transceiver.localCapabilities, + 'offer', transceiver.stream, pc._dtlsRole); + sdp += 'a=rtcp-rsize\r\n'; + + if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && + (sdpMLineIndex === 0 || !pc.usingBundle)) { + transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { + cand.component = 1; + sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; + }); + + if (transceiver.iceGatherer.state === 'completed') { + sdp += 'a=end-of-candidates\r\n'; + } + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'offer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.createAnswer = function() { + var pc = this; + + if (this._isClosed) { + return Promise.reject(makeError('InvalidStateError', + 'Can not call createAnswer after close')); + } + + var sdp = SDPUtils.writeSessionBoilerplate(this._sdpSessionId, + this._sdpSessionVersion++); + if (this.usingBundle) { + sdp += 'a=group:BUNDLE ' + this.transceivers.map(function(t) { + return t.mid; + }).join(' ') + '\r\n'; + } + var mediaSectionsInOffer = SDPUtils.splitSections( + this.remoteDescription.sdp).length - 1; + this.transceivers.forEach(function(transceiver, sdpMLineIndex) { + if (sdpMLineIndex + 1 > mediaSectionsInOffer) { + return; + } + if (transceiver.isDatachannel) { + sdp += 'm=application 0 DTLS/SCTP 5000\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=mid:' + transceiver.mid + '\r\n'; + return; + } + + // FIXME: look at direction. + if (transceiver.stream) { + var localTrack; + if (transceiver.kind === 'audio') { + localTrack = transceiver.stream.getAudioTracks()[0]; + } else if (transceiver.kind === 'video') { + localTrack = transceiver.stream.getVideoTracks()[0]; + } + if (localTrack) { + // add RTX + if (edgeVersion >= 15019 && transceiver.kind === 'video' && + !transceiver.sendEncodingParameters[0].rtx) { + transceiver.sendEncodingParameters[0].rtx = { + ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 + }; + } + } + } + + // Calculate intersection of capabilities. + var commonCapabilities = getCommonCapabilities( + transceiver.localCapabilities, + transceiver.remoteCapabilities); + + var hasRtx = commonCapabilities.codecs.filter(function(c) { + return c.name.toLowerCase() === 'rtx'; + }).length; + if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { + delete transceiver.sendEncodingParameters[0].rtx; + } + + sdp += writeMediaSection(transceiver, commonCapabilities, + 'answer', transceiver.stream, pc._dtlsRole); + if (transceiver.rtcpParameters && + transceiver.rtcpParameters.reducedSize) { + sdp += 'a=rtcp-rsize\r\n'; + } + }); + + var desc = new window.RTCSessionDescription({ + type: 'answer', + sdp: sdp + }); + return Promise.resolve(desc); + }; + + RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + var sections; + if (!candidate || candidate.candidate === '') { + for (var j = 0; j < this.transceivers.length; j++) { + if (this.transceivers[j].isDatachannel) { + continue; + } + this.transceivers[j].iceTransport.addRemoteCandidate({}); + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[j + 1] += 'a=end-of-candidates\r\n'; + this.remoteDescription.sdp = sections.join(''); + if (this.usingBundle) { + break; + } + } + } else if (!(candidate.sdpMLineIndex !== undefined || candidate.sdpMid)) { + throw new TypeError('sdpMLineIndex or sdpMid required'); + } else if (!this.remoteDescription) { + return Promise.reject(makeError('InvalidStateError', + 'Can not add ICE candidate without a remote description')); + } else { + var sdpMLineIndex = candidate.sdpMLineIndex; + if (candidate.sdpMid) { + for (var i = 0; i < this.transceivers.length; i++) { + if (this.transceivers[i].mid === candidate.sdpMid) { + sdpMLineIndex = i; + break; + } + } + } + var transceiver = this.transceivers[sdpMLineIndex]; + if (transceiver) { + if (transceiver.isDatachannel) { + return Promise.resolve(); + } + var cand = Object.keys(candidate.candidate).length > 0 ? + SDPUtils.parseCandidate(candidate.candidate) : {}; + // Ignore Chrome's invalid candidates since Edge does not like them. + if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { + return Promise.resolve(); + } + // Ignore RTCP candidates, we assume RTCP-MUX. + if (cand.component && cand.component !== 1) { + return Promise.resolve(); + } + // when using bundle, avoid adding candidates to the wrong + // ice transport. And avoid adding candidates added in the SDP. + if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && + transceiver.iceTransport !== this.transceivers[0].iceTransport)) { + if (!maybeAddCandidate(transceiver.iceTransport, cand)) { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + + // update the remoteDescription. + var candidateString = candidate.candidate.trim(); + if (candidateString.indexOf('a=') === 0) { + candidateString = candidateString.substr(2); + } + sections = SDPUtils.splitSections(this.remoteDescription.sdp); + sections[sdpMLineIndex + 1] += 'a=' + + (cand.type ? candidateString : 'end-of-candidates') + + '\r\n'; + this.remoteDescription.sdp = sections.join(''); + } else { + return Promise.reject(makeError('OperationError', + 'Can not add ICE candidate')); + } + } + return Promise.resolve(); + }; + + RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.transceivers.forEach(function(transceiver) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(method) { + if (transceiver[method]) { + promises.push(transceiver[method].getStats()); + } + }); + }); + var fixStatsType = function(stat) { + return { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[stat.type] || stat.type; + }; + return new Promise(function(resolve) { + // shim getStats with maplike support + var results = new Map(); + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + result[id].type = fixStatsType(result[id]); + results.set(id, result[id]); + }); + }); + resolve(results); + }); + }); + }; + + // legacy callback shims. Should be moved to adapter.js some days. + var methods = ['createOffer', 'createAnswer']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[0] === 'function' || + typeof args[1] === 'function') { // legacy + return nativeMethod.apply(this, [arguments[2]]) + .then(function(description) { + if (typeof args[0] === 'function') { + args[0].apply(null, [description]); + } + }, function(error) { + if (typeof args[1] === 'function') { + args[1].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; + methods.forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function' || + typeof args[2] === 'function') { // legacy + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }, function(error) { + if (typeof args[2] === 'function') { + args[2].apply(null, [error]); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + // getStats is special. It doesn't have a spec legacy method yet we support + // getStats(something, cb) without error callbacks. + ['getStats'].forEach(function(method) { + var nativeMethod = RTCPeerConnection.prototype[method]; + RTCPeerConnection.prototype[method] = function() { + var args = arguments; + if (typeof args[1] === 'function') { + return nativeMethod.apply(this, arguments) + .then(function() { + if (typeof args[1] === 'function') { + args[1].apply(null); + } + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + + return RTCPeerConnection; +}; + +},{"sdp":2}],2:[function(require,module,exports){ + /* eslint-env node */ +'use strict'; + +// SDP helpers. +var SDPUtils = {}; + +// Generate an alphanumeric identifier for cname or mids. +// TODO: use UUIDs instead? https://gist.github.com/jed/982883 +SDPUtils.generateIdentifier = function() { + return Math.random().toString(36).substr(2, 10); +}; + +// The RTCP CNAME used by all peerconnections from the same JS. +SDPUtils.localCName = SDPUtils.generateIdentifier(); + +// Splits SDP into lines, dealing with both CRLF and LF. +SDPUtils.splitLines = function(blob) { + return blob.trim().split('\n').map(function(line) { + return line.trim(); + }); +}; +// Splits SDP into sessionpart and mediasections. Ensures CRLF. +SDPUtils.splitSections = function(blob) { + var parts = blob.split('\nm='); + return parts.map(function(part, index) { + return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; + }); +}; + +// Returns lines that start with a certain prefix. +SDPUtils.matchPrefix = function(blob, prefix) { + return SDPUtils.splitLines(blob).filter(function(line) { + return line.indexOf(prefix) === 0; + }); +}; + +// Parses an ICE candidate line. Sample input: +// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 +// rport 55996" +SDPUtils.parseCandidate = function(line) { + var parts; + // Parse both variants. + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parseInt(parts[1], 10), + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + }; + + for (var i = 8; i < parts.length; i += 2) { + switch (parts[i]) { + case 'raddr': + candidate.relatedAddress = parts[i + 1]; + break; + case 'rport': + candidate.relatedPort = parseInt(parts[i + 1], 10); + break; + case 'tcptype': + candidate.tcpType = parts[i + 1]; + break; + case 'ufrag': + candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.usernameFragment = parts[i + 1]; + break; + default: // extension handling, in particular ufrag + candidate[parts[i]] = parts[i + 1]; + break; + } + } + return candidate; +}; + +// Translates a candidate object into SDP candidate attribute. +SDPUtils.writeCandidate = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type !== 'host' && candidate.relatedAddress && + candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + if (candidate.ufrag) { + sdp.push('ufrag'); + sdp.push(candidate.ufrag); + } + return 'candidate:' + sdp.join(' '); +}; + +// Parses an ice-options line, returns an array of option tags. +// a=ice-options:foo bar +SDPUtils.parseIceOptions = function(line) { + return line.substr(14).split(' '); +} + +// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// a=rtpmap:111 opus/48000/2 +SDPUtils.parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + // was: channels + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; + return parsed; +}; + +// Generate an a=rtpmap line from RTCRtpCodecCapability or +// RTCRtpCodecParameters. +SDPUtils.writeRtpMap = function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n'; +}; + +// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset +// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset +SDPUtils.parseExtmap = function(line) { + var parts = line.substr(9).split(' '); + return { + id: parseInt(parts[0], 10), + direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', + uri: parts[1] + }; +}; + +// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// RTCRtpHeaderExtension. +SDPUtils.writeExtmap = function(headerExtension) { + return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + + (headerExtension.direction && headerExtension.direction !== 'sendrecv' + ? '/' + headerExtension.direction + : '') + + ' ' + headerExtension.uri + '\r\n'; +}; + +// Parses an ftmp line, returns dictionary. Sample input: +// a=fmtp:96 vbr=on;cng=on +// Also deals with vbr=on; cng=on +SDPUtils.parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(line.indexOf(' ') + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].trim().split('='); + parsed[kv[0].trim()] = kv[1]; + } + return parsed; +}; + +// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function(codec) { + var line = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.parameters && Object.keys(codec.parameters).length) { + var params = []; + Object.keys(codec.parameters).forEach(function(param) { + params.push(param + '=' + codec.parameters[param]); + }); + line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; + } + return line; +}; + +// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// a=rtcp-fb:98 nack rpsi +SDPUtils.parseRtcpFb = function(line) { + var parts = line.substr(line.indexOf(' ') + 1).split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; +}; +// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeRtcpFb = function(codec) { + var lines = ''; + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + if (codec.rtcpFeedback && codec.rtcpFeedback.length) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + + '\r\n'; + }); + } + return lines; +}; + +// Parses an RFC 5576 ssrc media attribute. Sample input: +// a=ssrc:3735928559 cname:something +SDPUtils.parseSsrcMedia = function(line) { + var sp = line.indexOf(' '); + var parts = { + ssrc: parseInt(line.substr(7, sp - 7), 10) + }; + var colon = line.indexOf(':', sp); + if (colon > -1) { + parts.attribute = line.substr(sp + 1, colon - sp - 1); + parts.value = line.substr(colon + 1); + } else { + parts.attribute = line.substr(sp + 1); + } + return parts; +}; + +// Extracts the MID (RFC 5888) from a media section. +// returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function(mediaSection) { + var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; + if (mid) { + return mid.substr(6); + } +} + +SDPUtils.parseFingerprint = function(line) { + var parts = line.substr(14).split(' '); + return { + algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. + value: parts[1] + }; +}; + +// Extracts DTLS parameters from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the fingerprint line as input. See also getIceParameters. +SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, + 'a=fingerprint:'); + // Note: a=setup line is ignored since we use the 'auto' role. + // Note2: 'algorithm' is not case sensitive except in Edge. + return { + role: 'auto', + fingerprints: lines.map(SDPUtils.parseFingerprint) + }; +}; + +// Serializes DTLS parameters to SDP. +SDPUtils.writeDtlsParameters = function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; +}; +// Parses ICE information from SDP media section or sessionpart. +// FIXME: for consistency with other functions this should only +// get the ice-ufrag and ice-pwd lines as input. +SDPUtils.getIceParameters = function(mediaSection, sessionpart) { + var lines = SDPUtils.splitLines(mediaSection); + // Search in session part, too. + lines = lines.concat(SDPUtils.splitLines(sessionpart)); + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10) + }; + return iceParameters; +}; + +// Serializes ICE parameters to SDP. +SDPUtils.writeIceParameters = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; +}; + +// Parses the SDP media section and returns RTCRtpParameters. +SDPUtils.parseRtpParameters = function(mediaSection) { + var description = { + codecs: [], + headerExtensions: [], + fecMechanisms: [], + rtcp: [] + }; + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var pt = mline[i]; + var rtpmapline = SDPUtils.matchPrefix( + mediaSection, 'a=rtpmap:' + pt + ' ')[0]; + if (rtpmapline) { + var codec = SDPUtils.parseRtpMap(rtpmapline); + var fmtps = SDPUtils.matchPrefix( + mediaSection, 'a=fmtp:' + pt + ' '); + // Only the first a=fmtp: is considered. + codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; + codec.rtcpFeedback = SDPUtils.matchPrefix( + mediaSection, 'a=rtcp-fb:' + pt + ' ') + .map(SDPUtils.parseRtcpFb); + description.codecs.push(codec); + // parse FEC mechanisms from rtpmap lines. + switch (codec.name.toUpperCase()) { + case 'RED': + case 'ULPFEC': + description.fecMechanisms.push(codec.name.toUpperCase()); + break; + default: // only RED and ULPFEC are recognized as FEC mechanisms. + break; + } + } + } + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + description.headerExtensions.push(SDPUtils.parseExtmap(line)); + }); + // FIXME: parse rtcp. + return description; +}; + +// Generates parts of the SDP media section describing the capabilities / +// parameters. +SDPUtils.writeRtpDescription = function(kind, caps) { + var sdp = ''; + + // Build the mline. + sdp += 'm=' + kind + ' '; + sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. + sdp += ' UDP/TLS/RTP/SAVPF '; + sdp += caps.codecs.map(function(codec) { + if (codec.preferredPayloadType !== undefined) { + return codec.preferredPayloadType; + } + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + caps.codecs.forEach(function(codec) { + sdp += SDPUtils.writeRtpMap(codec); + sdp += SDPUtils.writeFmtp(codec); + sdp += SDPUtils.writeRtcpFb(codec); + }); + var maxptime = 0; + caps.codecs.forEach(function(codec) { + if (codec.maxptime > maxptime) { + maxptime = codec.maxptime; + } + }); + if (maxptime > 0) { + sdp += 'a=maxptime:' + maxptime + '\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + caps.headerExtensions.forEach(function(extension) { + sdp += SDPUtils.writeExtmap(extension); + }); + // FIXME: write fecMechanisms. + return sdp; +}; + +// Parses the SDP media section and returns an array of +// RTCRtpEncodingParameters. +SDPUtils.parseRtpEncodingParameters = function(mediaSection) { + var encodingParameters = []; + var description = SDPUtils.parseRtpParameters(mediaSection); + var hasRed = description.fecMechanisms.indexOf('RED') !== -1; + var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; + + // filter a=ssrc:... cname:, ignore PlanB-msid + var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'cname'; + }); + var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + var secondarySsrc; + + var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') + .map(function(line) { + var parts = line.split(' '); + parts.shift(); + return parts.map(function(part) { + return parseInt(part, 10); + }); + }); + if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { + secondarySsrc = flows[0][1]; + } + + description.codecs.forEach(function(codec) { + if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { + var encParam = { + ssrc: primarySsrc, + codecPayloadType: parseInt(codec.parameters.apt, 10), + rtx: { + ssrc: secondarySsrc + } + }; + encodingParameters.push(encParam); + if (hasRed) { + encParam = JSON.parse(JSON.stringify(encParam)); + encParam.fec = { + ssrc: secondarySsrc, + mechanism: hasUlpfec ? 'red+ulpfec' : 'red' + }; + encodingParameters.push(encParam); + } + } + }); + if (encodingParameters.length === 0 && primarySsrc) { + encodingParameters.push({ + ssrc: primarySsrc + }); + } + + // we support both b=AS and b=TIAS but interpret AS as TIAS. + var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + if (bandwidth.length) { + if (bandwidth[0].indexOf('b=TIAS:') === 0) { + bandwidth = parseInt(bandwidth[0].substr(7), 10); + } else if (bandwidth[0].indexOf('b=AS:') === 0) { + // use formula from JSEP to convert b=AS to TIAS value. + bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 + - (50 * 40 * 8); + } else { + bandwidth = undefined; + } + encodingParameters.forEach(function(params) { + params.maxBitrate = bandwidth; + }); + } + return encodingParameters; +}; + +// parses http://draft.ortc.org/#rtcrtcpparameters* +SDPUtils.parseRtcpParameters = function(mediaSection) { + var rtcpParameters = {}; + + var cname; + // Gets the first SSRC. Note that with RTX there might be multiple + // SSRCs. + var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(obj) { + return obj.attribute === 'cname'; + })[0]; + if (remoteSsrc) { + rtcpParameters.cname = remoteSsrc.value; + rtcpParameters.ssrc = remoteSsrc.ssrc; + } + + // Edge uses the compound attribute instead of reducedSize + // compound is !reducedSize + var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + rtcpParameters.reducedSize = rsize.length > 0; + rtcpParameters.compound = rsize.length === 0; + + // parses the rtcp-mux attrіbute. + // Note that Edge does not support unmuxed RTCP. + var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); + rtcpParameters.mux = mux.length > 0; + + return rtcpParameters; +}; + +// parses either a=msid: or a=ssrc:... msid lines and returns +// the id of the MediaStream and MediaStreamTrack. +SDPUtils.parseMsid = function(mediaSection) { + var parts; + var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); + if (spec.length === 1) { + parts = spec[0].substr(7).split(' '); + return {stream: parts[0], track: parts[1]}; + } + var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') + .map(function(line) { + return SDPUtils.parseSsrcMedia(line); + }) + .filter(function(parts) { + return parts.attribute === 'msid'; + }); + if (planB.length > 0) { + parts = planB[0].value.split(' '); + return {stream: parts[0], track: parts[1]}; + } +}; + +// Generate a session ID for SDP. +// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 +// recommends using a cryptographically random +ve 64-bit value +// but right now this should be acceptable and within the right range +SDPUtils.generateSessionId = function() { + return Math.random().toString().substr(2, 21); +}; + +// Write boilder plate for start of SDP +// sessId argument is optional - if not supplied it will +// be generated randomly +// sessVersion is optional and defaults to 2 +SDPUtils.writeSessionBoilerplate = function(sessId, sessVer) { + var sessionId; + var version = sessVer !== undefined ? sessVer : 2; + if (sessId) { + sessionId = sessId; + } else { + sessionId = SDPUtils.generateSessionId(); + } + // FIXME: sess-id should be an NTP timestamp. + return 'v=0\r\n' + + 'o=thisisadapterortc ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; +}; + +SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { + var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += SDPUtils.writeIceParameters( + transceiver.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += SDPUtils.writeDtlsParameters( + transceiver.dtlsTransport.getLocalParameters(), + type === 'offer' ? 'actpass' : 'active'); + + sdp += 'a=mid:' + transceiver.mid + '\r\n'; + + if (transceiver.direction) { + sdp += 'a=' + transceiver.direction + '\r\n'; + } else if (transceiver.rtpSender && transceiver.rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (transceiver.rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (transceiver.rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + + if (transceiver.rtpSender) { + // spec. + var msid = 'msid:' + stream.id + ' ' + + transceiver.rtpSender.track.id + '\r\n'; + sdp += 'a=' + msid; + + // for Chrome. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' ' + msid; + if (transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' ' + msid; + sdp += 'a=ssrc-group:FID ' + + transceiver.sendEncodingParameters[0].ssrc + ' ' + + transceiver.sendEncodingParameters[0].rtx.ssrc + + '\r\n'; + } + } + // FIXME: this should be written by writeRtpDescription. + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { + sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + + ' cname:' + SDPUtils.localCName + '\r\n'; + } + return sdp; +}; + +// Gets the direction from the mediaSection or the sessionpart. +SDPUtils.getDirection = function(mediaSection, sessionpart) { + // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. + var lines = SDPUtils.splitLines(mediaSection); + for (var i = 0; i < lines.length; i++) { + switch (lines[i]) { + case 'a=sendrecv': + case 'a=sendonly': + case 'a=recvonly': + case 'a=inactive': + return lines[i].substr(2); + default: + // FIXME: What should happen here? + } + } + if (sessionpart) { + return SDPUtils.getDirection(sessionpart); + } + return 'sendrecv'; +}; + +SDPUtils.getKind = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return mline[0].substr(2); +}; + +SDPUtils.isRejected = function(mediaSection) { + return mediaSection.split(' ', 2)[1] === '0'; +}; + +SDPUtils.parseMLine = function(mediaSection) { + var lines = SDPUtils.splitLines(mediaSection); + var mline = lines[0].split(' '); + return { + kind: mline[0].substr(2), + port: parseInt(mline[1], 10), + protocol: mline[2], + fmt: mline.slice(3).join(' ') + }; +}; + +// Expose public methods. +if (typeof module === 'object') { + module.exports = SDPUtils; +} + +},{}],3:[function(require,module,exports){ +(function (global){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var adapterFactory = require('./adapter_factory.js'); +module.exports = adapterFactory({window: global.window}); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./adapter_factory.js":4}],4:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ + +'use strict'; + +var utils = require('./utils'); +// Shimming starts here. +module.exports = function(dependencies, opts) { + var window = dependencies && dependencies.window; + + var options = { + shimChrome: true, + shimFirefox: true, + shimEdge: true, + shimSafari: true, + }; + + for (var key in opts) { + if (hasOwnProperty.call(opts, key)) { + options[key] = opts[key]; + } + } + + // Utils. + var logging = utils.log; + var browserDetails = utils.detectBrowser(window); + + // Uncomment the line below if you want logging to occur, including logging + // for the switch statement below. Can also be turned on in the browser via + // adapter.disableLog(false), but then logging from the switch statement below + // will not appear. + // require('./utils').disableLog(false); + + // Browser shims. + var chromeShim = require('./chrome/chrome_shim') || null; + var edgeShim = require('./edge/edge_shim') || null; + var firefoxShim = require('./firefox/firefox_shim') || null; + var safariShim = require('./safari/safari_shim') || null; + var commonShim = require('./common_shim') || null; + + // Export to the adapter global object visible in the browser. + var adapter = { + browserDetails: browserDetails, + commonShim: commonShim, + extractVersion: utils.extractVersion, + disableLog: utils.disableLog, + disableWarnings: utils.disableWarnings + }; + + // Shim browser if found. + switch (browserDetails.browser) { + case 'chrome': + if (!chromeShim || !chromeShim.shimPeerConnection || + !options.shimChrome) { + logging('Chrome shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming chrome.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = chromeShim; + commonShim.shimCreateObjectURL(window); + + chromeShim.shimGetUserMedia(window); + chromeShim.shimMediaStream(window); + chromeShim.shimSourceObject(window); + chromeShim.shimPeerConnection(window); + chromeShim.shimOnTrack(window); + chromeShim.shimAddTrackRemoveTrack(window); + chromeShim.shimGetSendersWithDtmf(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'firefox': + if (!firefoxShim || !firefoxShim.shimPeerConnection || + !options.shimFirefox) { + logging('Firefox shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming firefox.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = firefoxShim; + commonShim.shimCreateObjectURL(window); + + firefoxShim.shimGetUserMedia(window); + firefoxShim.shimSourceObject(window); + firefoxShim.shimPeerConnection(window); + firefoxShim.shimOnTrack(window); + firefoxShim.shimRemoveStream(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'edge': + if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { + logging('MS edge shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming edge.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = edgeShim; + commonShim.shimCreateObjectURL(window); + + edgeShim.shimGetUserMedia(window); + edgeShim.shimPeerConnection(window); + edgeShim.shimReplaceTrack(window); + + // the edge shim implements the full RTCIceCandidate object. + + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + case 'safari': + if (!safariShim || !options.shimSafari) { + logging('Safari shim is not included in this adapter release.'); + return adapter; + } + logging('adapter.js shimming safari.'); + // Export to the adapter global object visible in the browser. + adapter.browserShim = safariShim; + commonShim.shimCreateObjectURL(window); + + safariShim.shimRTCIceServerUrls(window); + safariShim.shimCallbacksAPI(window); + safariShim.shimLocalStreamsAPI(window); + safariShim.shimRemoteStreamsAPI(window); + safariShim.shimTrackEventTransceiver(window); + safariShim.shimGetUserMedia(window); + safariShim.shimCreateOfferLegacy(window); + + commonShim.shimRTCIceCandidate(window); + commonShim.shimMaxMessageSize(window); + commonShim.shimSendThrowTypeError(window); + break; + default: + logging('Unsupported browser!'); + break; + } + + return adapter; +}; + +},{"./chrome/chrome_shim":5,"./common_shim":7,"./edge/edge_shim":8,"./firefox/firefox_shim":10,"./safari/safari_shim":12,"./utils":13}],5:[function(require,module,exports){ + +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimMediaStream: function(window) { + window.MediaStream = window.MediaStream || window.webkitMediaStream; + }, + + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + } + this.addEventListener('track', this._ontrack = f); + } + }); + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + if (!pc._ontrackpoly) { + pc._ontrackpoly = function(e) { + // onaddstream does not fire when a track is added to an existing + // stream. But stream.onaddtrack is implemented so we use that. + e.stream.addEventListener('addtrack', function(te) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === te.track.id; + }); + } else { + receiver = {track: te.track}; + } + + var event = new Event('track'); + event.track = te.track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + e.stream.getTracks().forEach(function(track) { + var receiver; + if (window.RTCPeerConnection.prototype.getReceivers) { + receiver = pc.getReceivers().find(function(r) { + return r.track && r.track.id === track.id; + }); + } else { + receiver = {track: track}; + } + var event = new Event('track'); + event.track = track; + event.receiver = receiver; + event.transceiver = {receiver: receiver}; + event.streams = [e.stream]; + pc.dispatchEvent(event); + }); + }; + pc.addEventListener('addstream', pc._ontrackpoly); + } + return origSetRemoteDescription.apply(pc, arguments); + }; + } else if (!('RTCRtpTransceiver' in window)) { + utils.wrapPeerConnectionEvent(window, 'track', function(e) { + if (!e.transceiver) { + e.transceiver = {receiver: e.receiver}; + } + return e; + }); + } + }, + + shimGetSendersWithDtmf: function(window) { + // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. + if (typeof window === 'object' && window.RTCPeerConnection && + !('getSenders' in window.RTCPeerConnection.prototype) && + 'createDTMFSender' in window.RTCPeerConnection.prototype) { + var shimSenderWithDtmf = function(pc, track) { + return { + track: track, + get dtmf() { + if (this._dtmf === undefined) { + if (track.kind === 'audio') { + this._dtmf = pc.createDTMFSender(track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + }, + _pc: pc + }; + }; + + // augment addTrack when getSenders is not available. + if (!window.RTCPeerConnection.prototype.getSenders) { + window.RTCPeerConnection.prototype.getSenders = function() { + this._senders = this._senders || []; + return this._senders.slice(); // return a copy of the internal state. + }; + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + var sender = origAddTrack.apply(pc, arguments); + if (!sender) { + sender = shimSenderWithDtmf(pc, track); + pc._senders.push(sender); + } + return sender; + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + origRemoveTrack.apply(pc, arguments); + var idx = pc._senders.indexOf(sender); + if (idx !== -1) { + pc._senders.splice(idx, 1); + } + }; + } + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origAddStream.apply(pc, [stream]); + stream.getTracks().forEach(function(track) { + pc._senders.push(shimSenderWithDtmf(pc, track)); + }); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._senders = pc._senders || []; + origRemoveStream.apply(pc, [stream]); + + stream.getTracks().forEach(function(track) { + var sender = pc._senders.find(function(s) { + return s.track === track; + }); + if (sender) { + pc._senders.splice(pc._senders.indexOf(sender), 1); // remove sender + } + }); + }; + } else if (typeof window === 'object' && window.RTCPeerConnection && + 'getSenders' in window.RTCPeerConnection.prototype && + 'createDTMFSender' in window.RTCPeerConnection.prototype && + window.RTCRtpSender && + !('dtmf' in window.RTCRtpSender.prototype)) { + var origGetSenders = window.RTCPeerConnection.prototype.getSenders; + window.RTCPeerConnection.prototype.getSenders = function() { + var pc = this; + var senders = origGetSenders.apply(pc, []); + senders.forEach(function(sender) { + sender._pc = pc; + }); + return senders; + }; + + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = this._pc.createDTMFSender(this.track); + } else { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + }, + + shimSourceObject: function(window) { + var URL = window && window.URL; + + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this._srcObject; + }, + set: function(stream) { + var self = this; + // Use _srcObject as a private property for this shim + this._srcObject = stream; + if (this.src) { + URL.revokeObjectURL(this.src); + } + + if (!stream) { + this.src = ''; + return undefined; + } + this.src = URL.createObjectURL(stream); + // We need to recreate the blob url when a track is added or + // removed. Doing it manually since we want to avoid a recursion. + stream.addEventListener('addtrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + stream.addEventListener('removetrack', function() { + if (self.src) { + URL.revokeObjectURL(self.src); + } + self.src = URL.createObjectURL(stream); + }); + } + }); + } + } + }, + + shimAddTrackRemoveTrackWithNative: function(window) { + // shim addTrack/removeTrack with native variants in order to make + // the interactions with legacy getLocalStreams behave as in other browsers. + // Keeps a mapping stream.id => [stream, rtpsenders...] + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + return Object.keys(this._shimmedLocalStreams).map(function(streamId) { + return pc._shimmedLocalStreams[streamId][0]; + }); + }; + + var origAddTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (!stream) { + return origAddTrack.apply(this, arguments); + } + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + var sender = origAddTrack.apply(this, arguments); + if (!this._shimmedLocalStreams[stream.id]) { + this._shimmedLocalStreams[stream.id] = [stream, sender]; + } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { + this._shimmedLocalStreams[stream.id].push(sender); + } + return sender; + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + var existingSenders = pc.getSenders(); + origAddStream.apply(this, arguments); + var newSenders = pc.getSenders().filter(function(newSender) { + return existingSenders.indexOf(newSender) === -1; + }); + this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + delete this._shimmedLocalStreams[stream.id]; + return origRemoveStream.apply(this, arguments); + }; + + var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + this._shimmedLocalStreams = this._shimmedLocalStreams || {}; + if (sender) { + Object.keys(this._shimmedLocalStreams).forEach(function(streamId) { + var idx = pc._shimmedLocalStreams[streamId].indexOf(sender); + if (idx !== -1) { + pc._shimmedLocalStreams[streamId].splice(idx, 1); + } + if (pc._shimmedLocalStreams[streamId].length === 1) { + delete pc._shimmedLocalStreams[streamId]; + } + }); + } + return origRemoveTrack.apply(this, arguments); + }; + }, + + shimAddTrackRemoveTrack: function(window) { + var browserDetails = utils.detectBrowser(window); + // shim addTrack and removeTrack. + if (window.RTCPeerConnection.prototype.addTrack && + browserDetails.version >= 65) { + return this.shimAddTrackRemoveTrackWithNative(window); + } + + // also shim pc.getLocalStreams when addTrack is shimmed + // to return the original streams. + var origGetLocalStreams = window.RTCPeerConnection.prototype + .getLocalStreams; + window.RTCPeerConnection.prototype.getLocalStreams = function() { + var pc = this; + var nativeStreams = origGetLocalStreams.apply(this); + pc._reverseStreams = pc._reverseStreams || {}; + return nativeStreams.map(function(stream) { + return pc._reverseStreams[stream.id]; + }); + }; + + var origAddStream = window.RTCPeerConnection.prototype.addStream; + window.RTCPeerConnection.prototype.addStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + stream.getTracks().forEach(function(track) { + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + }); + // Add identity mapping for consistency with addTrack. + // Unless this is being used with a stream from addTrack. + if (!pc._reverseStreams[stream.id]) { + var newStream = new window.MediaStream(stream.getTracks()); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + stream = newStream; + } + origAddStream.apply(pc, [stream]); + }; + + var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + + origRemoveStream.apply(pc, [(pc._streams[stream.id] || stream)]); + delete pc._reverseStreams[(pc._streams[stream.id] ? + pc._streams[stream.id].id : stream.id)]; + delete pc._streams[stream.id]; + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + var streams = [].slice.call(arguments, 1); + if (streams.length !== 1 || + !streams[0].getTracks().find(function(t) { + return t === track; + })) { + // this is not fully correct but all we can manage without + // [[associated MediaStreams]] internal slot. + throw new DOMException( + 'The adapter.js addTrack polyfill only supports a single ' + + ' stream which is associated with the specified track.', + 'NotSupportedError'); + } + + var alreadyExists = pc.getSenders().find(function(s) { + return s.track === track; + }); + if (alreadyExists) { + throw new DOMException('Track already exists.', + 'InvalidAccessError'); + } + + pc._streams = pc._streams || {}; + pc._reverseStreams = pc._reverseStreams || {}; + var oldStream = pc._streams[stream.id]; + if (oldStream) { + // this is using odd Chrome behaviour, use with caution: + // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 + // Note: we rely on the high-level addTrack/dtmf shim to + // create the sender with a dtmf sender. + oldStream.addTrack(track); + + // Trigger ONN async. + Promise.resolve().then(function() { + pc.dispatchEvent(new Event('negotiationneeded')); + }); + } else { + var newStream = new window.MediaStream([track]); + pc._streams[stream.id] = newStream; + pc._reverseStreams[newStream.id] = stream; + pc.addStream(newStream); + } + return pc.getSenders().find(function(s) { + return s.track === track; + }); + }; + + // replace the internal stream id with the external one and + // vice versa. + function replaceInternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(internalStream.id, 'g'), + externalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + function replaceExternalStreamId(pc, description) { + var sdp = description.sdp; + Object.keys(pc._reverseStreams || []).forEach(function(internalId) { + var externalStream = pc._reverseStreams[internalId]; + var internalStream = pc._streams[externalStream.id]; + sdp = sdp.replace(new RegExp(externalStream.id, 'g'), + internalStream.id); + }); + return new RTCSessionDescription({ + type: description.type, + sdp: sdp + }); + } + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + var args = arguments; + var isLegacyCall = arguments.length && + typeof arguments[0] === 'function'; + if (isLegacyCall) { + return nativeMethod.apply(pc, [ + function(description) { + var desc = replaceInternalStreamId(pc, description); + args[0].apply(null, [desc]); + }, + function(err) { + if (args[1]) { + args[1].apply(null, err); + } + }, arguments[2] + ]); + } + return nativeMethod.apply(pc, arguments) + .then(function(description) { + return replaceInternalStreamId(pc, description); + }); + }; + }); + + var origSetLocalDescription = + window.RTCPeerConnection.prototype.setLocalDescription; + window.RTCPeerConnection.prototype.setLocalDescription = function() { + var pc = this; + if (!arguments.length || !arguments[0].type) { + return origSetLocalDescription.apply(pc, arguments); + } + arguments[0] = replaceExternalStreamId(pc, arguments[0]); + return origSetLocalDescription.apply(pc, arguments); + }; + + // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier + + var origLocalDescription = Object.getOwnPropertyDescriptor( + window.RTCPeerConnection.prototype, 'localDescription'); + Object.defineProperty(window.RTCPeerConnection.prototype, + 'localDescription', { + get: function() { + var pc = this; + var description = origLocalDescription.get.apply(this); + if (description.type === '') { + return description; + } + return replaceInternalStreamId(pc, description); + } + }); + + window.RTCPeerConnection.prototype.removeTrack = function(sender) { + var pc = this; + if (pc.signalingState === 'closed') { + throw new DOMException( + 'The RTCPeerConnection\'s signalingState is \'closed\'.', + 'InvalidStateError'); + } + // We can not yet check for sender instanceof RTCRtpSender + // since we shim RTPSender. So we check if sender._pc is set. + if (!sender._pc) { + throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + + 'does not implement interface RTCRtpSender.', 'TypeError'); + } + var isLocal = sender._pc === pc; + if (!isLocal) { + throw new DOMException('Sender was not created by this connection.', + 'InvalidAccessError'); + } + + // Search for the native stream the senders track belongs to. + pc._streams = pc._streams || {}; + var stream; + Object.keys(pc._streams).forEach(function(streamid) { + var hasTrack = pc._streams[streamid].getTracks().find(function(track) { + return sender.track === track; + }); + if (hasTrack) { + stream = pc._streams[streamid]; + } + }); + + if (stream) { + if (stream.getTracks().length === 1) { + // if this is the last track of the stream, remove the stream. This + // takes care of any shimmed _senders. + pc.removeStream(pc._reverseStreams[stream.id]); + } else { + // relying on the same odd chrome behaviour as above. + stream.removeTrack(sender.track); + } + pc.dispatchEvent(new Event('negotiationneeded')); + } + }; + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + // The RTCPeerConnection object. + if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + // this was fixed in M56 along with unprefixing RTCPeerConnection. + logging('PeerConnection'); + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + return new window.webkitRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.webkitRTCPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if (window.webkitRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.webkitRTCPeerConnection.generateCertificate; + } + }); + } + } else { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + + var origGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function(selector, + successCallback, errorCallback) { + var pc = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats.apply(this, arguments); + } + + // When spec-style getStats is supported, return those when called with + // either no arguments or the selector argument is null. + if (origGetStats.length === 0 && (arguments.length === 0 || + typeof arguments[0] !== 'function')) { + return origGetStats.apply(this, []); + } + + var fixChromeStats_ = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: { + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }[report.type] || report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + return new Map(Object.keys(stats).map(function(key) { + return [key, stats[key]]; + })); + }; + + if (arguments.length >= 2) { + var successCallbackWrapper_ = function(response) { + args[1](makeMapStats(fixChromeStats_(response))); + }; + + return origGetStats.apply(this, [successCallbackWrapper_, + arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + origGetStats.apply(pc, [ + function(response) { + resolve(makeMapStats(fixChromeStats_(response))); + }, reject]); + }).then(successCallback, errorCallback); + }; + + // add promise support -- natively available in Chrome 51 + if (browserDetails.version < 51) { + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var args = arguments; + var pc = this; + var promise = new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [args[0], resolve, reject]); + }); + if (args.length < 2) { + return promise; + } + return promise.then(function() { + args[1].apply(null, []); + }, + function(err) { + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }); + }; + }); + } + + // promise support for createOffer and createAnswer. Available (without + // bugs) since M52: crbug/619289 + if (browserDetails.version < 52) { + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + var pc = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof arguments[0] === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(pc, [resolve, reject, opts]); + }); + } + return nativeMethod.apply(this, arguments); + }; + }); + } + + // shim implicit creation of RTCSessionDescription/RTCIceCandidate + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + } +}; + +},{"../utils.js":13,"./getusermedia":6}],6:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; +var utils = require('../utils.js'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + + var constraintsToChrome_ = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname_ = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname_('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname_('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname_('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname_(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + var shimConstraints_ = function(constraints, func) { + if (browserDetails.version >= 61) { + return func(constraints); + } + constraints = JSON.parse(JSON.stringify(constraints)); + if (constraints && typeof constraints.audio === 'object') { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + constraints = JSON.parse(JSON.stringify(constraints)); + remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); + remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); + constraints.audio = constraintsToChrome_(constraints.audio); + } + if (constraints && typeof constraints.video === 'object') { + // Shim facingMode for mobile & surface pro. + var face = constraints.video.facingMode; + face = face && ((typeof face === 'object') ? face : {ideal: face}); + var getSupportedFacingModeLies = browserDetails.version < 66; + + if ((face && (face.exact === 'user' || face.exact === 'environment' || + face.ideal === 'user' || face.ideal === 'environment')) && + !(navigator.mediaDevices.getSupportedConstraints && + navigator.mediaDevices.getSupportedConstraints().facingMode && + !getSupportedFacingModeLies)) { + delete constraints.video.facingMode; + var matches; + if (face.exact === 'environment' || face.ideal === 'environment') { + matches = ['back', 'rear']; + } else if (face.exact === 'user' || face.ideal === 'user') { + matches = ['front']; + } + if (matches) { + // Look for matches in label, or use last cam for back (typical). + return navigator.mediaDevices.enumerateDevices() + .then(function(devices) { + devices = devices.filter(function(d) { + return d.kind === 'videoinput'; + }); + var dev = devices.find(function(d) { + return matches.some(function(match) { + return d.label.toLowerCase().indexOf(match) !== -1; + }); + }); + if (!dev && devices.length && matches.indexOf('back') !== -1) { + dev = devices[devices.length - 1]; // more likely the back cam + } + if (dev) { + constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : + {ideal: dev.deviceId}; + } + constraints.video = constraintsToChrome_(constraints.video); + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }); + } + } + constraints.video = constraintsToChrome_(constraints.video); + } + logging('chrome: ' + JSON.stringify(constraints)); + return func(constraints); + }; + + var shimError_ = function(e) { + return { + name: { + PermissionDeniedError: 'NotAllowedError', + InvalidStateError: 'NotReadableError', + DevicesNotFoundError: 'NotFoundError', + ConstraintNotSatisfiedError: 'OverconstrainedError', + TrackStartError: 'NotReadableError', + MediaDeviceFailedDueToShutdown: 'NotReadableError', + MediaDeviceKillSwitchOn: 'NotReadableError' + }[e.name] || e.name, + message: e.message, + constraint: e.constraintName, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + var getUserMedia_ = function(constraints, onSuccess, onError) { + shimConstraints_(constraints, function(c) { + navigator.webkitGetUserMedia(c, onSuccess, function(e) { + if (onError) { + onError(shimError_(e)); + } + }); + }); + }; + + navigator.getUserMedia = getUserMedia_; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + navigator.getUserMedia(constraints, resolve, reject); + }); + }; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = { + getUserMedia: getUserMediaPromise_, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return window.MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }, + getSupportedConstraints: function() { + return { + deviceId: true, echoCancellation: true, facingMode: true, + frameRate: true, height: true, width: true + }; + } + }; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return getUserMediaPromise_(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(cs) { + return shimConstraints_(cs, function(c) { + return origGetUserMedia(c).then(function(stream) { + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('', 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + logging('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + logging('Dummy mediaDevices.removeEventListener called.'); + }; + } +}; + +},{"../utils.js":13}],7:[function(require,module,exports){ +/* + * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var SDPUtils = require('sdp'); +var utils = require('./utils'); + +module.exports = { + shimRTCIceCandidate: function(window) { + // foundation is arbitrarily chosen as an indicator for full support for + // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface + if (window.RTCIceCandidate && 'foundation' in + window.RTCIceCandidate.prototype) { + return; + } + + var NativeRTCIceCandidate = window.RTCIceCandidate; + window.RTCIceCandidate = function(args) { + // Remove the a= which shouldn't be part of the candidate string. + if (typeof args === 'object' && args.candidate && + args.candidate.indexOf('a=') === 0) { + args = JSON.parse(JSON.stringify(args)); + args.candidate = args.candidate.substr(2); + } + + // Augment the native candidate with the parsed fields. + var nativeCandidate = new NativeRTCIceCandidate(args); + var parsedCandidate = SDPUtils.parseCandidate(args.candidate); + var augmentedCandidate = Object.assign(nativeCandidate, + parsedCandidate); + + // Add a serializer that does not serialize the extra attributes. + augmentedCandidate.toJSON = function() { + return { + candidate: augmentedCandidate.candidate, + sdpMid: augmentedCandidate.sdpMid, + sdpMLineIndex: augmentedCandidate.sdpMLineIndex, + usernameFragment: augmentedCandidate.usernameFragment, + }; + }; + return augmentedCandidate; + }; + + // Hook up the augmented candidate in onicecandidate and + // addEventListener('icecandidate', ...) + utils.wrapPeerConnectionEvent(window, 'icecandidate', function(e) { + if (e.candidate) { + Object.defineProperty(e, 'candidate', { + value: new window.RTCIceCandidate(e.candidate), + writable: 'false' + }); + } + return e; + }); + }, + + // shimCreateObjectURL must be called before shimSourceObject to avoid loop. + + shimCreateObjectURL: function(window) { + var URL = window && window.URL; + + if (!(typeof window === 'object' && window.HTMLMediaElement && + 'srcObject' in window.HTMLMediaElement.prototype && + URL.createObjectURL && URL.revokeObjectURL)) { + // Only shim CreateObjectURL using srcObject if srcObject exists. + return undefined; + } + + var nativeCreateObjectURL = URL.createObjectURL.bind(URL); + var nativeRevokeObjectURL = URL.revokeObjectURL.bind(URL); + var streams = new Map(), newId = 0; + + URL.createObjectURL = function(stream) { + if ('getTracks' in stream) { + var url = 'polyblob:' + (++newId); + streams.set(url, stream); + utils.deprecated('URL.createObjectURL(stream)', + 'elem.srcObject = stream'); + return url; + } + return nativeCreateObjectURL(stream); + }; + URL.revokeObjectURL = function(url) { + nativeRevokeObjectURL(url); + streams.delete(url); + }; + + var dsc = Object.getOwnPropertyDescriptor(window.HTMLMediaElement.prototype, + 'src'); + Object.defineProperty(window.HTMLMediaElement.prototype, 'src', { + get: function() { + return dsc.get.apply(this); + }, + set: function(url) { + this.srcObject = streams.get(url) || null; + return dsc.set.apply(this, [url]); + } + }); + + var nativeSetAttribute = window.HTMLMediaElement.prototype.setAttribute; + window.HTMLMediaElement.prototype.setAttribute = function() { + if (arguments.length === 2 && + ('' + arguments[0]).toLowerCase() === 'src') { + this.srcObject = streams.get(arguments[1]) || null; + } + return nativeSetAttribute.apply(this, arguments); + }; + }, + + shimMaxMessageSize: function(window) { + if (window.RTCSctpTransport || !window.RTCPeerConnection) { + return; + } + var browserDetails = utils.detectBrowser(window); + + if (!('sctp' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { + get: function() { + return typeof this._sctp === 'undefined' ? null : this._sctp; + } + }); + } + + var sctpInDescription = function(description) { + var sections = SDPUtils.splitSections(description.sdp); + sections.shift(); + return sections.some(function(mediaSection) { + var mLine = SDPUtils.parseMLine(mediaSection); + return mLine && mLine.kind === 'application' + && mLine.protocol.indexOf('SCTP') !== -1; + }); + }; + + var getRemoteFirefoxVersion = function(description) { + // TODO: Is there a better solution for detecting Firefox? + var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); + if (match === null || match.length < 2) { + return -1; + } + var version = parseInt(match[1], 10); + // Test for NaN (yes, this is ugly) + return version !== version ? -1 : version; + }; + + var getCanSendMaxMessageSize = function(remoteIsFirefox) { + // Every implementation we know can send at least 64 KiB. + // Note: Although Chrome is technically able to send up to 256 KiB, the + // data does not reach the other peer reliably. + // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 + var canSendMaxMessageSize = 65536; + if (browserDetails.browser === 'firefox') { + if (browserDetails.version < 57) { + if (remoteIsFirefox === -1) { + // FF < 57 will send in 16 KiB chunks using the deprecated PPID + // fragmentation. + canSendMaxMessageSize = 16384; + } else { + // However, other FF (and RAWRTC) can reassemble PPID-fragmented + // messages. Thus, supporting ~2 GiB when sending. + canSendMaxMessageSize = 2147483637; + } + } else { + // Currently, all FF >= 57 will reset the remote maximum message size + // to the default value when a data channel is created at a later + // stage. :( + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + canSendMaxMessageSize = + browserDetails.version === 57 ? 65535 : 65536; + } + } + return canSendMaxMessageSize; + }; + + var getMaxMessageSize = function(description, remoteIsFirefox) { + // Note: 65536 bytes is the default value from the SDP spec. Also, + // every implementation we know supports receiving 65536 bytes. + var maxMessageSize = 65536; + + // FF 57 has a slightly incorrect default remote max message size, so + // we need to adjust it here to avoid a failure when sending. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 + if (browserDetails.browser === 'firefox' + && browserDetails.version === 57) { + maxMessageSize = 65535; + } + + var match = SDPUtils.matchPrefix(description.sdp, 'a=max-message-size:'); + if (match.length > 0) { + maxMessageSize = parseInt(match[0].substr(19), 10); + } else if (browserDetails.browser === 'firefox' && + remoteIsFirefox !== -1) { + // If the maximum message size is not present in the remote SDP and + // both local and remote are Firefox, the remote peer can receive + // ~2 GiB. + maxMessageSize = 2147483637; + } + return maxMessageSize; + }; + + var origSetRemoteDescription = + window.RTCPeerConnection.prototype.setRemoteDescription; + window.RTCPeerConnection.prototype.setRemoteDescription = function() { + var pc = this; + pc._sctp = null; + + if (sctpInDescription(arguments[0])) { + // Check if the remote is FF. + var isFirefox = getRemoteFirefoxVersion(arguments[0]); + + // Get the maximum message size the local peer is capable of sending + var canSendMMS = getCanSendMaxMessageSize(isFirefox); + + // Get the maximum message size of the remote peer. + var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); + + // Determine final maximum message size + var maxMessageSize; + if (canSendMMS === 0 && remoteMMS === 0) { + maxMessageSize = Number.POSITIVE_INFINITY; + } else if (canSendMMS === 0 || remoteMMS === 0) { + maxMessageSize = Math.max(canSendMMS, remoteMMS); + } else { + maxMessageSize = Math.min(canSendMMS, remoteMMS); + } + + // Create a dummy RTCSctpTransport object and the 'maxMessageSize' + // attribute. + var sctp = {}; + Object.defineProperty(sctp, 'maxMessageSize', { + get: function() { + return maxMessageSize; + } + }); + pc._sctp = sctp; + } + + return origSetRemoteDescription.apply(pc, arguments); + }; + }, + + shimSendThrowTypeError: function(window) { + // Note: Although Firefox >= 57 has a native implementation, the maximum + // message size can be reset for all data channels at a later stage. + // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 + + var origCreateDataChannel = + window.RTCPeerConnection.prototype.createDataChannel; + window.RTCPeerConnection.prototype.createDataChannel = function() { + var pc = this; + var dataChannel = origCreateDataChannel.apply(pc, arguments); + var origDataChannelSend = dataChannel.send; + + // Patch 'send' method + dataChannel.send = function() { + var dc = this; + var data = arguments[0]; + var length = data.length || data.size || data.byteLength; + if (length > pc.sctp.maxMessageSize) { + throw new DOMException('Message too large (can send a maximum of ' + + pc.sctp.maxMessageSize + ' bytes)', 'TypeError'); + } + return origDataChannelSend.apply(dc, arguments); + }; + + return dataChannel; + }; + } +}; + +},{"./utils":13,"sdp":2}],8:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var shimRTCPeerConnection = require('rtcpeerconnection-shim'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (window.RTCIceGatherer) { + // ORTC defines an RTCIceCandidate object but no constructor. + // Not implemented in Edge. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = function(args) { + return args; + }; + } + // ORTC does not have a session description object but + // other browsers (i.e. Chrome) that will support both PC and ORTC + // in the future might have this defined already. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = function(args) { + return args; + }; + } + // this adds an additional event listener to MediaStrackTrack that signals + // when a tracks enabled property was changed. Workaround for a bug in + // addStream, see below. No longer required in 15025+ + if (browserDetails.version < 15025) { + var origMSTEnabled = Object.getOwnPropertyDescriptor( + window.MediaStreamTrack.prototype, 'enabled'); + Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { + set: function(value) { + origMSTEnabled.set.call(this, value); + var ev = new Event('enabled'); + ev.enabled = value; + this.dispatchEvent(ev); + } + }); + } + } + + // ORTC defines the DTMF sender a bit different. + // https://github.com/w3c/ortc/issues/714 + if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { + get: function() { + if (this._dtmf === undefined) { + if (this.track.kind === 'audio') { + this._dtmf = new window.RTCDtmfSender(this); + } else if (this.track.kind === 'video') { + this._dtmf = null; + } + } + return this._dtmf; + } + }); + } + + window.RTCPeerConnection = + shimRTCPeerConnection(window, browserDetails.version); + }, + shimReplaceTrack: function(window) { + // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 + if (window.RTCRtpSender && + !('replaceTrack' in window.RTCRtpSender.prototype)) { + window.RTCRtpSender.prototype.replaceTrack = + window.RTCRtpSender.prototype.setTrack; + } + } +}; + +},{"../utils":13,"./getusermedia":9,"rtcpeerconnection-shim":1}],9:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +// Expose public methods. +module.exports = function(window) { + var navigator = window && window.navigator; + + var shimError_ = function(e) { + return { + name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, + message: e.message, + constraint: e.constraint, + toString: function() { + return this.name; + } + }; + }; + + // getUserMedia error shim. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).catch(function(e) { + return Promise.reject(shimError_(e)); + }); + }; +}; + +},{}],10:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); + +module.exports = { + shimGetUserMedia: require('./getusermedia'), + shimOnTrack: function(window) { + if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in + window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { + get: function() { + return this._ontrack; + }, + set: function(f) { + if (this._ontrack) { + this.removeEventListener('track', this._ontrack); + this.removeEventListener('addstream', this._ontrackpoly); + } + this.addEventListener('track', this._ontrack = f); + this.addEventListener('addstream', this._ontrackpoly = function(e) { + e.stream.getTracks().forEach(function(track) { + var event = new Event('track'); + event.track = track; + event.receiver = {track: track}; + event.transceiver = {receiver: event.receiver}; + event.streams = [e.stream]; + this.dispatchEvent(event); + }.bind(this)); + }.bind(this)); + } + }); + } + if (typeof window === 'object' && window.RTCTrackEvent && + ('receiver' in window.RTCTrackEvent.prototype) && + !('transceiver' in window.RTCTrackEvent.prototype)) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimSourceObject: function(window) { + // Firefox has supported mozSrcObject since FF22, unprefixed in 42. + if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + return this.mozSrcObject; + }, + set: function(stream) { + this.mozSrcObject = stream; + } + }); + } + } + }, + + shimPeerConnection: function(window) { + var browserDetails = utils.detectBrowser(window); + + if (typeof window !== 'object' || !(window.RTCPeerConnection || + window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + // The RTCPeerConnection object. + if (!window.RTCPeerConnection) { + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (browserDetails.version < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new window.mozRTCPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = + window.mozRTCPeerConnection.prototype; + + // wrap static methods. Currently just generateCertificate. + if (window.mozRTCPeerConnection.generateCertificate) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return window.mozRTCPeerConnection.generateCertificate; + } + }); + } + + window.RTCSessionDescription = window.mozRTCSessionDescription; + window.RTCIceCandidate = window.mozRTCIceCandidate; + } + + // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. + ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] + .forEach(function(method) { + var nativeMethod = window.RTCPeerConnection.prototype[method]; + window.RTCPeerConnection.prototype[method] = function() { + arguments[0] = new ((method === 'addIceCandidate') ? + window.RTCIceCandidate : + window.RTCSessionDescription)(arguments[0]); + return nativeMethod.apply(this, arguments); + }; + }); + + // support for addIceCandidate(null or undefined) + var nativeAddIceCandidate = + window.RTCPeerConnection.prototype.addIceCandidate; + window.RTCPeerConnection.prototype.addIceCandidate = function() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); + } + return Promise.resolve(); + } + return nativeAddIceCandidate.apply(this, arguments); + }; + + // shim getStats with maplike support + var makeMapStats = function(stats) { + var map = new Map(); + Object.keys(stats).forEach(function(key) { + map.set(key, stats[key]); + map[key] = stats[key]; + }); + return map; + }; + + var modernStatsTypes = { + inboundrtp: 'inbound-rtp', + outboundrtp: 'outbound-rtp', + candidatepair: 'candidate-pair', + localcandidate: 'local-candidate', + remotecandidate: 'remote-candidate' + }; + + var nativeGetStats = window.RTCPeerConnection.prototype.getStats; + window.RTCPeerConnection.prototype.getStats = function( + selector, + onSucc, + onErr + ) { + return nativeGetStats.apply(this, [selector || null]) + .then(function(stats) { + if (browserDetails.version < 48) { + stats = makeMapStats(stats); + } + if (browserDetails.version < 53 && !onSucc) { + // Shim only promise getStats with spec-hyphens in type names + // Leave callback version alone; misc old uses of forEach before Map + try { + stats.forEach(function(stat) { + stat.type = modernStatsTypes[stat.type] || stat.type; + }); + } catch (e) { + if (e.name !== 'TypeError') { + throw e; + } + // Avoid TypeError: "type" is read-only, in old versions. 34-43ish + stats.forEach(function(stat, i) { + stats.set(i, Object.assign({}, stat, { + type: modernStatsTypes[stat.type] || stat.type + })); + }); + } + } + return stats; + }) + .then(onSucc, onErr); + }; + }, + + shimRemoveStream: function(window) { + if (!window.RTCPeerConnection || + 'removeStream' in window.RTCPeerConnection.prototype) { + return; + } + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var pc = this; + utils.deprecated('removeStream', 'removeTrack'); + this.getSenders().forEach(function(sender) { + if (sender.track && stream.getTracks().indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } +}; + +},{"../utils":13,"./getusermedia":11}],11:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var utils = require('../utils'); +var logging = utils.log; + +// Expose public methods. +module.exports = function(window) { + var browserDetails = utils.detectBrowser(window); + var navigator = window && window.navigator; + var MediaStreamTrack = window && window.MediaStreamTrack; + + var shimError_ = function(e) { + return { + name: { + InternalError: 'NotReadableError', + NotSupportedError: 'TypeError', + PermissionDeniedError: 'NotAllowedError', + SecurityError: 'NotAllowedError' + }[e.name] || e.name, + message: { + 'The operation is insecure.': 'The request is not allowed by the ' + + 'user agent or the platform in the current context.' + }[e.message] || e.message, + constraint: e.constraint, + toString: function() { + return this.name + (this.message && ': ') + this.message; + } + }; + }; + + // getUserMedia constraints shim. + var getUserMedia_ = function(constraints, onSuccess, onError) { + var constraintsToFF37_ = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r. min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + constraints = JSON.parse(JSON.stringify(constraints)); + if (browserDetails.version < 38) { + logging('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37_(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37_(constraints.video); + } + logging('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, function(e) { + onError(shimError_(e)); + }); + }; + + // Returns the result of getUserMedia as a Promise. + var getUserMediaPromise_ = function(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia_(constraints, resolve, reject); + }); + }; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: getUserMediaPromise_, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (browserDetails.version < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().then(undefined, function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } + if (browserDetails.version < 49) { + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + return origGetUserMedia(c).then(function(stream) { + // Work around https://bugzil.la/802326 + if (c.audio && !stream.getAudioTracks().length || + c.video && !stream.getVideoTracks().length) { + stream.getTracks().forEach(function(track) { + track.stop(); + }); + throw new DOMException('The object can not be found here.', + 'NotFoundError'); + } + return stream; + }, function(e) { + return Promise.reject(shimError_(e)); + }); + }; + } + if (!(browserDetails.version > 55 && + 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { + var remap = function(obj, a, b) { + if (a in obj && !(b in obj)) { + obj[b] = obj[a]; + delete obj[a]; + } + }; + + var nativeGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + if (typeof c === 'object' && typeof c.audio === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); + remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeGetUserMedia(c); + }; + + if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { + var nativeGetSettings = MediaStreamTrack.prototype.getSettings; + MediaStreamTrack.prototype.getSettings = function() { + var obj = nativeGetSettings.apply(this, arguments); + remap(obj, 'mozAutoGainControl', 'autoGainControl'); + remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); + return obj; + }; + } + + if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { + var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; + MediaStreamTrack.prototype.applyConstraints = function(c) { + if (this.kind === 'audio' && typeof c === 'object') { + c = JSON.parse(JSON.stringify(c)); + remap(c, 'autoGainControl', 'mozAutoGainControl'); + remap(c, 'noiseSuppression', 'mozNoiseSuppression'); + } + return nativeApplyConstraints.apply(this, [c]); + }; + } + } + navigator.getUserMedia = function(constraints, onSuccess, onError) { + if (browserDetails.version < 44) { + return getUserMedia_(constraints, onSuccess, onError); + } + // Replace Firefox 44+'s deprecation warning with unprefixed version. + utils.deprecated('navigator.getUserMedia', + 'navigator.mediaDevices.getUserMedia'); + navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); + }; +}; + +},{"../utils":13}],12:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ +'use strict'; +var utils = require('../utils'); + +module.exports = { + shimLocalStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getLocalStreams = function() { + if (!this._localStreams) { + this._localStreams = []; + } + return this._localStreams; + }; + } + if (!('getStreamById' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getStreamById = function(id) { + var result = null; + if (this._localStreams) { + this._localStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + if (this._remoteStreams) { + this._remoteStreams.forEach(function(stream) { + if (stream.id === id) { + result = stream; + } + }); + } + return result; + }; + } + if (!('addStream' in window.RTCPeerConnection.prototype)) { + var _addTrack = window.RTCPeerConnection.prototype.addTrack; + window.RTCPeerConnection.prototype.addStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + var pc = this; + stream.getTracks().forEach(function(track) { + _addTrack.call(pc, track, stream); + }); + }; + + window.RTCPeerConnection.prototype.addTrack = function(track, stream) { + if (stream) { + if (!this._localStreams) { + this._localStreams = [stream]; + } else if (this._localStreams.indexOf(stream) === -1) { + this._localStreams.push(stream); + } + } + return _addTrack.call(this, track, stream); + }; + } + if (!('removeStream' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.removeStream = function(stream) { + if (!this._localStreams) { + this._localStreams = []; + } + var index = this._localStreams.indexOf(stream); + if (index === -1) { + return; + } + this._localStreams.splice(index, 1); + var pc = this; + var tracks = stream.getTracks(); + this.getSenders().forEach(function(sender) { + if (tracks.indexOf(sender.track) !== -1) { + pc.removeTrack(sender); + } + }); + }; + } + }, + shimRemoteStreamsAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { + window.RTCPeerConnection.prototype.getRemoteStreams = function() { + return this._remoteStreams ? this._remoteStreams : []; + }; + } + if (!('onaddstream' in window.RTCPeerConnection.prototype)) { + Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { + get: function() { + return this._onaddstream; + }, + set: function(f) { + var pc = this; + if (this._onaddstream) { + this.removeEventListener('addstream', this._onaddstream); + this.removeEventListener('track', this._onaddstreampoly); + } + this.addEventListener('addstream', this._onaddstream = f); + this.addEventListener('track', this._onaddstreampoly = function(e) { + e.streams.forEach(function(stream) { + if (!pc._remoteStreams) { + pc._remoteStreams = []; + } + if (pc._remoteStreams.indexOf(stream) >= 0) { + return; + } + pc._remoteStreams.push(stream); + var event = new Event('addstream'); + event.stream = stream; + pc.dispatchEvent(event); + }); + }); + } + }); + } + }, + shimCallbacksAPI: function(window) { + if (typeof window !== 'object' || !window.RTCPeerConnection) { + return; + } + var prototype = window.RTCPeerConnection.prototype; + var createOffer = prototype.createOffer; + var createAnswer = prototype.createAnswer; + var setLocalDescription = prototype.setLocalDescription; + var setRemoteDescription = prototype.setRemoteDescription; + var addIceCandidate = prototype.addIceCandidate; + + prototype.createOffer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createOffer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + prototype.createAnswer = function(successCallback, failureCallback) { + var options = (arguments.length >= 2) ? arguments[2] : arguments[0]; + var promise = createAnswer.apply(this, [options]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + + var withCallback = function(description, successCallback, failureCallback) { + var promise = setLocalDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setLocalDescription = withCallback; + + withCallback = function(description, successCallback, failureCallback) { + var promise = setRemoteDescription.apply(this, [description]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.setRemoteDescription = withCallback; + + withCallback = function(candidate, successCallback, failureCallback) { + var promise = addIceCandidate.apply(this, [candidate]); + if (!failureCallback) { + return promise; + } + promise.then(successCallback, failureCallback); + return Promise.resolve(); + }; + prototype.addIceCandidate = withCallback; + }, + shimGetUserMedia: function(window) { + var navigator = window && window.navigator; + + if (!navigator.getUserMedia) { + if (navigator.webkitGetUserMedia) { + navigator.getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + } else if (navigator.mediaDevices && + navigator.mediaDevices.getUserMedia) { + navigator.getUserMedia = function(constraints, cb, errcb) { + navigator.mediaDevices.getUserMedia(constraints) + .then(cb, errcb); + }.bind(navigator); + } + } + }, + shimRTCIceServerUrls: function(window) { + // migrate from non-spec RTCIceServer.url to RTCIceServer.urls + var OrigPeerConnection = window.RTCPeerConnection; + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (!server.hasOwnProperty('urls') && + server.hasOwnProperty('url')) { + utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); + server = JSON.parse(JSON.stringify(server)); + server.urls = server.url; + delete server.url; + newIceServers.push(server); + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + return new OrigPeerConnection(pcConfig, pcConstraints); + }; + window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; + // wrap static methods. Currently just generateCertificate. + if ('generateCertificate' in window.RTCPeerConnection) { + Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { + get: function() { + return OrigPeerConnection.generateCertificate; + } + }); + } + }, + shimTrackEventTransceiver: function(window) { + // Add event.transceiver member over deprecated event.receiver + if (typeof window === 'object' && window.RTCPeerConnection && + ('receiver' in window.RTCTrackEvent.prototype) && + // can't check 'transceiver' in window.RTCTrackEvent.prototype, as it is + // defined for some reason even when window.RTCTransceiver is not. + !window.RTCTransceiver) { + Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { + get: function() { + return {receiver: this.receiver}; + } + }); + } + }, + + shimCreateOfferLegacy: function(window) { + var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; + window.RTCPeerConnection.prototype.createOffer = function(offerOptions) { + var pc = this; + if (offerOptions) { + var audioTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'audio'; + }); + if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { + if (audioTransceiver.direction === 'sendrecv') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('sendonly'); + } else { + audioTransceiver.direction = 'sendonly'; + } + } else if (audioTransceiver.direction === 'recvonly') { + if (audioTransceiver.setDirection) { + audioTransceiver.setDirection('inactive'); + } else { + audioTransceiver.direction = 'inactive'; + } + } + } else if (offerOptions.offerToReceiveAudio === true && + !audioTransceiver) { + pc.addTransceiver('audio'); + } + + var videoTransceiver = pc.getTransceivers().find(function(transceiver) { + return transceiver.sender.track && + transceiver.sender.track.kind === 'video'; + }); + if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { + if (videoTransceiver.direction === 'sendrecv') { + videoTransceiver.setDirection('sendonly'); + } else if (videoTransceiver.direction === 'recvonly') { + videoTransceiver.setDirection('inactive'); + } + } else if (offerOptions.offerToReceiveVideo === true && + !videoTransceiver) { + pc.addTransceiver('video'); + } + } + return origCreateOffer.apply(pc, arguments); + }; + } +}; + +},{"../utils":13}],13:[function(require,module,exports){ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. + */ + /* eslint-env node */ +'use strict'; + +var logDisabled_ = true; +var deprecationWarnings_ = true; + +/** + * Extract browser version out of the provided user agent string. + * + * @param {!string} uastring userAgent string. + * @param {!string} expr Regular expression used as match criteria. + * @param {!number} pos position in the version string to be returned. + * @return {!number} browser version. + */ +function extractVersion(uastring, expr, pos) { + var match = uastring.match(expr); + return match && match.length >= pos && parseInt(match[pos], 10); +} + +// Wraps the peerconnection event eventNameToWrap in a function +// which returns the modified event object. +function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { + if (!window.RTCPeerConnection) { + return; + } + var proto = window.RTCPeerConnection.prototype; + var nativeAddEventListener = proto.addEventListener; + proto.addEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap) { + return nativeAddEventListener.apply(this, arguments); + } + var wrappedCallback = function(e) { + cb(wrapper(e)); + }; + this._eventMap = this._eventMap || {}; + this._eventMap[cb] = wrappedCallback; + return nativeAddEventListener.apply(this, [nativeEventName, + wrappedCallback]); + }; + + var nativeRemoveEventListener = proto.removeEventListener; + proto.removeEventListener = function(nativeEventName, cb) { + if (nativeEventName !== eventNameToWrap || !this._eventMap + || !this._eventMap[cb]) { + return nativeRemoveEventListener.apply(this, arguments); + } + var unwrappedCb = this._eventMap[cb]; + delete this._eventMap[cb]; + return nativeRemoveEventListener.apply(this, [nativeEventName, + unwrappedCb]); + }; + + Object.defineProperty(proto, 'on' + eventNameToWrap, { + get: function() { + return this['_on' + eventNameToWrap]; + }, + set: function(cb) { + if (this['_on' + eventNameToWrap]) { + this.removeEventListener(eventNameToWrap, + this['_on' + eventNameToWrap]); + delete this['_on' + eventNameToWrap]; + } + if (cb) { + this.addEventListener(eventNameToWrap, + this['_on' + eventNameToWrap] = cb); + } + } + }); +} + +// Utility methods. +module.exports = { + extractVersion: extractVersion, + wrapPeerConnectionEvent: wrapPeerConnectionEvent, + disableLog: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + logDisabled_ = bool; + return (bool) ? 'adapter.js logging disabled' : + 'adapter.js logging enabled'; + }, + + /** + * Disable or enable deprecation warnings + * @param {!boolean} bool set to true to disable warnings. + */ + disableWarnings: function(bool) { + if (typeof bool !== 'boolean') { + return new Error('Argument type: ' + typeof bool + + '. Please use a boolean.'); + } + deprecationWarnings_ = !bool; + return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); + }, + + log: function() { + if (typeof window === 'object') { + if (logDisabled_) { + return; + } + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); + } + } + }, + + /** + * Shows a deprecation warning suggesting the modern and spec-compatible API. + */ + deprecated: function(oldMethod, newMethod) { + if (!deprecationWarnings_) { + return; + } + console.warn(oldMethod + ' is deprecated, please use ' + newMethod + + ' instead.'); + }, + + /** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ + detectBrowser: function(window) { + var navigator = window && window.navigator; + + // Returned result object. + var result = {}; + result.browser = null; + result.version = null; + + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator) { + result.browser = 'Not a browser.'; + return result; + } + + if (navigator.mozGetUserMedia) { // Firefox. + result.browser = 'firefox'; + result.version = extractVersion(navigator.userAgent, + /Firefox\/(\d+)\./, 1); + } else if (navigator.webkitGetUserMedia) { + // Chrome, Chromium, Webview, Opera. + // Version matches Chrome/WebRTC version. + result.browser = 'chrome'; + result.version = extractVersion(navigator.userAgent, + /Chrom(e|ium)\/(\d+)\./, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. + result.browser = 'edge'; + result.version = extractVersion(navigator.userAgent, + /Edge\/(\d+).(\d+)$/, 2); + } else if (navigator.mediaDevices && + navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. + result.browser = 'safari'; + result.version = extractVersion(navigator.userAgent, + /AppleWebKit\/(\d+)\./, 1); + } else { // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + + return result; + } +}; + +},{}]},{},[3])(3) +}); \ No newline at end of file